@@ -83,30 +83,12 @@ + (NSURL *)NSURL:(id)json
8383 return nil ;
8484 }
8585
86- @try { // NSURL has a history of crashing with bad input, so let's be safe
87-
88- NSURL *URL = [NSURL URLWithString: path];
89- if (URL.scheme ) { // Was a well-formed absolute URL
90- return URL;
86+ @try { // NSURL has a history of crashing with bad input, so let's be
87+ NSURLComponents *urlComponents = [NSURLComponents componentsWithString: path];
88+ if (urlComponents.scheme ) {
89+ return [self _preprocessURLComponents: urlComponents from: path].URL ;
9190 }
9291
93- // Check if it has a scheme
94- if ([path rangeOfString: @" ://" ].location != NSNotFound ) {
95- NSMutableCharacterSet *urlAllowedCharacterSet = [NSMutableCharacterSet new ];
96- [urlAllowedCharacterSet formUnionWithCharacterSet: [NSCharacterSet URLUserAllowedCharacterSet ]];
97- [urlAllowedCharacterSet formUnionWithCharacterSet: [NSCharacterSet URLPasswordAllowedCharacterSet ]];
98- [urlAllowedCharacterSet formUnionWithCharacterSet: [NSCharacterSet URLHostAllowedCharacterSet ]];
99- [urlAllowedCharacterSet formUnionWithCharacterSet: [NSCharacterSet URLPathAllowedCharacterSet ]];
100- [urlAllowedCharacterSet formUnionWithCharacterSet: [NSCharacterSet URLQueryAllowedCharacterSet ]];
101- [urlAllowedCharacterSet formUnionWithCharacterSet: [NSCharacterSet URLFragmentAllowedCharacterSet ]];
102- path = [path stringByAddingPercentEncodingWithAllowedCharacters: urlAllowedCharacterSet];
103- URL = [NSURL URLWithString: path];
104- if (URL) {
105- return URL;
106- }
107- }
108-
109- // Assume that it's a local path
11092 path = path.stringByRemovingPercentEncoding ;
11193 if ([path hasPrefix: @" ~" ]) {
11294 // Path is inside user directory
@@ -115,7 +97,8 @@ + (NSURL *)NSURL:(id)json
11597 // Assume it's a resource path
11698 path = [[NSBundle mainBundle ].resourcePath stringByAppendingPathComponent: path];
11799 }
118- if (!(URL = [NSURL fileURLWithPath: path])) {
100+ NSURL *URL = [NSURL fileURLWithPath: path];
101+ if (!URL) {
119102 RCTLogConvertError (json, @" a valid URL" );
120103 }
121104 return URL;
@@ -125,6 +108,32 @@ + (NSURL *)NSURL:(id)json
125108 }
126109}
127110
111+ // This function preprocess the URLComponents received to make sure that we decode it properly
112+ // handling all the use cases.
113+ // See the `RCTConvert_NSURLTests` file for a list of use cases that we want to support:
114+ // To achieve that, we are currently splitting the url, extracting the fragment, so we can
115+ // decode and encode everything but the fragment (which has to be left unmodified)
116+ + (NSURLComponents *)_preprocessURLComponents : (NSURLComponents *)urlComponents from : (NSString *)path
117+ {
118+ // https://developer.apple.com/documentation/foundation/nsurlcomponents
119+ // "[NSURLComponents's] behavior differs subtly from the NSURL class, which conforms to older RFCs"
120+ // Specifically, NSURL rejects some URLs that NSURLComponents will handle
121+ // gracefully.
122+ NSRange fragmentRange = urlComponents.rangeOfFragment ;
123+ if (fragmentRange.length == 0 ) {
124+ // No fragment, pre-remove all escaped characters so we can encode them once.
125+ return [NSURLComponents componentsWithString: path.stringByRemovingPercentEncoding];
126+ }
127+ // Pre-remove all escaped characters (excluding the fragment) to handle partially encoded strings
128+ NSString *baseUrlString = [path substringToIndex: fragmentRange.location].stringByRemovingPercentEncoding ;
129+ // Fragment must be kept as they are passed. We don't have to escape them
130+ NSString *unmodifiedFragment = [path substringFromIndex: fragmentRange.location];
131+
132+ // Recreate the url by using a decoded base and an unmodified fragment.
133+ NSString *preprocessedURL = [NSString stringWithFormat: @" %@%@ " , baseUrlString, unmodifiedFragment];
134+ return [NSURLComponents componentsWithString: preprocessedURL];
135+ }
136+
128137RCT_ENUM_CONVERTER (
129138 NSURLRequestCachePolicy ,
130139 (@{
0 commit comments