1 /*
2  * JBoss, Home of Professional Open Source.
3  * Copyright 2014 Red Hat, Inc., and individual contributors
4  * as indicated by the @author tags.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  */

18
19 package io.undertow.util;
20
21 import java.io.UnsupportedEncodingException;
22 import java.lang.reflect.Field;
23 import java.lang.reflect.Modifier;
24 import java.net.URLDecoder;
25 import java.security.AccessController;
26 import java.security.PrivilegedAction;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.Map;
30
31 /**
32  * NOTE: if you add a new header here you must also add it to {@link io.undertow.server.protocol.http.HttpRequestParser}
33  *
34  * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
35  */

36 public final class Headers {
37
38     private Headers() {
39     }
40
41     // Headers as strings
42
43     public static final String ACCEPT_STRING = "Accept";
44     public static final String ACCEPT_CHARSET_STRING = "Accept-Charset";
45     public static final String ACCEPT_ENCODING_STRING = "Accept-Encoding";
46     public static final String ACCEPT_LANGUAGE_STRING = "Accept-Language";
47     public static final String ACCEPT_RANGES_STRING = "Accept-Ranges";
48     public static final String AGE_STRING = "Age";
49     public static final String ALLOW_STRING = "Allow";
50     public static final String AUTHENTICATION_INFO_STRING = "Authentication-Info";
51     public static final String AUTHORIZATION_STRING = "Authorization";
52     public static final String CACHE_CONTROL_STRING = "Cache-Control";
53     public static final String COOKIE_STRING = "Cookie";
54     public static final String COOKIE2_STRING = "Cookie2";
55     public static final String CONNECTION_STRING = "Connection";
56     public static final String CONTENT_DISPOSITION_STRING = "Content-Disposition";
57     public static final String CONTENT_ENCODING_STRING = "Content-Encoding";
58     public static final String CONTENT_LANGUAGE_STRING = "Content-Language";
59     public static final String CONTENT_LENGTH_STRING = "Content-Length";
60     public static final String CONTENT_LOCATION_STRING = "Content-Location";
61     public static final String CONTENT_MD5_STRING = "Content-MD5";
62     public static final String CONTENT_RANGE_STRING = "Content-Range";
63     public static final String CONTENT_SECURITY_POLICY_STRING = "Content-Security-Policy";
64     public static final String CONTENT_TYPE_STRING = "Content-Type";
65     public static final String DATE_STRING = "Date";
66     public static final String ETAG_STRING = "ETag";
67     public static final String EXPECT_STRING = "Expect";
68     public static final String EXPIRES_STRING = "Expires";
69     public static final String FORWARDED_STRING = "Forwarded";
70     public static final String FROM_STRING = "From";
71     public static final String HOST_STRING = "Host";
72     public static final String IF_MATCH_STRING = "If-Match";
73     public static final String IF_MODIFIED_SINCE_STRING = "If-Modified-Since";
74     public static final String IF_NONE_MATCH_STRING = "If-None-Match";
75     public static final String IF_RANGE_STRING = "If-Range";
76     public static final String IF_UNMODIFIED_SINCE_STRING = "If-Unmodified-Since";
77     public static final String LAST_MODIFIED_STRING = "Last-Modified";
78     public static final String LOCATION_STRING = "Location";
79     public static final String MAX_FORWARDS_STRING = "Max-Forwards";
80     public static final String ORIGIN_STRING = "Origin";
81     public static final String PRAGMA_STRING = "Pragma";
82     public static final String PROXY_AUTHENTICATE_STRING = "Proxy-Authenticate";
83     public static final String PROXY_AUTHORIZATION_STRING = "Proxy-Authorization";
84     public static final String RANGE_STRING = "Range";
85     public static final String REFERER_STRING = "Referer";
86     public static final String REFERRER_POLICY_STRING = "Referrer-Policy";
87     public static final String REFRESH_STRING = "Refresh";
88     public static final String RETRY_AFTER_STRING = "Retry-After";
89     public static final String SEC_WEB_SOCKET_ACCEPT_STRING = "Sec-WebSocket-Accept";
90     public static final String SEC_WEB_SOCKET_EXTENSIONS_STRING = "Sec-WebSocket-Extensions";
91     public static final String SEC_WEB_SOCKET_KEY_STRING = "Sec-WebSocket-Key";
92     public static final String SEC_WEB_SOCKET_KEY1_STRING = "Sec-WebSocket-Key1";
93     public static final String SEC_WEB_SOCKET_KEY2_STRING = "Sec-WebSocket-Key2";
94     public static final String SEC_WEB_SOCKET_LOCATION_STRING = "Sec-WebSocket-Location";
95     public static final String SEC_WEB_SOCKET_ORIGIN_STRING = "Sec-WebSocket-Origin";
96     public static final String SEC_WEB_SOCKET_PROTOCOL_STRING = "Sec-WebSocket-Protocol";
97     public static final String SEC_WEB_SOCKET_VERSION_STRING = "Sec-WebSocket-Version";
98     public static final String SERVER_STRING = "Server";
99     public static final String SERVLET_ENGINE_STRING = "Servlet-Engine";
100     public static final String SET_COOKIE_STRING = "Set-Cookie";
101     public static final String SET_COOKIE2_STRING = "Set-Cookie2";
102     public static final String SSL_CLIENT_CERT_STRING = "SSL_CLIENT_CERT";
103     public static final String SSL_CIPHER_STRING = "SSL_CIPHER";
104     public static final String SSL_SESSION_ID_STRING = "SSL_SESSION_ID";
105     public static final String SSL_CIPHER_USEKEYSIZE_STRING = "SSL_CIPHER_USEKEYSIZE";
106     public static final String STATUS_STRING = "Status";
107     public static final String STRICT_TRANSPORT_SECURITY_STRING = "Strict-Transport-Security";
108     public static final String TE_STRING = "TE";
109     public static final String TRAILER_STRING = "Trailer";
110     public static final String TRANSFER_ENCODING_STRING = "Transfer-Encoding";
111     public static final String UPGRADE_STRING = "Upgrade";
112     public static final String USER_AGENT_STRING = "User-Agent";
113     public static final String VARY_STRING = "Vary";
114     public static final String VIA_STRING = "Via";
115     public static final String WARNING_STRING = "Warning";
116     public static final String WWW_AUTHENTICATE_STRING = "WWW-Authenticate";
117     public static final String X_CONTENT_TYPE_OPTIONS_STRING = "X-Content-Type-Options";
118     public static final String X_DISABLE_PUSH_STRING = "X-Disable-Push";
119     public static final String X_FORWARDED_FOR_STRING = "X-Forwarded-For";
120     public static final String X_FORWARDED_PROTO_STRING = "X-Forwarded-Proto";
121     public static final String X_FORWARDED_HOST_STRING = "X-Forwarded-Host";
122     public static final String X_FORWARDED_PORT_STRING = "X-Forwarded-Port";
123     public static final String X_FORWARDED_SERVER_STRING = "X-Forwarded-Server";
124     public static final String X_FRAME_OPTIONS_STRING = "X-Frame-Options";
125     public static final String X_XSS_PROTECTION_STRING = "X-Xss-Protection";
126
127     // Header names
128
129     public static final HttpString ACCEPT = new HttpString(ACCEPT_STRING, 1);
130     public static final HttpString ACCEPT_CHARSET = new HttpString(ACCEPT_CHARSET_STRING, 2);
131     public static final HttpString ACCEPT_ENCODING = new HttpString(ACCEPT_ENCODING_STRING, 3);
132     public static final HttpString ACCEPT_LANGUAGE = new HttpString(ACCEPT_LANGUAGE_STRING, 4);
133     public static final HttpString ACCEPT_RANGES = new HttpString(ACCEPT_RANGES_STRING, 5);
134     public static final HttpString AGE = new HttpString(AGE_STRING, 6);
135     public static final HttpString ALLOW = new HttpString(ALLOW_STRING, 7);
136     public static final HttpString AUTHENTICATION_INFO = new HttpString(AUTHENTICATION_INFO_STRING, 8);
137     public static final HttpString AUTHORIZATION = new HttpString(AUTHORIZATION_STRING, 9);
138     public static final HttpString CACHE_CONTROL = new HttpString(CACHE_CONTROL_STRING, 10);
139     public static final HttpString CONNECTION = new HttpString(CONNECTION_STRING, 11);
140     public static final HttpString CONTENT_DISPOSITION = new HttpString(CONTENT_DISPOSITION_STRING, 12);
141     public static final HttpString CONTENT_ENCODING = new HttpString(CONTENT_ENCODING_STRING, 13);
142     public static final HttpString CONTENT_LANGUAGE = new HttpString(CONTENT_LANGUAGE_STRING, 14);
143     public static final HttpString CONTENT_LENGTH = new HttpString(CONTENT_LENGTH_STRING, 15);
144     public static final HttpString CONTENT_LOCATION = new HttpString(CONTENT_LOCATION_STRING, 16);
145     public static final HttpString CONTENT_MD5 = new HttpString(CONTENT_MD5_STRING, 17);
146     public static final HttpString CONTENT_RANGE = new HttpString(CONTENT_RANGE_STRING, 18);
147     public static final HttpString CONTENT_SECURITY_POLICY = new HttpString(CONTENT_SECURITY_POLICY_STRING, 19);
148     public static final HttpString CONTENT_TYPE = new HttpString(CONTENT_TYPE_STRING, 20);
149     public static final HttpString COOKIE = new HttpString(COOKIE_STRING, 21);
150     public static final HttpString COOKIE2 = new HttpString(COOKIE2_STRING, 22);
151     public static final HttpString DATE = new HttpString(DATE_STRING, 23);
152     public static final HttpString ETAG = new HttpString(ETAG_STRING, 24);
153     public static final HttpString EXPECT = new HttpString(EXPECT_STRING, 25);
154     public static final HttpString EXPIRES = new HttpString(EXPIRES_STRING, 26);
155     public static final HttpString FORWARDED = new HttpString(FORWARDED_STRING, 27);
156     public static final HttpString FROM = new HttpString(FROM_STRING, 28);
157     public static final HttpString HOST = new HttpString(HOST_STRING, 29);
158     public static final HttpString IF_MATCH = new HttpString(IF_MATCH_STRING, 30);
159     public static final HttpString IF_MODIFIED_SINCE = new HttpString(IF_MODIFIED_SINCE_STRING, 31);
160     public static final HttpString IF_NONE_MATCH = new HttpString(IF_NONE_MATCH_STRING, 32);
161     public static final HttpString IF_RANGE = new HttpString(IF_RANGE_STRING, 33);
162     public static final HttpString IF_UNMODIFIED_SINCE = new HttpString(IF_UNMODIFIED_SINCE_STRING, 34);
163     public static final HttpString LAST_MODIFIED = new HttpString(LAST_MODIFIED_STRING, 35);
164     public static final HttpString LOCATION = new HttpString(LOCATION_STRING, 36);
165     public static final HttpString MAX_FORWARDS = new HttpString(MAX_FORWARDS_STRING, 37);
166     public static final HttpString ORIGIN = new HttpString(ORIGIN_STRING, 38);
167     public static final HttpString PRAGMA = new HttpString(PRAGMA_STRING, 39);
168     public static final HttpString PROXY_AUTHENTICATE = new HttpString(PROXY_AUTHENTICATE_STRING, 40);
169     public static final HttpString PROXY_AUTHORIZATION = new HttpString(PROXY_AUTHORIZATION_STRING, 41);
170     public static final HttpString RANGE = new HttpString(RANGE_STRING, 42);
171     public static final HttpString REFERER = new HttpString(REFERER_STRING, 43);
172     public static final HttpString REFERRER_POLICY = new HttpString(REFERRER_POLICY_STRING, 44);
173     public static final HttpString REFRESH = new HttpString(REFRESH_STRING, 45);
174     public static final HttpString RETRY_AFTER = new HttpString(RETRY_AFTER_STRING, 46);
175     public static final HttpString SEC_WEB_SOCKET_ACCEPT = new HttpString(SEC_WEB_SOCKET_ACCEPT_STRING, 47);
176     public static final HttpString SEC_WEB_SOCKET_EXTENSIONS = new HttpString(SEC_WEB_SOCKET_EXTENSIONS_STRING, 48);
177     public static final HttpString SEC_WEB_SOCKET_KEY = new HttpString(SEC_WEB_SOCKET_KEY_STRING, 49);
178     public static final HttpString SEC_WEB_SOCKET_KEY1 = new HttpString(SEC_WEB_SOCKET_KEY1_STRING, 50);
179     public static final HttpString SEC_WEB_SOCKET_KEY2 = new HttpString(SEC_WEB_SOCKET_KEY2_STRING, 51);
180     public static final HttpString SEC_WEB_SOCKET_LOCATION = new HttpString(SEC_WEB_SOCKET_LOCATION_STRING, 52);
181     public static final HttpString SEC_WEB_SOCKET_ORIGIN = new HttpString(SEC_WEB_SOCKET_ORIGIN_STRING, 53);
182     public static final HttpString SEC_WEB_SOCKET_PROTOCOL = new HttpString(SEC_WEB_SOCKET_PROTOCOL_STRING, 54);
183     public static final HttpString SEC_WEB_SOCKET_VERSION = new HttpString(SEC_WEB_SOCKET_VERSION_STRING, 55);
184     public static final HttpString SERVER = new HttpString(SERVER_STRING, 56);
185     public static final HttpString SERVLET_ENGINE = new HttpString(SERVLET_ENGINE_STRING, 57);
186     public static final HttpString SET_COOKIE = new HttpString(SET_COOKIE_STRING, 58);
187     public static final HttpString SET_COOKIE2 = new HttpString(SET_COOKIE2_STRING, 59);
188     public static final HttpString SSL_CIPHER = new HttpString(SSL_CIPHER_STRING, 60);
189     public static final HttpString SSL_CIPHER_USEKEYSIZE = new HttpString(SSL_CIPHER_USEKEYSIZE_STRING, 61);
190     public static final HttpString SSL_CLIENT_CERT = new HttpString(SSL_CLIENT_CERT_STRING, 62);
191     public static final HttpString SSL_SESSION_ID = new HttpString(SSL_SESSION_ID_STRING, 63);
192     public static final HttpString STATUS = new HttpString(STATUS_STRING, 64);
193     public static final HttpString STRICT_TRANSPORT_SECURITY = new HttpString(STRICT_TRANSPORT_SECURITY_STRING, 65);
194     public static final HttpString TE = new HttpString(TE_STRING, 66);
195     public static final HttpString TRAILER = new HttpString(TRAILER_STRING, 67);
196     public static final HttpString TRANSFER_ENCODING = new HttpString(TRANSFER_ENCODING_STRING, 68);
197     public static final HttpString UPGRADE = new HttpString(UPGRADE_STRING, 69);
198     public static final HttpString USER_AGENT = new HttpString(USER_AGENT_STRING, 70);
199     public static final HttpString VARY = new HttpString(VARY_STRING, 71);
200     public static final HttpString VIA = new HttpString(VIA_STRING, 72);
201     public static final HttpString WARNING = new HttpString(WARNING_STRING, 73);
202     public static final HttpString WWW_AUTHENTICATE = new HttpString(WWW_AUTHENTICATE_STRING, 74);
203     public static final HttpString X_CONTENT_TYPE_OPTIONS = new HttpString(X_CONTENT_TYPE_OPTIONS_STRING, 75);
204     public static final HttpString X_DISABLE_PUSH = new HttpString(X_DISABLE_PUSH_STRING, 76);
205     public static final HttpString X_FORWARDED_FOR = new HttpString(X_FORWARDED_FOR_STRING, 77);
206     public static final HttpString X_FORWARDED_HOST = new HttpString(X_FORWARDED_HOST_STRING, 78);
207     public static final HttpString X_FORWARDED_PORT = new HttpString(X_FORWARDED_PORT_STRING, 79);
208     public static final HttpString X_FORWARDED_PROTO = new HttpString(X_FORWARDED_PROTO_STRING, 80);
209     public static final HttpString X_FORWARDED_SERVER = new HttpString(X_FORWARDED_SERVER_STRING, 81);
210     public static final HttpString X_FRAME_OPTIONS = new HttpString(X_FRAME_OPTIONS_STRING, 82);
211     public static final HttpString X_XSS_PROTECTION = new HttpString(X_XSS_PROTECTION_STRING, 83);
212     // Content codings
213
214     public static final HttpString COMPRESS = new HttpString("compress");
215     public static final HttpString X_COMPRESS = new HttpString("x-compress");
216     public static final HttpString DEFLATE = new HttpString("deflate");
217     public static final HttpString IDENTITY = new HttpString("identity");
218     public static final HttpString GZIP = new HttpString("gzip");
219     public static final HttpString X_GZIP = new HttpString("x-gzip");
220
221     // Transfer codings
222
223     public static final HttpString CHUNKED = new HttpString("chunked");
224     // IDENTITY
225     // GZIP
226     // COMPRESS
227     // DEFLATE
228
229     // Connection values
230     public static final HttpString KEEP_ALIVE = new HttpString("keep-alive");
231     public static final HttpString CLOSE = new HttpString("close");
232
233     //MIME header used in multipart file uploads
234     public static final String CONTENT_TRANSFER_ENCODING_STRING = "Content-Transfer-Encoding";
235     public static final HttpString CONTENT_TRANSFER_ENCODING = new HttpString(CONTENT_TRANSFER_ENCODING_STRING);
236
237     // Authentication Schemes
238     public static final HttpString BASIC = new HttpString("Basic");
239     public static final HttpString DIGEST = new HttpString("Digest");
240     public static final HttpString NEGOTIATE = new HttpString("Negotiate");
241
242     // Digest authentication Token Names
243     public static final HttpString ALGORITHM = new HttpString("algorithm");
244     public static final HttpString AUTH_PARAM = new HttpString("auth-param");
245     public static final HttpString CNONCE = new HttpString("cnonce");
246     public static final HttpString DOMAIN = new HttpString("domain");
247     public static final HttpString NEXT_NONCE = new HttpString("nextnonce");
248     public static final HttpString NONCE = new HttpString("nonce");
249     public static final HttpString NONCE_COUNT = new HttpString("nc");
250     public static final HttpString OPAQUE = new HttpString("opaque");
251     public static final HttpString QOP = new HttpString("qop");
252     public static final HttpString REALM = new HttpString("realm");
253     public static final HttpString RESPONSE = new HttpString("response");
254     public static final HttpString RESPONSE_AUTH = new HttpString("rspauth");
255     public static final HttpString STALE = new HttpString("stale");
256     public static final HttpString URI = new HttpString("uri");
257     public static final HttpString USERNAME = new HttpString("username");
258
259
260     private static final Map<String, HttpString> HTTP_STRING_MAP;
261
262     static {
263         Map<String, HttpString> map = AccessController.doPrivileged(new PrivilegedAction<Map<String, HttpString>>() {
264             @Override
265             public Map<String, HttpString> run() {
266                 Map<String, HttpString> map = new HashMap<>();
267                 Field[] fields = Headers.class.getDeclaredFields();
268                 for(Field field : fields) {
269                     if(Modifier.isStatic(field.getModifiers()) && field.getType() == HttpString.class) {
270                         field.setAccessible(true);
271                         try {
272                             HttpString result = (HttpString) field.get(null);
273                             map.put(result.toString(), result);
274                         } catch (IllegalAccessException e) {
275                             throw new RuntimeException(e);
276                         }
277                     }
278                 }
279                 return map;
280             }
281         });
282         HTTP_STRING_MAP = Collections.unmodifiableMap(map);
283     }
284
285     public static HttpString fromCache(String string) {
286         return HTTP_STRING_MAP.get(string);
287     }
288
289     /**
290      * Extracts a token from a header that has a given key. For instance if the header is
291      * <p>
292      * content-type=multipart/form-data boundary=myboundary
293      * and the key is boundary the myboundary will be returned.
294      *
295      * @param header The header
296      * @param key    The key that identifies the token to extract
297      * @return The token, or null if it was not found
298      */

299     @Deprecated
300     public static String extractTokenFromHeader(final String header, final String key) {
301         int pos = header.indexOf(' ' + key + '=');
302         if (pos == -1) {
303             if(!header.startsWith(key + '=')) {
304                 return null;
305             }
306             pos = 0;
307         } else {
308             pos++;
309         }
310         int end;
311         int start = pos + key.length() + 1;
312         for (end = start; end < header.length(); ++end) {
313             char c = header.charAt(end);
314             if (c == ' ' || c == '\t' || c == ';') {
315                 break;
316             }
317         }
318         return header.substring(start, end);
319     }
320
321     /**
322      * Extracts a quoted value from a header that has a given key. For instance if the header is
323      * <p>
324      * content-disposition=form-data; name="my field"
325      * and the key is name then "my field" will be returned without the quotes.
326      *
327      *
328      * @param header The header
329      * @param key    The key that identifies the token to extract
330      * @return The token, or null if it was not found
331      */

332     public static String extractQuotedValueFromHeader(final String header, final String key) {
333
334         int keypos = 0;
335         int pos = -1;
336         boolean whiteSpace = true;
337         boolean inQuotes = false;
338         for (int i = 0; i < header.length() - 1; ++i) { //-1 because we need room for the = at the end
339             //TODO: a more efficient matching algorithm
340             char c = header.charAt(i);
341             if (inQuotes) {
342                 if (c == '"') {
343                     inQuotes = false;
344                 }
345             } else {
346                 if (key.charAt(keypos) == c && (whiteSpace || keypos > 0)) {
347                     keypos++;
348                     whiteSpace = false;
349                 } else if (c == '"') {
350                     keypos = 0;
351                     inQuotes = true;
352                     whiteSpace = false;
353                 } else {
354                     keypos = 0;
355                     whiteSpace = c == ' ' || c == ';' || c == '\t';
356                 }
357                 if (keypos == key.length()) {
358                     if (header.charAt(i + 1) == '=') {
359                         pos = i + 2;
360                         break;
361                     } else {
362                         keypos = 0;
363                     }
364                 }
365             }
366
367         }
368         if (pos == -1) {
369             return null;
370         }
371
372         int end;
373         int start = pos;
374         if (header.charAt(start) == '"') {
375             start++;
376             for (end = start; end < header.length(); ++end) {
377                 char c = header.charAt(end);
378                 if (c == '"') {
379                     break;
380                 }
381             }
382             return header.substring(start, end);
383
384         } else {
385             //no quotes
386             for (end = start; end < header.length(); ++end) {
387                 char c = header.charAt(end);
388                 if (c == ' ' || c == '\t' || c == ';') {
389                     break;
390                 }
391             }
392             return header.substring(start, end);
393         }
394     }
395
396     /**
397      * Extracts a quoted value from a header that has a given key. For instance if the header is
398      * <p>
399      * content-disposition=form-data; filename*="utf-8''test.txt"
400      * and the key is filename* then "test.txt" will be returned after extracting character set and language
401      * (following RFC 2231) and performing URL decoding to the value using the specified encoding
402      *
403      * @param header The header
404      * @param key    The key that identifies the token to extract
405      * @return The token, or null if it was not found
406      */

407     public static String extractQuotedValueFromHeaderWithEncoding(final String header, final String key) {
408         String value = extractQuotedValueFromHeader(header, key);
409         if (value != null) {
410             return value;
411         }
412         value = extractQuotedValueFromHeader(header , key + "*");
413         if(value != null) {
414             int characterSetDelimiter = value.indexOf('\'');
415             int languageDelimiter = value.lastIndexOf('\'', characterSetDelimiter + 1);
416             String characterSet = value.substring(0, characterSetDelimiter);
417             try {
418                 String fileNameURLEncoded = value.substring(languageDelimiter + 1);
419                 return URLDecoder.decode(fileNameURLEncoded, characterSet);
420             } catch (UnsupportedEncodingException e) {
421                 throw new RuntimeException(e);
422             }
423         }
424         return null;
425     }
426 }
427