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