URL Encoding doesn't work with iOS 17 which is working with iOS 16

We are working on URLs with special characters and also Unicode characters and it is not working with iOS 17 which was working fine with iOS 16 and below iOS versions

For example, URL consists of symbols like @&₹, and URL encoding provides the same output for both iOS 17 and 16 versions but the URL getting failed with the following error

Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory" UserInfo={NSErrorFailingURLStringKey=http://192.168.1/api/rest/contents/Shan%27s%20iPhone%20%40%24%E2%82%B9/test.jpg, NSErrorFailingURLKey=http://192.168.1/api/rest/contents/Shan%27s%20iPhone%20%40%24%E2%82%B9/test.jpg, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDownloadTask <140D8A75-0079-4479-ADD5-CA50077C85F0>.<1>"
), _NSURLErrorFailingURLSessionTaskErrorKey=LocalDownloadTask <140D8A75-0079-4479-ADD5-CA50077C85F0>.<1>}

Here is the function we are using to encode

func urlEncode(urlPath: String) -> String? {
        let generalDelimitersToEncode = "#[]@" 
        let subDelimitersToEncode = "!$&'()*+,;="
        
        var allowedCharacterSet = CharacterSet.urlQueryAllowed
        allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
        return urlPath.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet)
    }

/* 
Input: http://192.168.1/api/rest/contents/Shan's iPhone @$₹/test.jpg

Output in iOS 17: http://192.168.1/api/rest/contents/Shan%27s%20iPhone%20%40%24%E2%82%B9/test.jpg

Output in iOS 16 and below:
http://192.168.1/api/rest/contents/Shan%27s%20iPhone%20%40%24%E2%82%B9/test.jpg
*/

Question: Is there a way to hanle this in iOS 17 ?

I am facing the same issue while encoding he email like this

let customAllowedSet =  NSCharacterSet(charactersIn: "=\"#%/<>?@\\^`{|}+").inverted
email.addingPercentEncoding(withAllowedCharacters: customAllowedSet)

when I try example@something.com it returns this way

iOS 17: example%2540something.com iOS 16: example%40something.com

Any quick solutions for this issue?

Output in iOS 17: http://192.168.1/api/rest/contents/Shan%27s%20iPhone%20%40%24%E2%82%B9/test.jpg
Output in iOS 16: http://192.168.1/api/rest/contents/Shan%27s%20iPhone%20%40%24%E2%82%B9/test.jpg

Unless I've gone blind, those outputs are identical. So I believe your problem is not with the percent-encoding, which is unchanged, but with something else.

One thing to look at: 192.168.1 is not a valid IP address. Also, http: (rather than https:) may be blocked by security features.

A more general observation: generally, it's best to do percent-encoding on the individual components of the URL before joining them together. You seem to be taking a complete URL and then trying to encode it. That can work if you know that your query parameters (etc.) have a limited character set, but it doesn't work in general.

I can confirm the bug. The URLs look the same in the logs - that's true, but in fact they are encoded two times. This seems to be a critical problem.

I can confirm I have the same issue in encoding Data to a String, using ASCII encoding. Works as expected on iOS 16, but differently on iOS 17.

After hours of confusion for us it seems the answer was that iOS 17 now automatically encodes query parameters (seemingly regardless of whether or not they are already URL-encoded).

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.

https://developer.apple.com/documentation/foundation/url/3126806-init

Hi , this is happening for us as well , I have to use a hacky quick fix to revert the percentage encoding back , any better solution ?

encoded = [[[NSURL URLWithString:encoded] absoluteString] stringByReplacingOccurrencesOfString:@"%25" withString:@"%"]; //if ios 17

My users who use special characters can no longer log in to my app. If I fix this for iOS 17, it will break for users still on iOS 16. Great.

I had the same problem and it was due to this change that query parameters are now automatically encoded, although my parameter was already url-encoded. However, upon further inspection using the init(string:encodingInvalidCharacters:) initializer and passing encodingInvalidCharacters: false as suggested in the documentation I found out my parameter did not encode all invalid characters (in my case, block quotes). It returned nil, meaning the param did still contained invalid characters.

As soon as I made sure my param string was completely url-encoded, the default initializer on iOS 17 no longer double-encoded. I guess as soon it finds some unencoded character it blindly encodes the whole url.

I have the same issue with React Native - tried axios and fetch.

I posted that issue here https://github.com/axios/axios/issues/6102

looking for any way.

I'm using expo and react native and solved it using this method. If the ios version is 17 or higher, I force the axios request to ignore the urlencoding, and let the device do it.

import { AxiosRequestConfig } from "axios";
import * as Device from 'expo-device';
const qs = require('qs');

const BROKEN_IOS_MAJOR_VERSION = 17;

const osVersion = getDeviceVersion();
const isIos17OrNewer = Device.brand as DeviceBrand === DeviceBrand.Apple
  && osVersion as number >= BROKEN_IOS_MAJOR_VERSION;

export const getAxiosRequest = (
  request: AxiosRequestConfig<any>
): AxiosRequestConfig<any> => {
  return isIos17OrNewer
    ? {
     ...request,
     paramsSerializer: (params: any) => qs.stringify(params, { encode: false }),
    }
    : request
};

Where request is the second argument in the axios.get method e.g. something like:

{
      headers: { Authorization: 'bearer sometoken' },
      params: {
        someKey: someValue
      },
}

Hope this helps.

Same issue here. Specifically, on iOS 17 devices, some characters in the query string parameters of axios requests were encoded twice

As noted above, starting with iOS 17, NSUrLs are encoded according to the RFC 3986 format (previously RFC 1738/1808). Axios does not support RFC 3986 yet

I also set a paramsSerializer to resolve this. On iOS 17 and higher, the query string params for axios requests are encoded using the RFC 3986 format, which can be specified using the qs library

import axios from 'axios';
import qs from 'qs';
import { Platform } from 'react-native';

const isIos17OrGreater = 
    Platform.OS === 'ios' && parseInt(Platform.Version, 10) >= 17;

const instance = axios.create(
    isIos17OrGreater
        ? {
               paramsSerializer: (params: any) => 
                   qs.stringify(params, { format: 'RFC3986' }), 
           }
        : {},
);

iOS 17 does not encode query parameters automatically as claimed in the answers of this post. iOS17 only encodes "Invalid characters" which are not supported by HTTPS standard protocol. So if you have space or any invalid character like [ ] then only it will encode.

URL Encoding doesn't work with iOS 17 which is working with iOS 16
 
 
Q