From c6176696e55caad65701e8fcc4684a7e885570d6 Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Fri, 6 Dec 2024 02:58:39 -0800 Subject: [PATCH 1/2] Added proper Base64 URL coding support --- .../DataProtocol+Base64URLCoding.swift | 33 +++++++++++++++++++ Sources/WebPush/VAPID/VAPIDKey.swift | 2 +- Tests/WebPushTests/Base64URLCodingTests.swift | 25 ++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 Sources/WebPush/Helpers/DataProtocol+Base64URLCoding.swift create mode 100644 Tests/WebPushTests/Base64URLCodingTests.swift diff --git a/Sources/WebPush/Helpers/DataProtocol+Base64URLCoding.swift b/Sources/WebPush/Helpers/DataProtocol+Base64URLCoding.swift new file mode 100644 index 0000000..ef615ad --- /dev/null +++ b/Sources/WebPush/Helpers/DataProtocol+Base64URLCoding.swift @@ -0,0 +1,33 @@ +// +// DataProtocol+Base64URLCoding.swift +// swift-webpush +// +// Created by Dimitri Bouniol on 2024-12-06. +// Copyright © 2024 Mochi Development, Inc. All rights reserved. +// + +import Foundation + +extension DataProtocol { + func base64URLEncodedString() -> String { + Data(self) + .base64EncodedString() + .replacingOccurrences(of: "+", with: "-") + .replacingOccurrences(of: "/", with: "_") + .replacingOccurrences(of: "=", with: "") + } +} + +extension DataProtocol where Self: RangeReplaceableCollection { + init?(base64URLEncoded string: String) { + var base64String = string.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/") + while base64String.count % 4 != 0 { + base64String = base64String.appending("=") + } + + guard let decodedData = Data(base64Encoded: base64String) + else { return nil } + + self = Self(decodedData) + } +} diff --git a/Sources/WebPush/VAPID/VAPIDKey.swift b/Sources/WebPush/VAPID/VAPIDKey.swift index 40e8d38..4763c25 100644 --- a/Sources/WebPush/VAPID/VAPIDKey.swift +++ b/Sources/WebPush/VAPID/VAPIDKey.swift @@ -69,6 +69,6 @@ extension VAPID.Key: Identifiable { } public var id: ID { - ID(privateKey.publicKey.x963Representation.base64EncodedString()) // TODO: make url-safe + ID(privateKey.publicKey.x963Representation.base64URLEncodedString()) } } diff --git a/Tests/WebPushTests/Base64URLCodingTests.swift b/Tests/WebPushTests/Base64URLCodingTests.swift new file mode 100644 index 0000000..759ceec --- /dev/null +++ b/Tests/WebPushTests/Base64URLCodingTests.swift @@ -0,0 +1,25 @@ +// +// Base64URLCodingTests.swift +// swift-webpush +// +// Created by Dimitri Bouniol on 2024-12-06. +// Copyright © 2024 Mochi Development, Inc. All rights reserved. +// + +import Foundation +import Testing +@testable import WebPush + +@Test func base64URLDecoding() async throws { + let string = ">>> Hello, swift-webpush world??? 🎉" + let base64Encoded = "Pj4+IEhlbGxvLCBzd2lmdC13ZWJwdXNoIHdvcmxkPz8/IPCfjok=" + let base64URLEncoded = "Pj4-IEhlbGxvLCBzd2lmdC13ZWJwdXNoIHdvcmxkPz8_IPCfjok" + #expect(String(decoding: Data(base64URLEncoded: base64Encoded)!, as: UTF8.self) == string) + #expect(String(decoding: Data(base64URLEncoded: base64URLEncoded)!, as: UTF8.self) == string) +} + +@Test func base64URLEncoding() async throws { + let string = ">>> Hello, swift-webpush world??? 🎉" + let base64URLEncoded = "Pj4-IEhlbGxvLCBzd2lmdC13ZWJwdXNoIHdvcmxkPz8_IPCfjok" + #expect(Array(string.utf8).base64URLEncodedString() == base64URLEncoded) +} From 770fb516b55bd4eee6598e11963b50a809926fc0 Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Fri, 6 Dec 2024 03:06:02 -0800 Subject: [PATCH 2/2] Fixed an issue building on non-apple platforms --- Sources/WebPush/WebPushManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/WebPush/WebPushManager.swift b/Sources/WebPush/WebPushManager.swift index d668852..2ecf2ff 100644 --- a/Sources/WebPush/WebPushManager.swift +++ b/Sources/WebPush/WebPushManager.swift @@ -25,7 +25,7 @@ actor WebPushManager: Service, Sendable { vapidConfiguration: VAPID.Configuration, // TODO: Add networkConfiguration for proxy, number of simultaneous pushes, etc… logger: Logger? = nil, - eventLoopGroupProvider: NIOEventLoopGroupProvider = .shared(.singletonNIOTSEventLoopGroup) + eventLoopGroupProvider: NIOEventLoopGroupProvider = .shared(.singletonMultiThreadedEventLoopGroup) ) { self.vapidConfiguration = vapidConfiguration let allKeys = vapidConfiguration.keys + Array(vapidConfiguration.deprecatedKeys ?? [])