@@ -37,62 +37,85 @@ public static String decodeBytes(byte[] buf, int length, String contentType) {
3737 }
3838 }
3939
40+ /**
41+ * Walks the media-type parameter list of a Content-Type value looking for a {@code charset}
42+ * parameter, honoring RFC 7230 quoted-string syntax for parameter values. Quoted values are
43+ * fully consumed as opaque tokens, so a {@code charset}-looking substring inside another
44+ * parameter's quoted value (e.g. {@code boundary="charset=oops"}) can never be mistaken for a
45+ * real {@code charset} parameter. If a {@code charset} parameter is found but its value is not a
46+ * valid charset name, the search continues to any later {@code charset} parameter instead of
47+ * giving up.
48+ */
4049 public static Charset extractCharset (String contentType ) {
4150 if (contentType == null ) return null ;
4251 int len = contentType .length ();
43- int searchFrom = 0 ;
44- while (true ) {
45- int idx = indexOfIgnoreAsciiCase (contentType , "charset" , searchFrom );
46- if (idx < 0 ) return null ;
47- // Require a parameter boundary before "charset" so "xcharset=..." is not matched.
48- // RFC 7230 optional whitespace (OWS) allows both space and horizontal tab.
49- boolean boundaryOk =
50- idx == 0
51- || contentType .charAt (idx - 1 ) == ';'
52- || contentType .charAt (idx - 1 ) == ' '
53- || contentType .charAt (idx - 1 ) == '\t' ;
54- // Also allow OWS around the "=", e.g. "charset = ISO-8859-1".
55- int j = idx + 7 ;
56- while (j < len && (contentType .charAt (j ) == ' ' || contentType .charAt (j ) == '\t' )) j ++;
57- if (boundaryOk && j < len && contentType .charAt (j ) == '=' ) {
58- j ++;
59- while (j < len && (contentType .charAt (j ) == ' ' || contentType .charAt (j ) == '\t' )) j ++;
60- int nameStart = j ;
61- int end = len ;
62- for (int i = nameStart ; i < end ; i ++) {
52+ // Skip the media type itself (e.g. "text/plain") up to the first parameter delimiter.
53+ int i = skipToDelimiter (contentType , 0 , len );
54+ while (i < len ) {
55+ i ++; // consume the ';' or ',' that ends the previous token
56+ i = skipOws (contentType , i , len );
57+ int nameStart = i ;
58+ while (i < len
59+ && contentType .charAt (i ) != '='
60+ && contentType .charAt (i ) != ';'
61+ && contentType .charAt (i ) != ',' ) {
62+ i ++;
63+ }
64+ if (i >= len || contentType .charAt (i ) != '=' ) {
65+ // Parameter with no '=' (malformed) — skip past it and keep looking.
66+ i = skipToDelimiter (contentType , i , len );
67+ continue ;
68+ }
69+ int nameEnd = i ;
70+ while (nameEnd > nameStart && isOws (contentType .charAt (nameEnd - 1 ))) nameEnd --;
71+ String name = contentType .substring (nameStart , nameEnd );
72+ i = skipOws (contentType , i + 1 , len );
73+ String value ;
74+ if (i < len && contentType .charAt (i ) == '"' ) {
75+ StringBuilder sb = new StringBuilder ();
76+ i ++;
77+ while (i < len && contentType .charAt (i ) != '"' ) {
6378 char c = contentType .charAt (i );
64- if (c == ';' || c == ',' || c == ' ' ) {
65- end = i ;
66- break ;
79+ if (c == '\\' && i + 1 < len ) {
80+ i ++ ;
81+ c = contentType . charAt ( i ) ;
6782 }
83+ sb .append (c );
84+ i ++;
6885 }
69- String name = contentType .substring (nameStart , end ).trim ();
70- if (name .length () > 1 && name .charAt (0 ) == '"' && name .charAt (name .length () - 1 ) == '"' ) {
71- name = name .substring (1 , name .length () - 1 );
72- }
86+ if (i < len ) i ++; // consume closing quote
87+ value = sb .toString ();
88+ i = skipToDelimiter (contentType , i , len );
89+ } else {
90+ int valStart = i ;
91+ i = skipToDelimiter (contentType , i , len );
92+ int valEnd = i ;
93+ while (valEnd > valStart && isOws (contentType .charAt (valEnd - 1 ))) valEnd --;
94+ value = contentType .substring (valStart , valEnd );
95+ }
96+ if (name .equalsIgnoreCase ("charset" )) {
7397 try {
74- return Charset .forName (name );
75- } catch (IllegalArgumentException e ) {
76- return null ;
98+ return Charset .forName (value );
99+ } catch (IllegalArgumentException ignored ) {
100+ // An invalid charset value here does not rule out a later, valid charset parameter.
77101 }
78102 }
79- searchFrom = idx + 1 ;
80103 }
104+ return null ;
81105 }
82106
83- private static int indexOfIgnoreAsciiCase (String s , String needle , int fromIndex ) {
84- int sLen = s .length ();
85- int nLen = needle .length ();
86- outer :
87- for (int i = fromIndex , max = sLen - nLen ; i <= max ; i ++) {
88- for (int j = 0 ; j < nLen ; j ++) {
89- if (Character .toLowerCase (s .charAt (i + j )) != needle .charAt (j )) {
90- continue outer ;
91- }
92- }
93- return i ;
94- }
95- return -1 ;
107+ private static int skipToDelimiter (String s , int i , int len ) {
108+ while (i < len && s .charAt (i ) != ';' && s .charAt (i ) != ',' ) i ++;
109+ return i ;
110+ }
111+
112+ private static boolean isOws (char c ) {
113+ return c == ' ' || c == '\t' ;
114+ }
115+
116+ private static int skipOws (String s , int i , int len ) {
117+ while (i < len && isOws (s .charAt (i ))) i ++;
118+ return i ;
96119 }
97120
98121 private MultipartContentDecoder () {}
0 commit comments