Skip to content

0x03b-iOS中url编码问题探究

  • #问题
  • 多次url编码问题/url嵌套多层的情况
  • URL初始化的时候的问题
  • iOS17的变化

iOS上的URL编码测试

带特殊字符编码 https://zh.wikipedia.org/wiki/春節

swift
var url = URL(string: "https://zh.wikipedia.org/wiki/春節")
print("result is:", url) 
// iOS18模拟器: Optional(https://zh.wikipedia.org/wiki/%E6%98%A5%E7%AF%80)
// iOS15.6设备: nil
var url = URL(string: "https://zh.wikipedia.org/wiki/春節")
print("result is:", url) 
// iOS18模拟器: Optional(https://zh.wikipedia.org/wiki/%E6%98%A5%E7%AF%80)
// iOS15.6设备: nil

经过验证,可以看到iOS17(似乎)和iOS17以下的设备的表现是不一样的。带有非法字符的时候,低版本的设备上URL会返回nil。

因此在开发中,有时候为了增加代码容错率,可能会这样处理(如下),实际上是不合理的:

swift
let urlText = "https://zh.wikipedia.org/wiki/春節"
let charSet = CharacterSet.urlQueryAllowed
charSet.insert(charactersIn: "#")
let encodingURL = urlText.addingPercentEncoding(withAllowedCharacters: charSet)
// 或   [string stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
url = URL(string: encodingURL ?? "")
// Optional(https://zh.wikipedia.org/wiki/%E6%98%A5%E7%AF%80)
let urlText = "https://zh.wikipedia.org/wiki/春節"
let charSet = CharacterSet.urlQueryAllowed
charSet.insert(charactersIn: "#")
let encodingURL = urlText.addingPercentEncoding(withAllowedCharacters: charSet)
// 或   [string stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
url = URL(string: encodingURL ?? "")
// Optional(https://zh.wikipedia.org/wiki/%E6%98%A5%E7%AF%80)

因为这样可能会导致两次编码,比如有重定向的参数:

swift
urlText = "https://xz.com?a=https%3A%2F%2Fbaidu.com"
var charSet = CharacterSet.urlQueryAllowed
charSet.insert(charactersIn: "#")
let encodingURL = urlText.addingPercentEncoding(withAllowedCharacters: charSet)
url = URL(string: encodingURL ?? "")
print("result is:", url)
// Optional(https://xz.com?a=https%253A%252F%252Fbaidu.com)
urlText = "https://xz.com?a=https%3A%2F%2Fbaidu.com"
var charSet = CharacterSet.urlQueryAllowed
charSet.insert(charactersIn: "#")
let encodingURL = urlText.addingPercentEncoding(withAllowedCharacters: charSet)
url = URL(string: encodingURL ?? "")
print("result is:", url)
// Optional(https://xz.com?a=https%253A%252F%252Fbaidu.com)

这样会导致前端需要两次decode才能取到参数值。

绕过二次编码问题

如果绕过的话,可以将特殊字符的参数进行编码,不过不是最优解:

swift
// swift
extension String {
    func toHex() -> String {
        let data1 = Data(self.utf8)
        let hexString = data1.map { String(format: "%02x", $0) }.joined()
        return hexString
    }
}
// js
function hex2String (hexx) {  
  const hex = hexx.toString();// force conversion  
  let str = '';  
  for (let i = 0; i < hex.length; i += 2) { str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); }  
  return str;  
}
// swift
extension String {
    func toHex() -> String {
        let data1 = Data(self.utf8)
        let hexString = data1.map { String(format: "%02x", $0) }.joined()
        return hexString
    }
}
// js
function hex2String (hexx) {  
  const hex = hexx.toString();// force conversion  
  let str = '';  
  for (let i = 0; i < hex.length; i += 2) { str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); }  
  return str;  
}

额外的,看到了URLComponents的api:

iOS 17 的 URL 和 16 的 URLComponents 可以自動對網址進行 URL Encoding

swift
var urlString = "https://zh.wikipedia.org/wiki/春節#123"
if let url = URLComponents(string: urlString)?.url {
   print(url) // 发现iOS15上是nil  18上是ok的 。也许iOS16以上才会自动编码?
}
urlString = "https://xz.com?a=https%3A%2F%2Fbaidu.com"
if let url = URLComponents(string: urlString)?.url {
   print(url)
}
var urlString = "https://zh.wikipedia.org/wiki/春節#123"
if let url = URLComponents(string: urlString)?.url {
   print(url) // 发现iOS15上是nil  18上是ok的 。也许iOS16以上才会自动编码?
}
urlString = "https://xz.com?a=https%3A%2F%2Fbaidu.com"
if let url = URLComponents(string: urlString)?.url {
   print(url)
}

ios17的变化

Important

For apps linked on or after iOS 17 and aligned OS versions, URL parsing has updated from the obsolete RFC 1738/1808 parsing to the same RFC 3986 parsing as URLComponents. This unifies the parsing behaviors of the URL and URLComponents APIs. Now, URL automatically percent- and IDNA-encodes invalid characters to help create a valid URL.

RFC 1738/1808 -> RFC 3986