mount public data from the atmosphere to a virtual filesystem (macos only) pdfs.at
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(atproto/oauth): proper form-URL-encoding for POST bodies

+58 -3
+23
Packages/ATProto/Sources/ATProto/OAuth/Flow/FormURLEncoded.swift
··· 1 + import Foundation 2 + 3 + /// `application/x-www-form-urlencoded` body encoder. 4 + /// Percent-encodes everything except unreserved chars (alphanumerics + `-._~`). 5 + /// Spaces become `+`. 6 + enum FormURLEncoded { 7 + private static let unreserved: CharacterSet = { 8 + var s = CharacterSet.alphanumerics 9 + s.insert(charactersIn: "-._~") 10 + return s 11 + }() 12 + 13 + static func encode(_ pairs: [String: String]) -> Data { 14 + pairs.map { "\(encode($0.key))=\(encode($0.value))" } 15 + .joined(separator: "&") 16 + .data(using: .utf8) ?? Data() 17 + } 18 + 19 + static func encode(_ s: String) -> String { 20 + let escaped = s.addingPercentEncoding(withAllowedCharacters: unreserved) ?? "" 21 + return escaped.replacingOccurrences(of: "%20", with: "+") 22 + } 23 + }
+1 -3
Packages/ATProto/Sources/ATProto/OAuth/Flow/PushedAuthorizationRequest.swift
··· 53 53 body: [String: String], 54 54 key: any DPoPKey 55 55 ) async throws -> (Data, HTTPURLResponse) { 56 - let formBody = body.map { "\($0.key)=\($0.value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")" } 57 - .joined(separator: "&") 58 - .data(using: .utf8)! 56 + let formBody = FormURLEncoded.encode(body) 59 57 60 58 for attempt in 0..<2 { 61 59 var request = URLRequest(url: endpoint)
+34
Packages/ATProto/Tests/ATProtoTests/OAuth/FormURLEncodedTests.swift
··· 1 + import Foundation 2 + import Testing 3 + @testable import ATProto 4 + 5 + @Suite("FormURLEncoded") 6 + struct FormURLEncodedTests { 7 + @Test("unreserved chars pass through") 8 + func unreserved() { 9 + #expect(FormURLEncoded.encode("abcXYZ123-._~") == "abcXYZ123-._~") 10 + } 11 + 12 + @Test("spaces become +") 13 + func spaces() { 14 + #expect(FormURLEncoded.encode("hello world") == "hello+world") 15 + } 16 + 17 + @Test("reserved chars get percent-encoded") 18 + func reserved() { 19 + #expect(FormURLEncoded.encode("a+b") == "a%2Bb") 20 + #expect(FormURLEncoded.encode("a&b") == "a%26b") 21 + #expect(FormURLEncoded.encode("a=b") == "a%3Db") 22 + #expect(FormURLEncoded.encode("a/b") == "a%2Fb") 23 + #expect(FormURLEncoded.encode("a?b") == "a%3Fb") 24 + } 25 + 26 + @Test("pairs encode both key and value") 27 + func pairs() { 28 + let body: [String: String] = ["client_id": "https://example.com/meta.json", "scope": "atproto repo:*"] 29 + let encoded = String(data: FormURLEncoded.encode(body), encoding: .utf8) ?? "" 30 + // dict iteration order is nondeterministic; check both keys+values are properly encoded. 31 + #expect(encoded.contains("client_id=https%3A%2F%2Fexample.com%2Fmeta.json")) 32 + #expect(encoded.contains("scope=atproto+repo%3A%2A")) 33 + } 34 + }