@@ -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