diff options
Diffstat (limited to 'src/javascript/jsc/bindings/webcore/HTTPParsers.cpp')
-rw-r--r-- | src/javascript/jsc/bindings/webcore/HTTPParsers.cpp | 144 |
1 files changed, 93 insertions, 51 deletions
diff --git a/src/javascript/jsc/bindings/webcore/HTTPParsers.cpp b/src/javascript/jsc/bindings/webcore/HTTPParsers.cpp index d435cebf4..d49e7d820 100644 --- a/src/javascript/jsc/bindings/webcore/HTTPParsers.cpp +++ b/src/javascript/jsc/bindings/webcore/HTTPParsers.cpp @@ -10,13 +10,13 @@ * are met: * * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. + * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. + * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. + * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED @@ -33,9 +33,11 @@ #include "config.h" #include "HTTPParsers.h" +#include "CommonAtomStrings.h" #include "HTTPHeaderField.h" #include "HTTPHeaderNames.h" #include "ParsedContentType.h" +#include <wtf/CheckedArithmetic.h> #include <wtf/DateMath.h> #include <wtf/NeverDestroyed.h> #include <wtf/text/StringBuilder.h> @@ -86,7 +88,7 @@ static inline bool skipToken(const String& str, unsigned& pos, const char* token } // True if the expected equals sign is seen and there is more to follow. -static inline bool skipEquals(const String& str, unsigned &pos) +static inline bool skipEquals(const String& str, unsigned& pos) { return skipWhiteSpace(str, pos) && str[pos++] == '=' && skipWhiteSpace(str, pos); } @@ -150,7 +152,7 @@ bool isValidAcceptHeaderValue(const String& value) if (RFC7230::isDelimiter(c)) return false; } - + return true; } @@ -159,8 +161,7 @@ static bool containsCORSUnsafeRequestHeaderBytes(const String& value) for (unsigned i = 0; i < value.length(); ++i) { UChar c = value[i]; // https://fetch.spec.whatwg.org/#cors-unsafe-request-header-byte - if ((c < 0x20 && c != '\t') || (c == '"' || c == '(' || c == ')' || c == ':' || c == '<' || c == '>' || c == '?' - || c == '@' || c == '[' || c == '\\' || c == ']' || c == 0x7B || c == '{' || c == '}' || c == 0x7F)) + if ((c < 0x20 && c != '\t') || (c == '"' || c == '(' || c == ')' || c == ':' || c == '<' || c == '>' || c == '?' || c == '@' || c == '[' || c == '\\' || c == ']' || c == 0x7B || c == '{' || c == '}' || c == 0x7F)) return true; } @@ -168,6 +169,7 @@ static bool containsCORSUnsafeRequestHeaderBytes(const String& value) } // See RFC 7231, Section 5.3.5 and 3.1.3.2. +// https://fetch.spec.whatwg.org/#cors-safelisted-request-header bool isValidLanguageHeaderValue(const String& value) { for (unsigned i = 0; i < value.length(); ++i) { @@ -176,11 +178,6 @@ bool isValidLanguageHeaderValue(const String& value) continue; return false; } - - // FIXME: Validate further by splitting into language tags and optional quality - // values (q=) and then check each language tag. - // Language tags https://tools.ietf.org/html/rfc7231#section-3.1.3.1 - // Language tag syntax https://tools.ietf.org/html/bcp47#section-2.1 return true; } @@ -314,10 +311,9 @@ static const size_t maxInputSampleSize = 128; template<typename CharType> static String trimInputSample(CharType* p, size_t length) { - String s = String(p, std::min<size_t>(length, maxInputSampleSize)); - if (length > maxInputSampleSize) - s.append(horizontalEllipsis); - return s; + if (length <= maxInputSampleSize) + return String(p, length); + return makeString(StringView(p, length).left(maxInputSampleSize), horizontalEllipsis); } std::optional<WallTime> parseHTTPDate(const String& value) @@ -335,7 +331,7 @@ std::optional<WallTime> parseHTTPDate(const String& value) // that arises from quoted-string, nor does this function properly unquote // attribute values. Further this function appears to process parameter names // in a case-sensitive manner. (There are likely other bugs as well.) -String filenameFromHTTPContentDisposition(StringView value) +StringView filenameFromHTTPContentDisposition(StringView value) { for (auto keyValuePair : value.split(';')) { size_t valueStartPos = keyValuePair.find('='); @@ -346,14 +342,14 @@ String filenameFromHTTPContentDisposition(StringView value) if (key.isEmpty() || key != "filename") continue; - + auto value = keyValuePair.substring(valueStartPos + 1).stripWhiteSpace(); // Remove quotes if there are any if (value.length() > 1 && value[0] == '\"') value = value.substring(1, value.length() - 2); - return value.toString(); + return value; } return String(); @@ -397,7 +393,7 @@ String extractMIMETypeFromMediaType(const String& mediaType) return mediaType.substring(typeStart, typeEnd - typeStart); } -String extractCharsetFromMediaType(const String& mediaType) +StringView extractCharsetFromMediaType(StringView mediaType) { unsigned charsetPos = 0, charsetLen = 0; size_t pos = 0; @@ -411,7 +407,7 @@ String extractCharsetFromMediaType(const String& mediaType) } // is what we found a beginning of a word? - if (mediaType[pos-1] > ' ' && mediaType[pos-1] != ';') { + if (mediaType[pos - 1] > ' ' && mediaType[pos - 1] != ';') { pos += 7; continue; } @@ -419,18 +415,21 @@ String extractCharsetFromMediaType(const String& mediaType) pos += 7; // skip whitespace - while (pos != length && mediaType[pos] <= ' ') + while (pos < length && mediaType[pos] <= ' ') ++pos; + if (pos >= length) + break; + if (mediaType[pos++] != '=') // this "charset" substring wasn't a parameter name, but there may be others continue; - while (pos != length && (mediaType[pos] <= ' ' || mediaType[pos] == '"' || mediaType[pos] == '\'')) + while (pos < length && (mediaType[pos] <= ' ' || mediaType[pos] == '"' || mediaType[pos] == '\'')) ++pos; // we don't handle spaces within quoted parameter values, because charset names cannot have any unsigned endpos = pos; - while (pos != length && mediaType[endpos] > ' ' && mediaType[endpos] != '"' && mediaType[endpos] != '\'' && mediaType[endpos] != ';') + while (endpos < length && mediaType[endpos] > ' ' && mediaType[endpos] != '"' && mediaType[endpos] != '\'' && mediaType[endpos] != ';') ++endpos; charsetPos = pos; @@ -532,7 +531,7 @@ XSSProtectionDisposition parseXSSProtectionHeader(const String& header, String& ContentTypeOptionsDisposition parseContentTypeOptionsHeader(StringView header) { StringView leftToken = header.left(header.find(',')); - if (equalLettersIgnoringASCIICase(stripLeadingAndTrailingHTTPSpaces(leftToken), "nosniff")) + if (equalLettersIgnoringASCIICase(stripLeadingAndTrailingHTTPSpaces(leftToken), "nosniff"_s)) return ContentTypeOptionsDisposition::Nosniff; return ContentTypeOptionsDisposition::None; } @@ -562,11 +561,11 @@ XFrameOptionsDisposition parseXFrameOptionsHeader(StringView header) for (auto currentHeader : header.split(',')) { currentHeader = currentHeader.stripWhiteSpace(); XFrameOptionsDisposition currentValue = XFrameOptionsDisposition::None; - if (equalLettersIgnoringASCIICase(currentHeader, "deny")) + if (equalLettersIgnoringASCIICase(currentHeader, "deny"_s)) currentValue = XFrameOptionsDisposition::Deny; - else if (equalLettersIgnoringASCIICase(currentHeader, "sameorigin")) + else if (equalLettersIgnoringASCIICase(currentHeader, "sameorigin"_s)) currentValue = XFrameOptionsDisposition::SameOrigin; - else if (equalLettersIgnoringASCIICase(currentHeader, "allowall")) + else if (equalLettersIgnoringASCIICase(currentHeader, "allowall"_s)) currentValue = XFrameOptionsDisposition::AllowAll; else currentValue = XFrameOptionsDisposition::Invalid; @@ -597,7 +596,7 @@ std::optional<std::pair<StringView, HashMap<String, String>>> parseStructuredFie break; ++index; } - StringView bareItem = header.substring(0, index); + StringView bareItem = header.left(index); // Parse parameters (https://datatracker.ietf.org/doc/html/rfc8941#section-4.2.3.2). HashMap<String, String> parameters; @@ -619,8 +618,8 @@ std::optional<std::pair<StringView, HashMap<String, String>>> parseStructuredFie break; ++index; } - String key = header.substring(keyStart, index - keyStart).toString(); - String value = "true"; + StringView key = header.substring(keyStart, index - keyStart); + String value = trueAtom(); if (index < header.length() && header[index] == '=') { ++index; // Consume '='. if (isASCIIAlpha(header[index]) || header[index] == '*') { @@ -660,14 +659,14 @@ std::optional<std::pair<StringView, HashMap<String, String>>> parseStructuredFie } else return std::nullopt; } - parameters.set(WTFMove(key), WTFMove(value)); + parameters.set(key.toString(), WTFMove(value)); } if (index != header.length()) return std::nullopt; return std::make_pair(bareItem, parameters); } -bool parseRange(const String& range, long long& rangeOffset, long long& rangeEnd, long long& rangeSuffixLength) +bool parseRange(StringView range, long long& rangeOffset, long long& rangeEnd, long long& rangeSuffixLength) { // The format of "Range" header is defined in RFC 2616 Section 14.35.1. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1 @@ -677,10 +676,10 @@ bool parseRange(const String& range, long long& rangeOffset, long long& rangeEnd // The "bytes" unit identifier should be present. static const unsigned bytesLength = 6; - if (!startsWithLettersIgnoringASCIICase(range, "bytes=")) + if (!startsWithLettersIgnoringASCIICase(range, "bytes="_s)) return false; - // FIXME: The rest of this should use StringView. - String byteRange = range.substring(bytesLength); + + StringView byteRange = range.substring(bytesLength); // The '-' character needs to be present. int index = byteRange.find('-'); @@ -691,7 +690,7 @@ bool parseRange(const String& range, long long& rangeOffset, long long& rangeEnd // Example: // -500 if (!index) { - if (auto value = parseInteger<long long>(StringView { byteRange }.substring(index + 1))) + if (auto value = parseInteger<long long>(byteRange.substring(index + 1))) rangeSuffixLength = *value; return true; } @@ -700,11 +699,11 @@ bool parseRange(const String& range, long long& rangeOffset, long long& rangeEnd // Examples: // 0-499 // 500- - auto firstBytePos = parseInteger<long long>(StringView { byteRange }.left(index)); + auto firstBytePos = parseInteger<long long>(byteRange.left(index)); if (!firstBytePos) return false; - auto lastBytePosStr = stripLeadingAndTrailingHTTPSpaces(StringView { byteRange }.substring(index + 1)); + auto lastBytePosStr = stripLeadingAndTrailingHTTPSpaces(byteRange.substring(index + 1)); long long lastBytePos = -1; if (!lastBytePosStr.isEmpty()) { auto value = parseInteger<long long>(lastBytePosStr); @@ -785,7 +784,7 @@ size_t parseHTTPHeader(const uint8_t* start, size_t length, String& failureReaso default: if (!isValidHeaderNameCharacter(*p)) { if (name.size() < 1) - failureReason = "Unexpected start character in header name"; + failureReason = "Unexpected start character in header name"_s; else failureReason = makeString("Unexpected character in header name at ", trimInputSample(name.data(), name.size())); return 0; @@ -806,7 +805,8 @@ size_t parseHTTPHeader(const uint8_t* start, size_t length, String& failureReaso nameSize = name.size(); nameStr = StringView(namePtr, nameSize); - for (; p < end && *p == 0x20; p++) { } + for (; p < end && *p == 0x20; p++) { + } for (; p < end; p++) { switch (*p) { @@ -877,7 +877,7 @@ bool isForbiddenHeaderName(const String& name) break; } } - return startsWithLettersIgnoringASCIICase(name, "sec-") || startsWithLettersIgnoringASCIICase(name, "proxy-"); + return startsWithLettersIgnoringASCIICase(name, "sec-"_s) || startsWithLettersIgnoringASCIICase(name, "proxy-"_s); } // Implements <https://fetch.spec.whatwg.org/#no-cors-safelisted-request-header-name>. @@ -901,19 +901,19 @@ bool isNoCORSSafelistedRequestHeaderName(const String& name) // Implements <https://fetch.spec.whatwg.org/#privileged-no-cors-request-header-name>. bool isPriviledgedNoCORSRequestHeaderName(const String& name) { - return equalLettersIgnoringASCIICase(name, "range"); + return equalLettersIgnoringASCIICase(name, "range"_s); } // Implements <https://fetch.spec.whatwg.org/#forbidden-response-header-name>. bool isForbiddenResponseHeaderName(const String& name) { - return equalLettersIgnoringASCIICase(name, "set-cookie") || equalLettersIgnoringASCIICase(name, "set-cookie2"); + return equalLettersIgnoringASCIICase(name, "set-cookie"_s) || equalLettersIgnoringASCIICase(name, "set-cookie2"_s); } // Implements <https://fetch.spec.whatwg.org/#forbidden-method>. bool isForbiddenMethod(const String& name) { - return equalLettersIgnoringASCIICase(name, "connect") || equalLettersIgnoringASCIICase(name, "trace") || equalLettersIgnoringASCIICase(name, "track"); + return equalLettersIgnoringASCIICase(name, "connect"_s) || equalLettersIgnoringASCIICase(name, "trace"_s) || equalLettersIgnoringASCIICase(name, "track"_s); } bool isSimpleHeader(const String& name, const String& value) @@ -942,7 +942,7 @@ bool isCrossOriginSafeHeader(HTTPHeaderName name, const HTTPHeaderSet& accessCon default: break; } - return accessControlExposeHeaderSet.contains(httpHeaderNameString(name).toStringWithoutCopying()); + return accessControlExposeHeaderSet.contains<ASCIICaseInsensitiveStringViewHashTranslator>(httpHeaderNameString(name)); } bool isCrossOriginSafeHeader(const String& name, const HTTPHeaderSet& accessControlExposeHeaderSet) @@ -954,9 +954,47 @@ bool isCrossOriginSafeHeader(const String& name, const HTTPHeaderSet& accessCont return accessControlExposeHeaderSet.contains(name); } +static bool isSimpleRangeHeaderValue(const String& value) +{ + if (!value.startsWith("bytes="_s)) + return false; + + unsigned start = 0; + unsigned end = 0; + bool hasHyphen = false; + + for (size_t cptr = 6; cptr < value.length(); ++cptr) { + auto character = value[cptr]; + if (character >= '0' && character <= '9') { + if (productOverflows<unsigned>(hasHyphen ? end : start, 10)) + return false; + auto newDecimal = (hasHyphen ? end : start) * 10; + auto sum = Checked<unsigned, RecordOverflow>(newDecimal) + Checked<unsigned, RecordOverflow>(character - '0'); + if (sum.hasOverflowed()) + return false; + + if (hasHyphen) + end = sum.value(); + else + start = sum.value(); + continue; + } + if (character == '-' && !hasHyphen) { + hasHyphen = true; + continue; + } + return false; + } + + return hasHyphen && (!end || start < end); +} + // Implements https://fetch.spec.whatwg.org/#cors-safelisted-request-header bool isCrossOriginSafeRequestHeader(HTTPHeaderName name, const String& value) { + if (value.length() > 128) + return false; + switch (name) { case HTTPHeaderName::Accept: if (!isValidAcceptHeaderValue(value)) @@ -975,15 +1013,19 @@ bool isCrossOriginSafeRequestHeader(HTTPHeaderName name, const String& value) if (!parsedContentType) return false; String mimeType = parsedContentType->mimeType(); - if (!(equalLettersIgnoringASCIICase(mimeType, "application/x-www-form-urlencoded") || equalLettersIgnoringASCIICase(mimeType, "multipart/form-data") || equalLettersIgnoringASCIICase(mimeType, "text/plain"))) + if (!(equalLettersIgnoringASCIICase(mimeType, "application/x-www-form-urlencoded"_s) || equalLettersIgnoringASCIICase(mimeType, "multipart/form-data"_s) || equalLettersIgnoringASCIICase(mimeType, "text/plain"_s))) return false; break; } + case HTTPHeaderName::Range: + if (!isSimpleRangeHeaderValue(value)) + return false; + break; default: // FIXME: Should we also make safe other headers (DPR, Downlink, Save-Data...)? That would require validating their values. return false; } - return value.length() <= 128; + return true; } // Implements <https://fetch.spec.whatwg.org/#concept-method-normalize>. @@ -991,7 +1033,7 @@ String normalizeHTTPMethod(const String& method) { const ASCIILiteral methods[] = { "DELETE"_s, "GET"_s, "HEAD"_s, "OPTIONS"_s, "POST"_s, "PUT"_s }; for (auto value : methods) { - if (equalIgnoringASCIICase(method, value.characters())) { + if (equalIgnoringASCIICase(method, value)) { // Don't bother allocating a new string if it's already all uppercase. if (method == value) break; @@ -1006,7 +1048,7 @@ bool isSafeMethod(const String& method) { const ASCIILiteral safeMethods[] = { "GET"_s, "HEAD"_s, "OPTIONS"_s, "TRACE"_s }; for (auto value : safeMethods) { - if (equalIgnoringASCIICase(method, value.characters())) + if (equalIgnoringASCIICase(method, value)) return true; } return false; @@ -1031,4 +1073,4 @@ CrossOriginResourcePolicy parseCrossOriginResourcePolicyHeader(StringView header return CrossOriginResourcePolicy::Invalid; } -} +}
\ No newline at end of file |