aboutsummaryrefslogtreecommitdiff
path: root/src/javascript/jsc/bindings/webcore/HTTPParsers.cpp
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-05-08 01:12:13 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-05-08 01:12:13 -0700
commit97cceb47b9fb14fbf8cb45a28038934b6416e8ef (patch)
tree250c430effeff27b5d37070f7c647bd1bfd7477b /src/javascript/jsc/bindings/webcore/HTTPParsers.cpp
parent94637711b9f558576ffebe2f5dd834dc05a95f7b (diff)
downloadbun-97cceb47b9fb14fbf8cb45a28038934b6416e8ef.tar.gz
bun-97cceb47b9fb14fbf8cb45a28038934b6416e8ef.tar.zst
bun-97cceb47b9fb14fbf8cb45a28038934b6416e8ef.zip
Upgrade WebKit
Diffstat (limited to 'src/javascript/jsc/bindings/webcore/HTTPParsers.cpp')
-rw-r--r--src/javascript/jsc/bindings/webcore/HTTPParsers.cpp144
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