From-Origin: Support for 'same' and 'same-site' response header, nested frame origin...
[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
46 namespace WebCore {
47 using namespace WTF;
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.findIgnoringASCIICase("url", urlStartPos) == 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<WallTime> parseHTTPDate(const String& value)
257 {
258     double dateInMillisecondsSinceEpoch = parseDateFromNullTerminatedCharacters(value.utf8().data());
259     if (!std::isfinite(dateInMillisecondsSinceEpoch))
260         return std::nullopt;
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 WallTime too.
263     return WallTime::fromRawSeconds(dateInMillisecondsSinceEpoch / 1000.0);
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.findIgnoringASCIICase("charset", pos);
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 unsigned bytesLength = 6;
536     if (!startsWithLettersIgnoringASCIICase(range, "bytes="))
537         return false;
538     // FIXME: The rest of this should use StringView.
539     String byteRange = range.substring(bytesLength);
540
541     // The '-' character needs to be present.
542     int index = byteRange.find('-');
543     if (index == -1)
544         return false;
545
546     // If the '-' character is at the beginning, the suffix length, which specifies the last N bytes, is provided.
547     // Example:
548     //     -500
549     if (!index) {
550         String suffixLengthString = byteRange.substring(index + 1).stripWhiteSpace();
551         bool ok;
552         long long value = suffixLengthString.toInt64Strict(&ok);
553         if (ok)
554             rangeSuffixLength = value;
555         return true;
556     }
557
558     // Otherwise, the first-byte-position and the last-byte-position are provied.
559     // Examples:
560     //     0-499
561     //     500-
562     String firstBytePosStr = byteRange.left(index).stripWhiteSpace();
563     bool ok;
564     long long firstBytePos = firstBytePosStr.toInt64Strict(&ok);
565     if (!ok)
566         return false;
567
568     String lastBytePosStr = byteRange.substring(index + 1).stripWhiteSpace();
569     long long lastBytePos = -1;
570     if (!lastBytePosStr.isEmpty()) {
571         lastBytePos = lastBytePosStr.toInt64Strict(&ok);
572         if (!ok)
573             return false;
574     }
575
576     if (firstBytePos < 0 || !(lastBytePos == -1 || lastBytePos >= firstBytePos))
577         return false;
578
579     rangeOffset = firstBytePos;
580     rangeEnd = lastBytePos;
581     return true;
582 }
583
584 // HTTP/1.1 - RFC 2616
585 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1
586 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF
587 size_t parseHTTPRequestLine(const char* data, size_t length, String& failureReason, String& method, String& url, HTTPVersion& httpVersion)
588 {
589     method = String();
590     url = String();
591     httpVersion = Unknown;
592
593     const char* space1 = 0;
594     const char* space2 = 0;
595     const char* p;
596     size_t consumedLength;
597
598     for (p = data, consumedLength = 0; consumedLength < length; p++, consumedLength++) {
599         if (*p == ' ') {
600             if (!space1)
601                 space1 = p;
602             else if (!space2)
603                 space2 = p;
604         } else if (*p == '\n')
605             break;
606     }
607
608     // Haven't finished header line.
609     if (consumedLength == length) {
610         failureReason = ASCIILiteral("Incomplete Request Line");
611         return 0;
612     }
613
614     // RequestLine does not contain 3 parts.
615     if (!space1 || !space2) {
616         failureReason = ASCIILiteral("Request Line does not appear to contain: <Method> <Url> <HTTPVersion>.");
617         return 0;
618     }
619
620     // The line must end with "\r\n".
621     const char* end = p + 1;
622     if (*(end - 2) != '\r') {
623         failureReason = ASCIILiteral("Request line does not end with CRLF");
624         return 0;
625     }
626
627     // Request Method.
628     method = String(data, space1 - data); // For length subtract 1 for space, but add 1 for data being the first character.
629
630     // Request URI.
631     url = String(space1 + 1, space2 - space1 - 1); // For length subtract 1 for space.
632
633     // HTTP Version.
634     String httpVersionString(space2 + 1, end - space2 - 3); // For length subtract 1 for space, and 2 for "\r\n".
635     if (httpVersionString.length() != 8 || !httpVersionString.startsWith("HTTP/1."))
636         httpVersion = Unknown;
637     else if (httpVersionString[7] == '0')
638         httpVersion = HTTP_1_0;
639     else if (httpVersionString[7] == '1')
640         httpVersion = HTTP_1_1;
641     else
642         httpVersion = Unknown;
643
644     return end - data;
645 }
646
647 static inline bool isValidHeaderNameCharacter(const char* character)
648 {
649     // https://tools.ietf.org/html/rfc7230#section-3.2
650     // A header name should only contain one or more of
651     // alphanumeric or ! # $ % & ' * + - . ^ _ ` | ~
652     if (isASCIIAlphanumeric(*character))
653         return true;
654     switch (*character) {
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     case '~':
670         return true;
671     default:
672         return false;
673     }
674 }
675
676 size_t parseHTTPHeader(const char* start, size_t length, String& failureReason, StringView& nameStr, String& valueStr, bool strict)
677 {
678     const char* p = start;
679     const char* end = start + length;
680
681     Vector<char> name;
682     Vector<char> value;
683
684     bool foundFirstNameChar = false;
685     const char* namePtr = nullptr;
686     size_t nameSize = 0;
687
688     nameStr = StringView();
689     valueStr = String();
690
691     for (; p < end; p++) {
692         switch (*p) {
693         case '\r':
694             if (name.isEmpty()) {
695                 if (p + 1 < end && *(p + 1) == '\n')
696                     return (p + 2) - start;
697                 failureReason = makeString("CR doesn't follow LF in header name at ", trimInputSample(p, end - p));
698                 return 0;
699             }
700             failureReason = makeString("Unexpected CR in header name at ", trimInputSample(name.data(), name.size()));
701             return 0;
702         case '\n':
703             failureReason = makeString("Unexpected LF in header name at ", trimInputSample(name.data(), name.size()));
704             return 0;
705         case ':':
706             break;
707         default:
708             if (!isValidHeaderNameCharacter(p)) {
709                 if (name.size() < 1)
710                     failureReason = "Unexpected start character in header name";
711                 else
712                     failureReason = makeString("Unexpected character in header name at ", trimInputSample(name.data(), name.size()));
713                 return 0;
714             }
715             name.append(*p);
716             if (!foundFirstNameChar) {
717                 namePtr = p;
718                 foundFirstNameChar = true;
719             }
720             continue;
721         }
722         if (*p == ':') {
723             ++p;
724             break;
725         }
726     }
727
728     nameSize = name.size();
729     nameStr = StringView(reinterpret_cast<const LChar*>(namePtr), nameSize);
730
731     for (; p < end && *p == 0x20; p++) { }
732
733     for (; p < end; p++) {
734         switch (*p) {
735         case '\r':
736             break;
737         case '\n':
738             if (strict) {
739                 failureReason = makeString("Unexpected LF in header value at ", trimInputSample(value.data(), value.size()));
740                 return 0;
741             }
742             break;
743         default:
744             value.append(*p);
745         }
746         if (*p == '\r' || (!strict && *p == '\n')) {
747             ++p;
748             break;
749         }
750     }
751     if (p >= end || (strict && *p != '\n')) {
752         failureReason = makeString("CR doesn't follow LF after header value at ", trimInputSample(p, end - p));
753         return 0;
754     }
755     valueStr = String::fromUTF8(value.data(), value.size());
756     if (valueStr.isNull()) {
757         failureReason = ASCIILiteral("Invalid UTF-8 sequence in header value");
758         return 0;
759     }
760     return p - start;
761 }
762
763 size_t parseHTTPRequestBody(const char* data, size_t length, Vector<unsigned char>& body)
764 {
765     body.clear();
766     body.append(data, length);
767
768     return length;
769 }
770
771 void parseAccessControlExposeHeadersAllowList(const String& headerValue, HTTPHeaderSet& headerSet)
772 {
773     Vector<String> headers;
774     headerValue.split(',', false, headers);
775     for (auto& header : headers) {
776         String strippedHeader = header.stripWhiteSpace();
777         if (!strippedHeader.isEmpty())
778             headerSet.add(strippedHeader);
779     }
780 }
781
782 // Implements <https://fetch.spec.whatwg.org/#forbidden-header-name>.
783 bool isForbiddenHeaderName(const String& name)
784 {
785     HTTPHeaderName headerName;
786     if (findHTTPHeaderName(name, headerName)) {
787         switch (headerName) {
788         case HTTPHeaderName::AcceptCharset:
789         case HTTPHeaderName::AcceptEncoding:
790         case HTTPHeaderName::AccessControlRequestHeaders:
791         case HTTPHeaderName::AccessControlRequestMethod:
792         case HTTPHeaderName::Connection:
793         case HTTPHeaderName::ContentLength:
794         case HTTPHeaderName::Cookie:
795         case HTTPHeaderName::Cookie2:
796         case HTTPHeaderName::Date:
797         case HTTPHeaderName::DNT:
798         case HTTPHeaderName::Expect:
799         case HTTPHeaderName::Host:
800         case HTTPHeaderName::KeepAlive:
801         case HTTPHeaderName::Origin:
802         case HTTPHeaderName::Referer:
803         case HTTPHeaderName::TE:
804         case HTTPHeaderName::Trailer:
805         case HTTPHeaderName::TransferEncoding:
806         case HTTPHeaderName::Upgrade:
807         case HTTPHeaderName::Via:
808             return true;
809         default:
810             break;
811         }
812     }
813     return startsWithLettersIgnoringASCIICase(name, "sec-") || startsWithLettersIgnoringASCIICase(name, "proxy-");
814 }
815
816 // Implements <https://fetch.spec.whatwg.org/#forbidden-response-header-name>.
817 bool isForbiddenResponseHeaderName(const String& name)
818 {
819     return equalLettersIgnoringASCIICase(name, "set-cookie") || equalLettersIgnoringASCIICase(name, "set-cookie2");
820 }
821
822 // Implements <https://fetch.spec.whatwg.org/#forbidden-method>.
823 bool isForbiddenMethod(const String& name)
824 {
825     return equalLettersIgnoringASCIICase(name, "connect") || equalLettersIgnoringASCIICase(name, "trace") || equalLettersIgnoringASCIICase(name, "track");
826 }
827
828 bool isSimpleHeader(const String& name, const String& value)
829 {
830     HTTPHeaderName headerName;
831     if (!findHTTPHeaderName(name, headerName))
832         return false;
833     return isCrossOriginSafeRequestHeader(headerName, value);
834 }
835
836 bool isCrossOriginSafeHeader(HTTPHeaderName name, const HTTPHeaderSet& accessControlExposeHeaderSet)
837 {
838     switch (name) {
839     case HTTPHeaderName::CacheControl:
840     case HTTPHeaderName::ContentLanguage:
841     case HTTPHeaderName::ContentType:
842     case HTTPHeaderName::Expires:
843     case HTTPHeaderName::LastModified:
844     case HTTPHeaderName::Pragma:
845     case HTTPHeaderName::Accept:
846         return true;
847     case HTTPHeaderName::SetCookie:
848     case HTTPHeaderName::SetCookie2:
849         return false;
850     default:
851         break;
852     }
853     return accessControlExposeHeaderSet.contains(httpHeaderNameString(name).toStringWithoutCopying());
854 }
855
856 bool isCrossOriginSafeHeader(const String& name, const HTTPHeaderSet& accessControlExposeHeaderSet)
857 {
858 #ifndef ASSERT_DISABLED
859     HTTPHeaderName headerName;
860     ASSERT(!findHTTPHeaderName(name, headerName));
861 #endif
862     return accessControlExposeHeaderSet.contains(name);
863 }
864
865 // Implements https://fetch.spec.whatwg.org/#cors-safelisted-request-header
866 bool isCrossOriginSafeRequestHeader(HTTPHeaderName name, const String& value)
867 {
868     switch (name) {
869     case HTTPHeaderName::Accept:
870         return isValidAcceptHeaderValue(value);
871     case HTTPHeaderName::AcceptLanguage:
872     case HTTPHeaderName::ContentLanguage:
873         return isValidLanguageHeaderValue(value);
874     case HTTPHeaderName::ContentType: {
875         // Preflight is required for MIME types that can not be sent via form submission.
876         String mimeType = extractMIMETypeFromMediaType(value);
877         return equalLettersIgnoringASCIICase(mimeType, "application/x-www-form-urlencoded") || equalLettersIgnoringASCIICase(mimeType, "multipart/form-data") || equalLettersIgnoringASCIICase(mimeType, "text/plain");
878     }
879     default:
880         // FIXME: Should we also make safe other headers (DPR, Downlink, Save-Data...)? That would require validating their values.
881         return false;
882     }
883 }
884
885 // Implements <https://fetch.spec.whatwg.org/#concept-method-normalize>.
886 String normalizeHTTPMethod(const String& method)
887 {
888     const char* const methods[] = { "DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT" };
889     for (auto* value : methods) {
890         if (equalIgnoringASCIICase(method, value)) {
891             // Don't bother allocating a new string if it's already all uppercase.
892             if (method == value)
893                 break;
894             return ASCIILiteral { value };
895         }
896     }
897     return method;
898 }
899
900 FromOriginDisposition parseFromOriginHeader(const String& header)
901 {
902     auto strippedHeader = stripLeadingAndTrailingHTTPSpaces(header);
903
904     if (strippedHeader.isEmpty())
905         return FromOriginDisposition::None;
906
907     if (equalLettersIgnoringASCIICase(strippedHeader, "same"))
908         return FromOriginDisposition::Same;
909
910     if (equalLettersIgnoringASCIICase(strippedHeader, "same-site"))
911         return FromOriginDisposition::SameSite;
912
913     return FromOriginDisposition::Invalid;
914 }
915
916 }