|
3 | 3 | // S3Middleware |
4 | 4 | // |
5 | 5 | // Created by Greg Neagle on 5/11/25. |
| 6 | +// Modified by Adam Anklewicz on 2026-01-05 to allow percent encoding of perenthesis (using ISO formatted date as 1/5/26 is possibly January 5 or 1 May depending on locale). |
6 | 7 | // |
7 | 8 | // A proof-of-concept port of Wade Robson's s3 auth middleware |
8 | 9 | // https://github.com/waderobson/s3-auth |
@@ -87,12 +88,29 @@ class S3RequestHeadersBuilder { |
87 | 88 | // populate hashedRequest |
88 | 89 | createCanonicalRequestHash() |
89 | 90 | } |
| 91 | + |
| 92 | + /* Function to allow () to be percentage encoded. |
| 93 | + Function tells it what characters to not encode, then encodes the rest. |
| 94 | + Documentation can be found here: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv-create-signed-request.html#sig-v4-examples-get-auth-header |
| 95 | + As per this documentation, one must URI encode every character |
| 96 | + URI encode every byte except the unreserved characters: 'A'-'Z', 'a'-'z', '0'-'9', '-', '.', '_', and '~'. |
| 97 | + The space character is a reserved character and must be encoded as "%20" (and not as "+"). |
| 98 | + Encode the forward slash character, '/', everywhere except in the object key name. For example, if the object key name is photos/Jan/sample.jpg, the forward slash in the key name is not encoded. */ |
| 99 | + private func awsUriEncode(_ string: String) -> String { |
| 100 | + var allowed = CharacterSet.alphanumerics // A-Z, a-z, 0-9 must not be encoded, so it's added to allowed. |
| 101 | + allowed.insert(charactersIn: "-._~") // Per the documentation, - . _ and ~ must not be encoded, so they are added to the allowed list. |
| 102 | + allowed.insert(charactersIn: "/") // Slashes must not be encoded, so add it to the allowed list of characters. |
| 103 | + return string.addingPercentEncoding(withAllowedCharacters: allowed) ?? string // addingPercentEncoding is built into Swift and will percent encode while passing it a list of allowed characters. |
| 104 | + } |
90 | 105 |
|
91 | 106 | /// build a canonical request string |
92 | 107 | func createCanonicalRequestHash() { |
93 | 108 | let method = "GET" |
94 | 109 | guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return } |
95 | | - let canonicalURI = components.percentEncodedPath |
| 110 | + |
| 111 | + // Call the function to handle () in the path |
| 112 | + let canonicalURI = awsUriEncode(url.path) |
| 113 | + |
96 | 114 | let host = components.percentEncodedHost ?? "" |
97 | 115 | let canonicalizedQueryString = components.percentEncodedQuery ?? "" |
98 | 116 | let canonicalHeaders = "host:\(host)\nx-amz-date:\(amzDate)\n" |
|
0 commit comments