Share code to determine a forbidden method
[WebKit.git] / Source / WebCore / platform / network / HTTPParsers.cpp
1 /*
2  * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org)
3  * Copyright (C) 2006-2017 Apple Inc. All rights reserved.
4  * Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/
5  * Copyright (C) 2009 Google Inc. All rights reserved.
6  * Copyright (C) 2011 Apple Inc. All Rights Reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1.  Redistributions of source code must retain the above copyright
13  *     notice, this list of conditions and the following disclaimer. 
14  * 2.  Redistributions in binary form must reproduce the above copyright
15  *     notice, this list of conditions and the following disclaimer in the
16  *     documentation and/or other materials provided with the distribution. 
17  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
18  *     its contributors may be used to endorse or promote products derived
19  *     from this software without specific prior written permission. 
20  *
21  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
22  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
25  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
28  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include "config.h"
34 #include "HTTPParsers.h"
35
36 #include "HTTPHeaderNames.h"
37 #include <wtf/DateMath.h>
38 #include <wtf/Language.h>
39 #include <wtf/NeverDestroyed.h>
40 #include <wtf/text/CString.h>
41 #include <wtf/text/StringBuilder.h>
42 #include <wtf/text/WTFString.h>
43 #include <wtf/unicode/CharacterNames.h>
44
45 using namespace WTF;
46
47 namespace WebCore {
48
49 // true if there is more to parse, after incrementing pos past whitespace.
50 // Note: Might return pos == str.length()
51 static inline bool skipWhiteSpace(const String& str, unsigned& pos)
52 {
53     unsigned len = str.length();
54
55     while (pos < len && (str[pos] == '\t' || str[pos] == ' '))
56         ++pos;
57
58     return pos < len;
59 }
60
61 // Returns true if the function can match the whole token (case insensitive)
62 // incrementing pos on match, otherwise leaving pos unchanged.
63 // Note: Might return pos == str.length()
64 static inline bool skipToken(const String& str, unsigned& pos, const char* token)
65 {
66     unsigned len = str.length();
67     unsigned current = pos;
68
69     while (current < len && *token) {
70         if (toASCIILower(str[current]) != *token++)
71             return false;
72         ++current;
73     }
74
75     if (*token)
76         return false;
77
78     pos = current;
79     return true;
80 }
81
82 // True if the expected equals sign is seen and there is more to follow.
83 static inline bool skipEquals(const String& str, unsigned &pos)
84 {
85     return skipWhiteSpace(str, pos) && str[pos++] == '=' && skipWhiteSpace(str, pos);
86 }
87
88 // True if a value present, incrementing pos to next space or semicolon, if any.  
89 // Note: might return pos == str.length().
90 static inline bool skipValue(const String& str, unsigned& pos)
91 {
92     unsigned start = pos;
93     unsigned len = str.length();
94     while (pos < len) {
95         if (str[pos] == ' ' || str[pos] == '\t' || str[pos] == ';')
96             break;
97         ++pos;
98     }
99     return pos != start;
100 }
101
102 // See RFC 7230, Section 3.1.2.
103 bool isValidReasonPhrase(const String& value)
104 {
105     for (unsigned i = 0; i < value.length(); ++i) {
106         UChar c = value[i];
107         if (c == 0x7F || c > 0xFF || (c < 0x20 && c != '\t'))
108             return false;
109     }
110     return true;
111 }
112
113 // See RFC 7230, Section 3.2.3.
114 bool isValidHTTPHeaderValue(const String& value)
115 {
116     UChar c = value[0];
117     if (c == ' ' || c == '\t')
118         return false;
119     c = value[value.length() - 1];
120     if (c == ' ' || c == '\t')
121         return false;
122     for (unsigned i = 0; i < value.length(); ++i) {
123         c = value[i];
124         if (c == 0x7F || c > 0xFF || (c < 0x20 && c != '\t'))
125             return false;
126     }
127     return true;
128 }
129
130 // See RFC 7230, Section 3.2.6.
131 static bool isDelimiterCharacter(const UChar c)
132 {
133     // DQUOTE and "(),/:;<=>?@[\]{}"
134     return (c == '"' || c == '(' || c == ')' || c == ',' || c == '/' || c == ':' || c == ';'
135         || c == '<' || c == '=' || c == '>' || c == '?' || c == '@' || c == '[' || c == '\\'
136         || c == ']' || c == '{' || c == '}');
137 }
138
139 // See RFC 7231, Section 5.3.2.
140 bool isValidAcceptHeaderValue(const String& value)
141 {
142     for (unsigned i = 0; i < value.length(); ++i) {
143         UChar c = value[i];
144         // First check for alphanumeric for performance reasons then whitelist four delimiter characters.
145         if (isASCIIAlphanumeric(c) || c == ',' || c == '/' || c == ';' || c == '=')
146             continue;
147         if (isDelimiterCharacter(c))
148             return false;
149     }
150     
151     return true;
152 }
153
154 // See RFC 7231, Section 5.3.5 and 3.1.3.2.
155 bool isValidLanguageHeaderValue(const String& value)
156 {
157     for (unsigned i = 0; i < value.length(); ++i) {
158         UChar c = value[i];
159         if (isASCIIAlphanumeric(c) || c == ' ' || c == '*' || c == ',' || c == '-' || c == '.' || c == ';' || c == '=')
160             continue;
161         return false;
162     }
163     
164     // FIXME: Validate further by splitting into language tags and optional quality
165     // values (q=) and then check each language tag.
166     // Language tags https://tools.ietf.org/html/rfc7231#section-3.1.3.1
167     // Language tag syntax https://tools.ietf.org/html/bcp47#section-2.1
168     return true;
169 }
170
171 // See RFC 7230, Section 3.2.6.
172 bool isValidHTTPToken(const String& value)
173 {
174     if (value.isEmpty())
175         return false;
176     auto valueStringView = StringView(value);
177     for (UChar c : valueStringView.codeUnits()) {
178         if (c <= 0x20 || c >= 0x7F
179             || c == '(' || c == ')' || c == '<' || c == '>' || c == '@'
180             || c == ',' || c == ';' || c == ':' || c == '\\' || c == '"'
181             || c == '/' || c == '[' || c == ']' || c == '?' || c == '='
182             || c == '{' || c == '}')
183         return false;
184     }
185     return true;
186 }
187
188 static const size_t maxInputSampleSize = 128;
189 static String trimInputSample(const char* p, size_t length)
190 {
191     String s = String(p, std::min<size_t>(length, maxInputSampleSize));
192     if (length > maxInputSampleSize)
193         s.append(horizontalEllipsis);
194     return s;
195 }
196
197 bool parseHTTPRefresh(const String& refresh, double& delay, String& url)
198 {
199     unsigned len = refresh.length();
200     unsigned pos = 0;
201     
202     if (!skipWhiteSpace(refresh, pos))
203         return false;
204     
205     while (pos != len && refresh[pos] != ',' && refresh[pos] != ';')
206         ++pos;
207     
208     if (pos == len) { // no URL
209         url = String();
210         bool ok;
211         delay = refresh.stripWhiteSpace().toDouble(&ok);
212         return ok;
213     } else {
214         bool ok;
215         delay = refresh.left(pos).stripWhiteSpace().toDouble(&ok);
216         if (!ok)
217             return false;
218         
219         ++pos;
220         skipWhiteSpace(refresh, pos);
221         unsigned urlStartPos = pos;
222         if (refresh.find("url", urlStartPos, false) == urlStartPos) {
223             urlStartPos += 3;
224             skipWhiteSpace(refresh, urlStartPos);
225             if (refresh[urlStartPos] == '=') {
226                 ++urlStartPos;
227                 skipWhiteSpace(refresh, urlStartPos);
228             } else
229                 urlStartPos = pos;  // e.g. "Refresh: 0; url.html"
230         }
231
232         unsigned urlEndPos = len;
233
234         if (refresh[urlStartPos] == '"' || refresh[urlStartPos] == '\'') {
235             UChar quotationMark = refresh[urlStartPos];
236             urlStartPos++;
237             while (urlEndPos > urlStartPos) {
238                 urlEndPos--;
239                 if (refresh[urlEndPos] == quotationMark)
240                     break;
241             }
242             
243             // https://bugs.webkit.org/show_bug.cgi?id=27868
244             // Sometimes there is no closing quote for the end of the URL even though there was an opening quote.
245             // If we looped over the entire alleged URL string back to the opening quote, just use everything
246             // after the opening quote instead.
247             if (urlEndPos == urlStartPos)
248                 urlEndPos = len;
249         }
250
251         url = refresh.substring(urlStartPos, urlEndPos - urlStartPos).stripWhiteSpace();
252         return true;
253     }
254 }
255
256 std::optional<std::chrono::system_clock::time_point> parseHTTPDate(const String& value)
257 {
258     double dateInMillisecondsSinceEpoch = parseDateFromNullTerminatedCharacters(value.utf8().data());
259     if (!std::isfinite(dateInMillisecondsSinceEpoch))
260         return { };
261     // This assumes system_clock epoch equals Unix epoch which is true for all implementations but unspecified.
262     // FIXME: The parsing function should be switched to std::chrono too.
263     return std::chrono::system_clock::time_point(std::chrono::milliseconds(static_cast<long long>(dateInMillisecondsSinceEpoch)));
264 }
265
266 // FIXME: This function doesn't comply with RFC 6266.
267 // For example, this function doesn't handle the interaction between " and ;
268 // that arises from quoted-string, nor does this function properly unquote
269 // attribute values. Further this function appears to process parameter names
270 // in a case-sensitive manner. (There are likely other bugs as well.)
271 String filenameFromHTTPContentDisposition(const String& value)
272 {
273     Vector<String> keyValuePairs;
274     value.split(';', keyValuePairs);
275
276     unsigned length = keyValuePairs.size();
277     for (unsigned i = 0; i < length; i++) {
278         size_t valueStartPos = keyValuePairs[i].find('=');
279         if (valueStartPos == notFound)
280             continue;
281
282         String key = keyValuePairs[i].left(valueStartPos).stripWhiteSpace();
283
284         if (key.isEmpty() || key != "filename")
285             continue;
286         
287         String value = keyValuePairs[i].substring(valueStartPos + 1).stripWhiteSpace();
288
289         // Remove quotes if there are any
290         if (value[0] == '\"')
291             value = value.substring(1, value.length() - 2);
292
293         return value;
294     }
295
296     return String();
297 }
298
299 String extractMIMETypeFromMediaType(const String& mediaType)
300 {
301     StringBuilder mimeType;
302     unsigned length = mediaType.length();
303     mimeType.reserveCapacity(length);
304     for (unsigned i = 0; i < length; i++) {
305         UChar c = mediaType[i];
306
307         if (c == ';')
308             break;
309
310         // While RFC 2616 does not allow it, other browsers allow multiple values in the HTTP media
311         // type header field, Content-Type. In such cases, the media type string passed here may contain
312         // the multiple values separated by commas. For now, this code ignores text after the first comma,
313         // which prevents it from simply failing to parse such types altogether. Later for better
314         // compatibility we could consider using the first or last valid MIME type instead.
315         // See https://bugs.webkit.org/show_bug.cgi?id=25352 for more discussion.
316         if (c == ',')
317             break;
318
319         // FIXME: The following is not correct. RFC 2616 allows linear white space before and
320         // after the MIME type, but not within the MIME type itself. And linear white space
321         // includes only a few specific ASCII characters; a small subset of isSpaceOrNewline.
322         // See https://bugs.webkit.org/show_bug.cgi?id=8644 for a bug tracking part of this.
323         if (isSpaceOrNewline(c))
324             continue;
325
326         mimeType.append(c);
327     }
328
329     if (mimeType.length() == length)
330         return mediaType;
331     return mimeType.toString();
332 }
333
334 String extractCharsetFromMediaType(const String& mediaType)
335 {
336     unsigned int pos, len;
337     findCharsetInMediaType(mediaType, pos, len);
338     return mediaType.substring(pos, len);
339 }
340
341 void findCharsetInMediaType(const String& mediaType, unsigned int& charsetPos, unsigned int& charsetLen, unsigned int start)
342 {
343     charsetPos = start;
344     charsetLen = 0;
345
346     size_t pos = start;
347     unsigned length = mediaType.length();
348     
349     while (pos < length) {
350         pos = mediaType.find("charset", pos, false);
351         if (pos == notFound || pos == 0) {
352             charsetLen = 0;
353             return;
354         }
355         
356         // is what we found a beginning of a word?
357         if (mediaType[pos-1] > ' ' && mediaType[pos-1] != ';') {
358             pos += 7;
359             continue;
360         }
361         
362         pos += 7;
363
364         // skip whitespace
365         while (pos != length && mediaType[pos] <= ' ')
366             ++pos;
367     
368         if (mediaType[pos++] != '=') // this "charset" substring wasn't a parameter name, but there may be others
369             continue;
370
371         while (pos != length && (mediaType[pos] <= ' ' || mediaType[pos] == '"' || mediaType[pos] == '\''))
372             ++pos;
373
374         // we don't handle spaces within quoted parameter values, because charset names cannot have any
375         unsigned endpos = pos;
376         while (pos != length && mediaType[endpos] > ' ' && mediaType[endpos] != '"' && mediaType[endpos] != '\'' && mediaType[endpos] != ';')
377             ++endpos;
378
379         charsetPos = pos;
380         charsetLen = endpos - pos;
381         return;
382     }
383 }
384
385 XSSProtectionDisposition parseXSSProtectionHeader(const String& header, String& failureReason, unsigned& failurePosition, String& reportURL)
386 {
387     static NeverDestroyed<String> failureReasonInvalidToggle(MAKE_STATIC_STRING_IMPL("expected 0 or 1"));
388     static NeverDestroyed<String> failureReasonInvalidSeparator(MAKE_STATIC_STRING_IMPL("expected semicolon"));
389     static NeverDestroyed<String> failureReasonInvalidEquals(MAKE_STATIC_STRING_IMPL("expected equals sign"));
390     static NeverDestroyed<String> failureReasonInvalidMode(MAKE_STATIC_STRING_IMPL("invalid mode directive"));
391     static NeverDestroyed<String> failureReasonInvalidReport(MAKE_STATIC_STRING_IMPL("invalid report directive"));
392     static NeverDestroyed<String> failureReasonDuplicateMode(MAKE_STATIC_STRING_IMPL("duplicate mode directive"));
393     static NeverDestroyed<String> failureReasonDuplicateReport(MAKE_STATIC_STRING_IMPL("duplicate report directive"));
394     static NeverDestroyed<String> failureReasonInvalidDirective(MAKE_STATIC_STRING_IMPL("unrecognized directive"));
395
396     unsigned pos = 0;
397
398     if (!skipWhiteSpace(header, pos))
399         return XSSProtectionDisposition::Enabled;
400
401     if (header[pos] == '0')
402         return XSSProtectionDisposition::Disabled;
403
404     if (header[pos++] != '1') {
405         failureReason = failureReasonInvalidToggle;
406         return XSSProtectionDisposition::Invalid;
407     }
408
409     XSSProtectionDisposition result = XSSProtectionDisposition::Enabled;
410     bool modeDirectiveSeen = false;
411     bool reportDirectiveSeen = false;
412
413     while (1) {
414         // At end of previous directive: consume whitespace, semicolon, and whitespace.
415         if (!skipWhiteSpace(header, pos))
416             return result;
417
418         if (header[pos++] != ';') {
419             failureReason = failureReasonInvalidSeparator;
420             failurePosition = pos;
421             return XSSProtectionDisposition::Invalid;
422         }
423
424         if (!skipWhiteSpace(header, pos))
425             return result;
426
427         // At start of next directive.
428         if (skipToken(header, pos, "mode")) {
429             if (modeDirectiveSeen) {
430                 failureReason = failureReasonDuplicateMode;
431                 failurePosition = pos;
432                 return XSSProtectionDisposition::Invalid;
433             }
434             modeDirectiveSeen = true;
435             if (!skipEquals(header, pos)) {
436                 failureReason = failureReasonInvalidEquals;
437                 failurePosition = pos;
438                 return XSSProtectionDisposition::Invalid;
439             }
440             if (!skipToken(header, pos, "block")) {
441                 failureReason = failureReasonInvalidMode;
442                 failurePosition = pos;
443                 return XSSProtectionDisposition::Invalid;
444             }
445             result = XSSProtectionDisposition::BlockEnabled;
446         } else if (skipToken(header, pos, "report")) {
447             if (reportDirectiveSeen) {
448                 failureReason = failureReasonDuplicateReport;
449                 failurePosition = pos;
450                 return XSSProtectionDisposition::Invalid;
451             }
452             reportDirectiveSeen = true;
453             if (!skipEquals(header, pos)) {
454                 failureReason = failureReasonInvalidEquals;
455                 failurePosition = pos;
456                 return XSSProtectionDisposition::Invalid;
457             }
458             size_t startPos = pos;
459             if (!skipValue(header, pos)) {
460                 failureReason = failureReasonInvalidReport;
461                 failurePosition = pos;
462                 return XSSProtectionDisposition::Invalid;
463             }
464             reportURL = header.substring(startPos, pos - startPos);
465             failurePosition = startPos; // If later semantic check deems unacceptable.
466         } else {
467             failureReason = failureReasonInvalidDirective;
468             failurePosition = pos;
469             return XSSProtectionDisposition::Invalid;
470         }
471     }
472 }
473
474 ContentTypeOptionsDisposition parseContentTypeOptionsHeader(const String& header)
475 {
476     if (equalLettersIgnoringASCIICase(header.stripWhiteSpace(), "nosniff"))
477         return ContentTypeOptionsNosniff;
478     return ContentTypeOptionsNone;
479 }
480
481 // For example: "HTTP/1.1 200 OK" => "OK".
482 // Note that HTTP/2 does not include a reason phrase, so we return the empty atom.
483 AtomicString extractReasonPhraseFromHTTPStatusLine(const String& statusLine)
484 {
485     StringView view = statusLine;
486     size_t spacePos = view.find(' ');
487
488     // Remove status code from the status line.
489     spacePos = view.find(' ', spacePos + 1);
490     if (spacePos == notFound)
491         return emptyAtom();
492
493     return view.substring(spacePos + 1).toAtomicString();
494 }
495
496 XFrameOptionsDisposition parseXFrameOptionsHeader(const String& header)
497 {
498     XFrameOptionsDisposition result = XFrameOptionsNone;
499
500     if (header.isEmpty())
501         return result;
502
503     Vector<String> headers;
504     header.split(',', headers);
505
506     for (size_t i = 0; i < headers.size(); i++) {
507         String currentHeader = headers[i].stripWhiteSpace();
508         XFrameOptionsDisposition currentValue = XFrameOptionsNone;
509         if (equalLettersIgnoringASCIICase(currentHeader, "deny"))
510             currentValue = XFrameOptionsDeny;
511         else if (equalLettersIgnoringASCIICase(currentHeader, "sameorigin"))
512             currentValue = XFrameOptionsSameOrigin;
513         else if (equalLettersIgnoringASCIICase(currentHeader, "allowall"))
514             currentValue = XFrameOptionsAllowAll;
515         else
516             currentValue = XFrameOptionsInvalid;
517
518         if (result == XFrameOptionsNone)
519             result = currentValue;
520         else if (result != currentValue)
521             return XFrameOptionsConflict;
522     }
523     return result;
524 }
525
526 bool parseRange(const String& range, long long& rangeOffset, long long& rangeEnd, long long& rangeSuffixLength)
527 {
528     // The format of "Range" header is defined in RFC 2616 Section 14.35.1.
529     // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
530     // We don't support multiple range requests.
531
532     rangeOffset = rangeEnd = rangeSuffixLength = -1;
533
534     // The "bytes" unit identifier should be present.
535     static const char bytesStart[] = "bytes="; 
536     if (!range.startsWith(bytesStart, false))
537         return false;
538     String byteRange = range.substring(sizeof(bytesStart) - 1);
539
540     // The '-' character needs to be present.
541     int index = byteRange.find('-');
542     if (index == -1)
543         return false;
544
545     // If the '-' character is at the beginning, the suffix length, which specifies the last N bytes, is provided.
546     // Example:
547     //     -500
548     if (!index) {
549         String suffixLengthString = byteRange.substring(index + 1).stripWhiteSpace();
550         bool ok;
551         long long value = suffixLengthString.toInt64Strict(&ok);
552         if (ok)
553             rangeSuffixLength = value;
554         return true;
555     }
556
557     // Otherwise, the first-byte-position and the last-byte-position are provied.
558     // Examples:
559     //     0-499
560     //     500-
561     String firstBytePosStr = byteRange.left(index).stripWhiteSpace();
562     bool ok;
563     long long firstBytePos = firstBytePosStr.toInt64Strict(&ok);
564     if (!ok)
565         return false;
566
567     String lastBytePosStr = byteRange.substring(index + 1).stripWhiteSpace();
568     long long lastBytePos = -1;
569     if (!lastBytePosStr.isEmpty()) {
570         lastBytePos = lastBytePosStr.toInt64Strict(&ok);
571         if (!ok)
572             return false;
573     }
574
575     if (firstBytePos < 0 || !(lastBytePos == -1 || lastBytePos >= firstBytePos))
576         return false;
577
578     rangeOffset = firstBytePos;
579     rangeEnd = lastBytePos;
580     return true;
581 }
582
583 // HTTP/1.1 - RFC 2616
584 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1
585 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF
586 size_t parseHTTPRequestLine(const char* data, size_t length, String& failureReason, String& method, String& url, HTTPVersion& httpVersion)
587 {
588     method = String();
589     url = String();
590     httpVersion = Unknown;
591
592     const char* space1 = 0;
593     const char* space2 = 0;
594     const char* p;
595     size_t consumedLength;
596
597     for (p = data, consumedLength = 0; consumedLength < length; p++, consumedLength++) {
598         if (*p == ' ') {
599             if (!space1)
600                 space1 = p;
601             else if (!space2)
602                 space2 = p;
603         } else if (*p == '\n')
604             break;
605     }
606
607     // Haven't finished header line.
608     if (consumedLength == length) {
609         failureReason = ASCIILiteral("Incomplete Request Line");
610         return 0;
611     }
612
613     // RequestLine does not contain 3 parts.
614     if (!space1 || !space2) {
615         failureReason = ASCIILiteral("Request Line does not appear to contain: <Method> <Url> <HTTPVersion>.");
616         return 0;
617     }
618
619     // The line must end with "\r\n".
620     const char* end = p + 1;
621     if (*(end - 2) != '\r') {
622         failureReason = ASCIILiteral("Request line does not end with CRLF");
623         return 0;
624     }
625
626     // Request Method.
627     method = String(data, space1 - data); // For length subtract 1 for space, but add 1 for data being the first character.
628
629     // Request URI.
630     url = String(space1 + 1, space2 - space1 - 1); // For length subtract 1 for space.
631
632     // HTTP Version.
633     String httpVersionString(space2 + 1, end - space2 - 3); // For length subtract 1 for space, and 2 for "\r\n".
634     if (httpVersionString.length() != 8 || !httpVersionString.startsWith("HTTP/1."))
635         httpVersion = Unknown;
636     else if (httpVersionString[7] == '0')
637         httpVersion = HTTP_1_0;
638     else if (httpVersionString[7] == '1')
639         httpVersion = HTTP_1_1;
640     else
641         httpVersion = Unknown;
642
643     return end - data;
644 }
645
646 static inline bool isValidHeaderNameCharacter(const char* character)
647 {
648     // https://tools.ietf.org/html/rfc7230#section-3.2
649     // A header name should only contain one or more of
650     // alphanumeric or ! # $ % & ' * + - . ^ _ ` | ~
651     if (isASCIIAlphanumeric(*character))
652         return true;
653     switch (*character) {
654     case '!':
655     case '#':
656     case '$':
657     case '%':
658     case '&':
659     case '\'':
660     case '*':
661     case '+':
662     case '-':
663     case '.':
664     case '^':
665     case '_':
666     case '`':
667     case '|':
668     case '~':
669         return true;
670     default:
671         return false;
672     }
673 }
674
675 size_t parseHTTPHeader(const char* start, size_t length, String& failureReason, StringView& nameStr, String& valueStr, bool strict)
676 {
677     const char* p = start;
678     const char* end = start + length;
679
680     Vector<char> name;
681     Vector<char> value;
682
683     bool foundFirstNameChar = false;
684     const char* namePtr = nullptr;
685     size_t nameSize = 0;
686
687     nameStr = StringView();
688     valueStr = String();
689
690     for (; p < end; p++) {
691         switch (*p) {
692         case '\r':
693             if (name.isEmpty()) {
694                 if (p + 1 < end && *(p + 1) == '\n')
695                     return (p + 2) - start;
696                 failureReason = makeString("CR doesn't follow LF in header name at ", trimInputSample(p, end - p));
697                 return 0;
698             }
699             failureReason = makeString("Unexpected CR in header name at ", trimInputSample(name.data(), name.size()));
700             return 0;
701         case '\n':
702             failureReason = makeString("Unexpected LF in header name at ", trimInputSample(name.data(), name.size()));
703             return 0;
704         case ':':
705             break;
706         default:
707             if (!isValidHeaderNameCharacter(p)) {
708                 if (name.size() < 1)
709                     failureReason = "Unexpected start character in header name";
710                 else
711                     failureReason = makeString("Unexpected character in header name at ", trimInputSample(name.data(), name.size()));
712                 return 0;
713             }
714             name.append(*p);
715             if (!foundFirstNameChar) {
716                 namePtr = p;
717                 foundFirstNameChar = true;
718             }
719             continue;
720         }
721         if (*p == ':') {
722             ++p;
723             break;
724         }
725     }
726
727     nameSize = name.size();
728     nameStr = StringView(reinterpret_cast<const LChar*>(namePtr), nameSize);
729
730     for (; p < end && *p == 0x20; p++) { }
731
732     for (; p < end; p++) {
733         switch (*p) {
734         case '\r':
735             break;
736         case '\n':
737             if (strict) {
738                 failureReason = makeString("Unexpected LF in header value at ", trimInputSample(value.data(), value.size()));
739                 return 0;
740             }
741             break;
742         default:
743             value.append(*p);
744         }
745         if (*p == '\r' || (!strict && *p == '\n')) {
746             ++p;
747             break;
748         }
749     }
750     if (p >= end || (strict && *p != '\n')) {
751         failureReason = makeString("CR doesn't follow LF after header value at ", trimInputSample(p, end - p));
752         return 0;
753     }
754     valueStr = String::fromUTF8(value.data(), value.size());
755     if (valueStr.isNull()) {
756         failureReason = ASCIILiteral("Invalid UTF-8 sequence in header value");
757         return 0;
758     }
759     return p - start;
760 }
761
762 size_t parseHTTPRequestBody(const char* data, size_t length, Vector<unsigned char>& body)
763 {
764     body.clear();
765     body.append(data, length);
766
767     return length;
768 }
769
770 void parseAccessControlExposeHeadersAllowList(const String& headerValue, HTTPHeaderSet& headerSet)
771 {
772     Vector<String> headers;
773     headerValue.split(',', false, headers);
774     for (auto& header : headers) {
775         String strippedHeader = header.stripWhiteSpace();
776         if (!strippedHeader.isEmpty())
777             headerSet.add(strippedHeader);
778     }
779 }
780
781 // Implements <https://fetch.spec.whatwg.org/#forbidden-header-name>.
782 bool isForbiddenHeaderName(const String& name)
783 {
784     HTTPHeaderName headerName;
785     if (findHTTPHeaderName(name, headerName)) {
786         switch (headerName) {
787         case HTTPHeaderName::AcceptCharset:
788         case HTTPHeaderName::AcceptEncoding:
789         case HTTPHeaderName::AccessControlRequestHeaders:
790         case HTTPHeaderName::AccessControlRequestMethod:
791         case HTTPHeaderName::Connection:
792         case HTTPHeaderName::ContentLength:
793         case HTTPHeaderName::Cookie:
794         case HTTPHeaderName::Cookie2:
795         case HTTPHeaderName::Date:
796         case HTTPHeaderName::DNT:
797         case HTTPHeaderName::Expect:
798         case HTTPHeaderName::Host:
799         case HTTPHeaderName::KeepAlive:
800         case HTTPHeaderName::Origin:
801         case HTTPHeaderName::Referer:
802         case HTTPHeaderName::TE:
803         case HTTPHeaderName::Trailer:
804         case HTTPHeaderName::TransferEncoding:
805         case HTTPHeaderName::Upgrade:
806         case HTTPHeaderName::Via:
807             return true;
808         default:
809             break;
810         }
811     }
812     return startsWithLettersIgnoringASCIICase(name, "sec-") || startsWithLettersIgnoringASCIICase(name, "proxy-");
813 }
814
815 // Implements <https://fetch.spec.whatwg.org/#forbidden-response-header-name>.
816 bool isForbiddenResponseHeaderName(const String& name)
817 {
818     return equalLettersIgnoringASCIICase(name, "set-cookie") || equalLettersIgnoringASCIICase(name, "set-cookie2");
819 }
820
821 // Implements <https://fetch.spec.whatwg.org/#forbidden-method>.
822 bool isForbiddenMethod(const String& name)
823 {
824     return equalLettersIgnoringASCIICase(name, "connect") || equalLettersIgnoringASCIICase(name, "trace") || equalLettersIgnoringASCIICase(name, "track");
825 }
826
827 bool isSimpleHeader(const String& name, const String& value)
828 {
829     HTTPHeaderName headerName;
830     if (!findHTTPHeaderName(name, headerName))
831         return false;
832     return isCrossOriginSafeRequestHeader(headerName, value);
833 }
834
835 bool isCrossOriginSafeHeader(HTTPHeaderName name, const HTTPHeaderSet& accessControlExposeHeaderSet)
836 {
837     switch (name) {
838     case HTTPHeaderName::CacheControl:
839     case HTTPHeaderName::ContentLanguage:
840     case HTTPHeaderName::ContentType:
841     case HTTPHeaderName::Expires:
842     case HTTPHeaderName::LastModified:
843     case HTTPHeaderName::Pragma:
844     case HTTPHeaderName::Accept:
845         return true;
846     case HTTPHeaderName::SetCookie:
847     case HTTPHeaderName::SetCookie2:
848         return false;
849     default:
850         break;
851     }
852     return accessControlExposeHeaderSet.contains(httpHeaderNameString(name).toStringWithoutCopying());
853 }
854
855 bool isCrossOriginSafeHeader(const String& name, const HTTPHeaderSet& accessControlExposeHeaderSet)
856 {
857 #ifndef ASSERT_DISABLED
858     HTTPHeaderName headerName;
859     ASSERT(!findHTTPHeaderName(name, headerName));
860 #endif
861     return accessControlExposeHeaderSet.contains(name);
862 }
863
864 // Implements https://fetch.spec.whatwg.org/#cors-safelisted-request-header
865 bool isCrossOriginSafeRequestHeader(HTTPHeaderName name, const String& value)
866 {
867     switch (name) {
868     case HTTPHeaderName::Accept:
869         return isValidAcceptHeaderValue(value);
870     case HTTPHeaderName::AcceptLanguage:
871     case HTTPHeaderName::ContentLanguage:
872         return isValidLanguageHeaderValue(value);
873     case HTTPHeaderName::ContentType: {
874         // Preflight is required for MIME types that can not be sent via form submission.
875         String mimeType = extractMIMETypeFromMediaType(value);
876         return equalLettersIgnoringASCIICase(mimeType, "application/x-www-form-urlencoded") || equalLettersIgnoringASCIICase(mimeType, "multipart/form-data") || equalLettersIgnoringASCIICase(mimeType, "text/plain");
877     }
878     default:
879         // FIXME: Should we also make safe other headers (DPR, Downlink, Save-Data...)? That would require validating their values.
880         return false;
881     }
882 }
883
884 }