@@ -37,62 +37,84 @@ 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 fully
43+ * consumed as opaque tokens, so a {@code charset}-looking substring inside another parameter's
44+ * quoted value (e.g. {@code boundary="charset=oops"}) can never be mistaken for a real {@code
45+ * charset} parameter. If a {@code charset} parameter is found but its value is not a valid
46+ * charset name, the search continues to any later {@code charset} parameter instead of giving up.
47+ */
4048 public static Charset extractCharset (String contentType ) {
4149 if (contentType == null ) return null ;
4250 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 ++) {
51+ // Skip the media type itself (e.g. "text/plain") up to the first parameter delimiter.
52+ int i = skipToDelimiter (contentType , 0 , len );
53+ while (i < len ) {
54+ i ++; // consume the ';' or ',' that ends the previous token
55+ i = skipOws (contentType , i , len );
56+ int nameStart = i ;
57+ while (i < len
58+ && contentType .charAt (i ) != '='
59+ && contentType .charAt (i ) != ';'
60+ && contentType .charAt (i ) != ',' ) {
61+ i ++;
62+ }
63+ if (i >= len || contentType .charAt (i ) != '=' ) {
64+ // Parameter with no '=' (malformed) — skip past it and keep looking.
65+ i = skipToDelimiter (contentType , i , len );
66+ continue ;
67+ }
68+ int nameEnd = i ;
69+ while (nameEnd > nameStart && isOws (contentType .charAt (nameEnd - 1 ))) nameEnd --;
70+ String name = contentType .substring (nameStart , nameEnd );
71+ i = skipOws (contentType , i + 1 , len );
72+ String value ;
73+ if (i < len && contentType .charAt (i ) == '"' ) {
74+ StringBuilder sb = new StringBuilder ();
75+ i ++;
76+ while (i < len && contentType .charAt (i ) != '"' ) {
6377 char c = contentType .charAt (i );
64- if (c == ';' || c == ',' || c == ' ' ) {
65- end = i ;
66- break ;
78+ if (c == '\\' && i + 1 < len ) {
79+ i ++ ;
80+ c = contentType . charAt ( i ) ;
6781 }
82+ sb .append (c );
83+ i ++;
6884 }
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- }
85+ if (i < len ) i ++; // consume closing quote
86+ value = sb .toString ();
87+ i = skipToDelimiter (contentType , i , len );
88+ } else {
89+ int valStart = i ;
90+ i = skipToDelimiter (contentType , i , len );
91+ int valEnd = i ;
92+ while (valEnd > valStart && isOws (contentType .charAt (valEnd - 1 ))) valEnd --;
93+ value = contentType .substring (valStart , valEnd );
94+ }
95+ if (name .equalsIgnoreCase ("charset" )) {
7396 try {
74- return Charset .forName (name );
75- } catch (IllegalArgumentException e ) {
76- return null ;
97+ return Charset .forName (value );
98+ } catch (IllegalArgumentException ignored ) {
99+ // An invalid charset value here does not rule out a later, valid charset parameter.
77100 }
78101 }
79- searchFrom = idx + 1 ;
80102 }
103+ return null ;
81104 }
82105
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 ;
106+ private static int skipToDelimiter (String s , int i , int len ) {
107+ while (i < len && s .charAt (i ) != ';' && s .charAt (i ) != ',' ) i ++;
108+ return i ;
109+ }
110+
111+ private static boolean isOws (char c ) {
112+ return c == ' ' || c == '\t' ;
113+ }
114+
115+ private static int skipOws (String s , int i , int len ) {
116+ while (i < len && isOws (s .charAt (i ))) i ++;
117+ return i ;
96118 }
97119
98120 private MultipartContentDecoder () {}
0 commit comments