at-internet / atinternet-apple-sdk

AT Internet mobile analytics solution for Apple devices
http://www.atinternet.com
MIT License
25 stars 47 forks source link

iOS 17 Double-encoded URL strings #139

Closed bloxidge closed 8 months ago

bloxidge commented 8 months ago

Issue

Apple has updated their API for URL(string:): https://developer.apple.com/documentation/foundation/url/3126806-init

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.

This means that if the provided string argument has already been percent-encoded, the returned URL will re-encode appearances of the % character.

The Tracker API does not provide full customisation to provide ParamOption with encode = false because it is built with the older Apple API in mind, e.g. Tracker.setProp(key:value:persistent:) forces encoding of the parameter.

Solution

Since it is impossible to know whether the SDK will be compiled by the developer with the iOS 17 SDK or not, the implementation cannot be updated to simply accommodate the new RFC 3986 encoding, as this would break for anyone still compiling with the older Apple SDKs.

Any instances of URL(string:) must be replaced by URL(string:encodingInvalidCharacters:) with encodingInvalidCharacters set to false.

Since this is only available in iOS 17.0 or above, it will need to be wrapped in an availability check:

let URL: Foundation.URL?
if #available(iOS 17.0, *) {
    URL = Foundation.URL(string: hit.url, encodingInvalidCharacters: false)
} else {
    URL = Foundation.URL(string: hit.url)
}
bloxidge commented 8 months ago

The above solution is not adequate if the Hit URL string has been partially encoded. If some params remain without percent-encoding, the encodingInvalidCharacters: false flag will result in failing to form the URL.

The suitable alternative solution is to attempt to percent-decode the string before passing to the iOS 17 API, e.g.

extension URL {
    init?(percentEncodedString string: String) {
        if #available(iOS 17.0, *) {
            self.init(string: string.percentDecodedString)
        } else {
            self.init(string: string)
        }
    }
}

then replace all call-sites for URL(string:) with URL(percentEncodedString:).

NB: If the string provided has no percent-encoding, the percentDecodedString will return the same result and the API will not break.

BenDz commented 8 months ago

Hi @bloxidge Thank you for your submissions! We don't support this SDK anymore. But regarding the impact of this API change on Apple's side, we will challenge our capability to deliver an update on that.

piano-analytics commented 8 months ago

Thank you, 2.23.10 released