Fetch: content-length header is being added to the safe-list
[WebKit-https.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 https://fetch.spec.whatwg.org/#concept-header
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         ASSERT(c <= 0xFF);
125         if (c == 0x00 || c == 0x0A || c == 0x0D)
126             return false;
127     }
128     return true;
129 }
130
131 // See RFC 7230, Section 3.2.6.
132 static bool isDelimiterCharacter(const UChar c)
133 {
134     // DQUOTE and "(),/:;<=>?@[\]{}"
135     return (c == '"' || c == '(' || c == ')' || c == ',' || c == '/' || c == ':' || c == ';'
136         || c == '<' || c == '=' || c == '>' || c == '?' || c == '@' || c == '[' || c == '\\'
137         || c == ']' || c == '{' || c == '}');
138 }
139
140 // See RFC 7231, Section 5.3.2.
141 bool isValidAcceptHeaderValue(const String& value)
142 {
143     for (unsigned i = 0; i < value.length(); ++i) {
144         UChar c = value[i];
145
146         // First check for alphanumeric for performance reasons then whitelist four delimiter characters.
147         if (isASCIIAlphanumeric(c) || c == ',' || c == '/' || c == ';' || c == '=')
148             continue;
149
150         ASSERT(c <= 0xFF);
151         if (c == 0x7F || (c < 0x20 && c != '\t'))
152             return false;
153
154         if (isDelimiterCharacter(c))
155             return false;
156     }
157     
158     return true;
159 }
160
161 // See RFC 7231, Section 5.3.5 and 3.1.3.2.
162 bool isValidLanguageHeaderValue(const String& value)
163 {
164     for (unsigned i = 0; i < value.length(); ++i) {
165         UChar c = value[i];
166         if (isASCIIAlphanumeric(c) || c == ' ' || c == '*' || c == ',' || c == '-' || c == '.' || c == ';' || c == '=')
167             continue;
168         return false;
169     }
170     
171     // FIXME: Validate further by splitting into language tags and optional quality
172     // values (q=) and then check each language tag.
173     // Language tags https://tools.ietf.org/html/rfc7231#section-3.1.3.1
174     // Language tag syntax https://tools.ietf.org/html/bcp47#section-2.1
175     return true;
176 }
177
178 // See RFC 7230, Section 3.2.6.
179 bool isValidHTTPToken(const String& value)
180 {
181     if (value.isEmpty())
182         return false;
183     auto valueStringView = StringView(value);
184     for (UChar c : valueStringView.codeUnits()) {
185         if (c <= 0x20 || c >= 0x7F
186             || c == '(' || c == ')' || c == '<' || c == '>' || c == '@'
187             || c == ',' || c == ';' || c == ':' || c == '\\' || c == '"'
188             || c == '/' || c == '[' || c == ']' || c == '?' || c == '='
189             || c == '{' || c == '}')
190         return false;
191     }
192     return true;
193 }
194
195 static const size_t maxInputSampleSize = 128;
196 static String trimInputSample(const char* p, size_t length)
197 {
198     String s = String(p, std::min<size_t>(length, maxInputSampleSize));
199     if (length > maxInputSampleSize)
200         s.append(horizontalEllipsis);
201     return s;
202 }
203
204 bool parseHTTPRefresh(const String& refresh, double& delay, String& url)
205 {
206     unsigned len = refresh.length();
207     unsigned pos = 0;
208     
209     if (!skipWhiteSpace(refresh, pos))
210         return false;
211     
212     while (pos != len && refresh[pos] != ',' && refresh[pos] != ';')
213         ++pos;
214     
215     if (pos == len) { // no URL
216         url = String();
217         bool ok;
218         delay = refresh.stripWhiteSpace().toDouble(&ok);
219         return ok;
220     } else {
221         bool ok;
222         delay = refresh.left(pos).stripWhiteSpace().toDouble(&ok);
223         if (!ok)
224             return false;
225         
226         ++pos;
227         skipWhiteSpace(refresh, pos);
228         unsigned urlStartPos = pos;
229         if (refresh.findIgnoringASCIICase("url", urlStartPos) == urlStartPos) {
230             urlStartPos += 3;
231             skipWhiteSpace(refresh, urlStartPos);
232             if (refresh[urlStartPos] == '=') {
233                 ++urlStartPos;
234                 skipWhiteSpace(refresh, urlStartPos);
235             } else
236                 urlStartPos = pos;  // e.g. "Refresh: 0; url.html"
237         }
238
239         unsigned urlEndPos = len;
240
241         if (refresh[urlStartPos] == '"' || refresh[urlStartPos] == '\'') {
242             UChar quotationMark = refresh[urlStartPos];
243             urlStartPos++;
244             while (urlEndPos > urlStartPos) {
245                 urlEndPos--;
246                 if (refresh[urlEndPos] == quotationMark)
247                     break;
248             }
249             
250             // https://bugs.webkit.org/show_bug.cgi?id=27868
251             // Sometimes there is no closing quote for the end of the URL even though there was an opening quote.
252             // If we looped over the entire alleged URL string back to the opening quote, just use everything
253             // after the opening quote instead.
254             if (urlEndPos == urlStartPos)
255                 urlEndPos = len;
256         }
257
258         url = refresh.substring(urlStartPos, urlEndPos - urlStartPos).stripWhiteSpace();
259         return true;
260     }
261 }
262
263 std::optional<WallTime> parseHTTPDate(const String& value)
264 {
265     double dateInMillisecondsSinceEpoch = parseDateFromNullTerminatedCharacters(value.utf8().data());
266     if (!std::isfinite(dateInMillisecondsSinceEpoch))
267         return std::nullopt;
268     // This assumes system_clock epoch equals Unix epoch which is true for all implementations but unspecified.
269     // FIXME: The parsing function should be switched to WallTime too.
270     return WallTime::fromRawSeconds(dateInMillisecondsSinceEpoch / 1000.0);
271 }
272
273 // FIXME: This function doesn't comply with RFC 6266.
274 // For example, this function doesn't handle the interaction between " and ;
275 // that arises from quoted-string, nor does this function properly unquote
276 // attribute values. Further this function appears to process parameter names
277 // in a case-sensitive manner. (There are likely other bugs as well.)
278 String filenameFromHTTPContentDisposition(const String& value)
279 {
280     for (auto& keyValuePair : value.split(';')) {
281         size_t valueStartPos = keyValuePair.find('=');
282         if (valueStartPos == notFound)
283             continue;
284
285         String key = keyValuePair.left(valueStartPos).stripWhiteSpace();
286
287         if (key.isEmpty() || key != "filename")
288             continue;
289         
290         String value = keyValuePair.substring(valueStartPos + 1).stripWhiteSpace();
291
292         // Remove quotes if there are any
293         if (value[0] == '\"')
294             value = value.substring(1, value.length() - 2);
295
296         return value;
297     }
298
299     return String();
300 }
301
302 String extractMIMETypeFromMediaType(const String& mediaType)
303 {
304     StringBuilder mimeType;
305     unsigned length = mediaType.length();
306     mimeType.reserveCapacity(length);
307     for (unsigned i = 0; i < length; i++) {
308         UChar c = mediaType[i];
309
310         if (c == ';')
311             break;
312
313         // While RFC 2616 does not allow it, other browsers allow multiple values in the HTTP media
314         // type header field, Content-Type. In such cases, the media type string passed here may contain
315         // the multiple values separated by commas. For now, this code ignores text after the first comma,
316         // which prevents it from simply failing to parse such types altogether. Later for better
317         // compatibility we could consider using the first or last valid MIME type instead.
318         // See https://bugs.webkit.org/show_bug.cgi?id=25352 for more discussion.
319         if (c == ',')
320             break;
321
322         // FIXME: The following is not correct. RFC 2616 allows linear white space before and
323         // after the MIME type, but not within the MIME type itself. And linear white space
324         // includes only a few specific ASCII characters; a small subset of isSpaceOrNewline.
325         // See https://bugs.webkit.org/show_bug.cgi?id=8644 for a bug tracking part of this.
326         if (isSpaceOrNewline(c))
327             continue;
328
329         mimeType.append(c);
330     }
331
332     if (mimeType.length() == length)
333         return mediaType;
334     return mimeType.toString();
335 }
336
337 String extractCharsetFromMediaType(const String& mediaType)
338 {
339     unsigned int pos, len;
340     findCharsetInMediaType(mediaType, pos, len);
341     return mediaType.substring(pos, len);
342 }
343
344 void findCharsetInMediaType(const String& mediaType, unsigned int& charsetPos, unsigned int& charsetLen, unsigned int start)
345 {
346     charsetPos = start;
347     charsetLen = 0;
348
349     size_t pos = start;
350     unsigned length = mediaType.length();
351     
352     while (pos < length) {
353         pos = mediaType.findIgnoringASCIICase("charset", pos);
354         if (pos == notFound || pos == 0) {
355             charsetLen = 0;
356             return;
357         }
358         
359         // is what we found a beginning of a word?
360         if (mediaType[pos-1] > ' ' && mediaType[pos-1] != ';') {
361             pos += 7;
362             continue;
363         }
364         
365         pos += 7;
366
367         // skip whitespace
368         while (pos != length && mediaType[pos] <= ' ')
369             ++pos;
370     
371         if (mediaType[pos++] != '=') // this "charset" substring wasn't a parameter name, but there may be others
372             continue;
373
374         while (pos != length && (mediaType[pos] <= ' ' || mediaType[pos] == '"' || mediaType[pos] == '\''))
375             ++pos;
376
377         // we don't handle spaces within quoted parameter values, because charset names cannot have any
378         unsigned endpos = pos;
379         while (pos != length && mediaType[endpos] > ' ' && mediaType[endpos] != '"' && mediaType[endpos] != '\'' && mediaType[endpos] != ';')
380             ++endpos;
381
382         charsetPos = pos;
383         charsetLen = endpos - pos;
384         return;
385     }
386 }
387
388 XSSProtectionDisposition parseXSSProtectionHeader(const String& header, String& failureReason, unsigned& failurePosition, String& reportURL)
389 {
390     static NeverDestroyed<String> failureReasonInvalidToggle(MAKE_STATIC_STRING_IMPL("expected 0 or 1"));
391     static NeverDestroyed<String> failureReasonInvalidSeparator(MAKE_STATIC_STRING_IMPL("expected semicolon"));
392     static NeverDestroyed<String> failureReasonInvalidEquals(MAKE_STATIC_STRING_IMPL("expected equals sign"));
393     static NeverDestroyed<String> failureReasonInvalidMode(MAKE_STATIC_STRING_IMPL("invalid mode directive"));
394     static NeverDestroyed<String> failureReasonInvalidReport(MAKE_STATIC_STRING_IMPL("invalid report directive"));
395     static NeverDestroyed<String> failureReasonDuplicateMode(MAKE_STATIC_STRING_IMPL("duplicate mode directive"));
396     static NeverDestroyed<String> failureReasonDuplicateReport(MAKE_STATIC_STRING_IMPL("duplicate report directive"));
397     static NeverDestroyed<String> failureReasonInvalidDirective(MAKE_STATIC_STRING_IMPL("unrecognized directive"));
398
399     unsigned pos = 0;
400
401     if (!skipWhiteSpace(header, pos))
402         return XSSProtectionDisposition::Enabled;
403
404     if (header[pos] == '0')
405         return XSSProtectionDisposition::Disabled;
406
407     if (header[pos++] != '1') {
408         failureReason = failureReasonInvalidToggle;
409         return XSSProtectionDisposition::Invalid;
410     }
411
412     XSSProtectionDisposition result = XSSProtectionDisposition::Enabled;
413     bool modeDirectiveSeen = false;
414     bool reportDirectiveSeen = false;
415
416     while (1) {
417         // At end of previous directive: consume whitespace, semicolon, and whitespace.
418         if (!skipWhiteSpace(header, pos))
419             return result;
420
421         if (header[pos++] != ';') {
422             failureReason = failureReasonInvalidSeparator;
423             failurePosition = pos;
424             return XSSProtectionDisposition::Invalid;
425         }
426
427         if (!skipWhiteSpace(header, pos))
428             return result;
429
430         // At start of next directive.
431         if (skipToken(header, pos, "mode")) {
432             if (modeDirectiveSeen) {
433                 failureReason = failureReasonDuplicateMode;
434                 failurePosition = pos;
435                 return XSSProtectionDisposition::Invalid;
436             }
437             modeDirectiveSeen = true;
438             if (!skipEquals(header, pos)) {
439                 failureReason = failureReasonInvalidEquals;
440                 failurePosition = pos;
441                 return XSSProtectionDisposition::Invalid;
442             }
443             if (!skipToken(header, pos, "block")) {
444                 failureReason = failureReasonInvalidMode;
445                 failurePosition = pos;
446                 return XSSProtectionDisposition::Invalid;
447             }
448             result = XSSProtectionDisposition::BlockEnabled;
449         } else if (skipToken(header, pos, "report")) {
450             if (reportDirectiveSeen) {
451                 failureReason = failureReasonDuplicateReport;
452                 failurePosition = pos;
453                 return XSSProtectionDisposition::Invalid;
454             }
455             reportDirectiveSeen = true;
456             if (!skipEquals(header, pos)) {
457                 failureReason = failureReasonInvalidEquals;
458                 failurePosition = pos;
459                 return XSSProtectionDisposition::Invalid;
460             }
461             size_t startPos = pos;
462             if (!skipValue(header, pos)) {
463                 failureReason = failureReasonInvalidReport;
464                 failurePosition = pos;
465                 return XSSProtectionDisposition::Invalid;
466             }
467             reportURL = header.substring(startPos, pos - startPos);
468             failurePosition = startPos; // If later semantic check deems unacceptable.
469         } else {
470             failureReason = failureReasonInvalidDirective;
471             failurePosition = pos;
472             return XSSProtectionDisposition::Invalid;
473         }
474     }
475 }
476
477 ContentTypeOptionsDisposition parseContentTypeOptionsHeader(const String& header)
478 {
479     if (equalLettersIgnoringASCIICase(header.stripWhiteSpace(), "nosniff"))
480         return ContentTypeOptionsNosniff;
481     return ContentTypeOptionsNone;
482 }
483
484 // For example: "HTTP/1.1 200 OK" => "OK".
485 // Note that HTTP/2 does not include a reason phrase, so we return the empty atom.
486 AtomicString extractReasonPhraseFromHTTPStatusLine(const String& statusLine)
487 {
488     StringView view = statusLine;
489     size_t spacePos = view.find(' ');
490
491     // Remove status code from the status line.
492     spacePos = view.find(' ', spacePos + 1);
493     if (spacePos == notFound)
494         return emptyAtom();
495
496     return view.substring(spacePos + 1).toAtomicString();
497 }
498
499 XFrameOptionsDisposition parseXFrameOptionsHeader(const String& header)
500 {
501     XFrameOptionsDisposition result = XFrameOptionsNone;
502
503     if (header.isEmpty())
504         return result;
505
506     for (auto& currentHeader : header.split(',')) {
507         currentHeader = currentHeader.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 = "Incomplete Request Line"_s;
611         return 0;
612     }
613
614     // RequestLine does not contain 3 parts.
615     if (!space1 || !space2) {
616         failureReason = "Request Line does not appear to contain: <Method> <Url> <HTTPVersion>."_s;
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 = "Request line does not end with CRLF"_s;
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 = "Invalid UTF-8 sequence in header value"_s;
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     for (auto& header : headerValue.split(',')) {
774         String strippedHeader = header.stripWhiteSpace();
775         if (!strippedHeader.isEmpty())
776             headerSet.add(strippedHeader);
777     }
778 }
779
780 // Implements <https://fetch.spec.whatwg.org/#forbidden-header-name>.
781 bool isForbiddenHeaderName(const String& name)
782 {
783     HTTPHeaderName headerName;
784     if (findHTTPHeaderName(name, headerName)) {
785         switch (headerName) {
786         case HTTPHeaderName::AcceptCharset:
787         case HTTPHeaderName::AcceptEncoding:
788         case HTTPHeaderName::AccessControlRequestHeaders:
789         case HTTPHeaderName::AccessControlRequestMethod:
790         case HTTPHeaderName::Connection:
791         case HTTPHeaderName::ContentLength:
792         case HTTPHeaderName::Cookie:
793         case HTTPHeaderName::Cookie2:
794         case HTTPHeaderName::Date:
795         case HTTPHeaderName::DNT:
796         case HTTPHeaderName::Expect:
797         case HTTPHeaderName::Host:
798         case HTTPHeaderName::KeepAlive:
799         case HTTPHeaderName::Origin:
800         case HTTPHeaderName::Referer:
801         case HTTPHeaderName::TE:
802         case HTTPHeaderName::Trailer:
803         case HTTPHeaderName::TransferEncoding:
804         case HTTPHeaderName::Upgrade:
805         case HTTPHeaderName::Via:
806             return true;
807         default:
808             break;
809         }
810     }
811     return startsWithLettersIgnoringASCIICase(name, "sec-") || startsWithLettersIgnoringASCIICase(name, "proxy-");
812 }
813
814 // Implements <https://fetch.spec.whatwg.org/#forbidden-response-header-name>.
815 bool isForbiddenResponseHeaderName(const String& name)
816 {
817     return equalLettersIgnoringASCIICase(name, "set-cookie") || equalLettersIgnoringASCIICase(name, "set-cookie2");
818 }
819
820 // Implements <https://fetch.spec.whatwg.org/#forbidden-method>.
821 bool isForbiddenMethod(const String& name)
822 {
823     return equalLettersIgnoringASCIICase(name, "connect") || equalLettersIgnoringASCIICase(name, "trace") || equalLettersIgnoringASCIICase(name, "track");
824 }
825
826 bool isSimpleHeader(const String& name, const String& value)
827 {
828     HTTPHeaderName headerName;
829     if (!findHTTPHeaderName(name, headerName))
830         return false;
831     return isCrossOriginSafeRequestHeader(headerName, value);
832 }
833
834 bool isCrossOriginSafeHeader(HTTPHeaderName name, const HTTPHeaderSet& accessControlExposeHeaderSet)
835 {
836     switch (name) {
837     case HTTPHeaderName::CacheControl:
838     case HTTPHeaderName::ContentLanguage:
839     case HTTPHeaderName::ContentLength:
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 // Implements <https://fetch.spec.whatwg.org/#concept-method-normalize>.
885 String normalizeHTTPMethod(const String& method)
886 {
887     const ASCIILiteral methods[] = { "DELETE"_s, "GET"_s, "HEAD"_s, "OPTIONS"_s, "POST"_s, "PUT"_s };
888     for (auto value : methods) {
889         if (equalIgnoringASCIICase(method, value.characters())) {
890             // Don't bother allocating a new string if it's already all uppercase.
891             if (method == value)
892                 break;
893             return value;
894         }
895     }
896     return method;
897 }
898
899 CrossOriginResourcePolicy parseCrossOriginResourcePolicyHeader(StringView header)
900 {
901     auto strippedHeader = stripLeadingAndTrailingHTTPSpaces(header);
902
903     if (strippedHeader.isEmpty())
904         return CrossOriginResourcePolicy::None;
905
906     if (strippedHeader == "same-origin")
907         return CrossOriginResourcePolicy::SameOrigin;
908
909     if (strippedHeader == "same-site")
910         return CrossOriginResourcePolicy::SameSite;
911
912     return CrossOriginResourcePolicy::Invalid;
913 }
914
915 CrossOriginWindowPolicy parseCrossOriginWindowPolicyHeader(StringView header)
916 {
917     header = stripLeadingAndTrailingHTTPSpaces(header);
918     if (header.isEmpty())
919         return CrossOriginWindowPolicy::Allow;
920
921     if (equalLettersIgnoringASCIICase(header, "deny"))
922         return CrossOriginWindowPolicy::Deny;
923
924     if (equalLettersIgnoringASCIICase(header, "allow-postmessage"))
925         return CrossOriginWindowPolicy::AllowPostMessage;
926
927     return CrossOriginWindowPolicy::Allow;
928 }
929
930 }