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