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