@@ -515,18 +515,18 @@ static id clientForHandle(void *data, NSURLHandle *hdl)
515515}
516516
517517/*
518- * Check a string to see if it contains only legal data characters
519- * or percent escape sequences.
518+ * Check a bounded string (up to end pointer) to see if it contains only
519+ * legal data characters or percent escape sequences.
520520 */
521- static BOOL legal (const char *str, const char *extras)
521+ static BOOL legal_bounded (const char *str, const char *end , const char *extras)
522522{
523523 const char *mark = " -_.!~*'()" ;
524524
525525 if (str != 0 )
526526 {
527- while (* str != 0 )
527+ while (str < end )
528528 {
529- if (*str == ' %' && isxdigit (str[1 ]) && isxdigit (str[2 ]))
529+ if (*str == ' %' && str + 2 < end && isxdigit (str[1 ]) && isxdigit (str[2 ]))
530530 {
531531 str += 3 ;
532532 }
@@ -551,6 +551,17 @@ static BOOL legal(const char *str, const char *extras)
551551 return YES ;
552552}
553553
554+ /*
555+ * Check a string to see if it contains only legal data characters
556+ * or percent escape sequences. Wrapper around legal_bounded.
557+ */
558+ static BOOL legal (const char *str, const char *extras)
559+ {
560+ if (str == 0 )
561+ return YES ;
562+ return legal_bounded (str, str + strlen (str), extras);
563+ }
564+
554565/*
555566 * Convert percent escape sequences to individual characters.
556567 */
@@ -995,41 +1006,78 @@ - (id) initWithString: (NSString*)aUrlString
9951006 /*
9961007 * Set 'end' to point to the start of the path, or just past
9971008 * the 'authority' if there is no path.
1009+ * Check for delimiters in order: '/' (path), '?' (query), '#' (fragment).
1010+ * We find the authority end without modifying the string, using legal_bounded()
1011+ * for validation (RFC 3986).
9981012 */
1013+ char *authEnd; // End of authority section
1014+ char *pathStart = NULL ; // Where path/query/fragment starts
1015+
9991016 end = strchr (start, ' /' );
10001017 if (end == 0 )
10011018 {
10021019 buf->hasNoPath = YES ;
1003- end = &start[strlen (start)];
1020+ char *alt = strchr (start, ' ?' );
1021+ if (alt == 0 )
1022+ {
1023+ alt = strchr (start, ' #' );
1024+ }
1025+ if (alt == 0 )
1026+ {
1027+ authEnd = &start[strlen (start)];
1028+ pathStart = authEnd;
1029+ }
1030+ else
1031+ {
1032+ authEnd = alt;
1033+ pathStart = alt;
1034+ }
10041035 }
10051036 else
10061037 {
1038+ authEnd = end;
1039+ pathStart = end + 1 ; // Skip the '/' we found
10071040 *end++ = ' \0 ' ;
10081041 }
10091042
10101043 /*
1011- * Parser username:password part
1044+ * Parser username:password part within authority bounds
10121045 */
10131046 ptr = strchr (start, ' @' );
1014- if (ptr != 0 )
1047+ if (ptr != 0 && ptr < authEnd )
10151048 {
10161049 buf->user = start;
1050+ char *userEnd = ptr;
10171051 *ptr++ = ' \0 ' ;
10181052 start = ptr;
1019- if (legal (buf->user , " ;:&=+$," ) == NO )
1053+ /* Validate user[:password] without the null terminator at ':' */
1054+ char *colonPos = strchr (buf->user , ' :' );
1055+ if (colonPos != 0 && colonPos < userEnd)
10201056 {
1021- [NSException raise: NSInvalidArgumentException
1022- format: @" [%@ %@ ](%@ , %@ ) "
1023- @" illegal character in user/password part" ,
1024- NSStringFromClass ([self class ]),
1025- NSStringFromSelector (_cmd ),
1026- aUrlString, aBaseUrl];
1057+ if (legal_bounded (buf->user , colonPos, " ;:&=+$," ) == NO
1058+ || legal_bounded (colonPos + 1 , userEnd, " ;:&=+$," ) == NO )
1059+ {
1060+ [NSException raise: NSInvalidArgumentException
1061+ format: @" [%@ %@ ](%@ , %@ ) "
1062+ @" illegal character in user/password part" ,
1063+ NSStringFromClass ([self class ]),
1064+ NSStringFromSelector (_cmd ),
1065+ aUrlString, aBaseUrl];
1066+ }
1067+ *colonPos = ' \0 ' ;
1068+ buf->password = colonPos + 1 ;
10271069 }
1028- ptr = strchr (buf->user , ' :' );
1029- if (ptr != 0 )
1070+ else
10301071 {
1031- *ptr++ = ' \0 ' ;
1032- buf->password = ptr;
1072+ if (legal_bounded (buf->user , userEnd, " ;:&=+$," ) == NO )
1073+ {
1074+ [NSException raise: NSInvalidArgumentException
1075+ format: @" [%@ %@ ](%@ , %@ ) "
1076+ @" illegal character in user/password part" ,
1077+ NSStringFromClass ([self class ]),
1078+ NSStringFromSelector (_cmd ),
1079+ aUrlString, aBaseUrl];
1080+ }
10331081 }
10341082 }
10351083
@@ -1142,11 +1190,22 @@ - (id) initWithString: (NSString*)aUrlString
11421190 }
11431191 }
11441192 }
1145- start = end ;
1193+ start = pathStart ;
11461194 /* Check for a legal host, unless it's an ipv6 address
11471195 * (which would have been checked earlier).
1196+ * Use legal_bounded to validate only the host portion without modifying the string.
11481197 */
1149- if (*buf->host != ' [' && legal (buf->host , " -" ) == NO )
1198+ char *hostEnd = authEnd;
1199+ /* Account for port if present */
1200+ if (*buf->host != ' [' )
1201+ {
1202+ char *colon = strchr (buf->host , ' :' );
1203+ if (colon != 0 && colon < authEnd)
1204+ {
1205+ hostEnd = colon;
1206+ }
1207+ }
1208+ if (*buf->host != ' [' && legal_bounded (buf->host , hostEnd, " -" ) == NO )
11501209 {
11511210 [NSException raise: NSInvalidArgumentException
11521211 format: @" [%@ %@ ](%@ , %@ ) "
0 commit comments