URLComponents queryItems losing percent encoding w

2019-08-04 02:23发布

问题:

When using URLComponents's queryItems I've found that if you have a query item whose value contains some percent encoded characters, in my case a / being encoded as %2F, then if you construct a URLComponents object from a String URL that contains such a query item, then mutate the list of query items for the URLComponents object, then if you try to get a URL by calling .url on the URLComponents object, then the query items lose their percent encoding.

Here's the code I've been testing this with in a playground:

import UIKit

// --- Part 1 ---
print("--- Part 1 ---\n")

let startURL = "https://test.com/test.jpg?X-Test-Token=FQdzEPH%2F%2F%2F"
var components = URLComponents(string: startURL)!

if let compURL = components.url {
    print(URL(string: startURL)! == compURL) // True
    print(startURL)
    print(compURL)
}

// --- Part 2 ---
print("\n--- Part 2 ---\n")

let startURLTwo = "https://test.com/test.jpg?X-Test-Token=FQdzEPH%2F%2F%2F"
let finalURL = "https://test.com/test.jpg?X-Test-Token=FQdzEPH%2F%2F%2F&foo=bar"
var componentsTwo = URLComponents(string: startURLTwo)!

let extraQueryItem = URLQueryItem(name: "foo", value: "bar")
componentsTwo.queryItems!.append(extraQueryItem)

if let compURLTwo = componentsTwo.url {
    print(URL(string: finalURL)! == compURLTwo) // False
    print(finalURL)
    print(compURLTwo)
}

Here's an image if that makes it easier to understand what's going on:

回答1:

You should use percentEncodedQuery if you have a query that is already percent encoded:

let startURL = "https://test.com/test.jpg"
var components = URLComponents(string: startURL)!
components.percentEncodedQuery = "X-Test-Token=FQdzEPH%2F%2F%2F"

if let compURL = components.url {
    print(compURL)
}

Or you can specify it unescaped (and it leaves it unescaped as it's not necessary to escape / characters in a query):

let startURL = "https://test.com/test.jpg"
var components = URLComponents(string: startURL)!
components.queryItems = [URLQueryItem(name: "X-Test-Token", value: "FQdzEPH///")]

if let compURL = components.url {
    print(compURL)
}

And if you have to update queryItems, just make sure to set percentEncodedQuery at the very end:

let startURL = "https://test.com/test.jpg"
let encodedQuery = "X-Test-Token=FQdzEPH%2F%2F%2F"
var components = URLComponents(string: startURL)!
components.queryItems = [URLQueryItem(name: "foo", value: "bar, baz, & qux")]
if let query = components.percentEncodedQuery {
    components.percentEncodedQuery = query + "&" + encodedQuery
} else {
    components.percentEncodedQuery = encodedQuery
}

if let compURL = components.url {
    print(compURL)
}


回答2:

RFC 3986 specifically states that a URL query may contain the / character. It doesn't need to be percent encoded. URLComponents is simply following the standard and unencoding the %2F to / when you specifically modify any of the query parameters.

In the first case you don't modify anything at all so the URL stays unchanged. In the 2nd, you modify the query parameters property of the components. So the URLComponents builds a new query string from that updated array of query parameters. In the process, if normalizes all of them and the unnecessary percent encoding is removed.