| /* |
| * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org) |
| * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. |
| * Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/ |
| * Copyright (C) 2009 Google Inc. All rights reserved. |
| * Copyright (C) 2011 Apple Inc. All Rights Reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * 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. |
| * 3. Neither the name of Apple Computer, 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. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "platform/network/HTTPParsers.h" |
| |
| #include "net/http/http_response_headers.h" |
| #include "net/http/http_util.h" |
| #include "platform/json/JSONParser.h" |
| #include "platform/network/ResourceResponse.h" |
| #include "platform/weborigin/Suborigin.h" |
| #include "public/platform/WebString.h" |
| #include "wtf/DateMath.h" |
| #include "wtf/MathExtras.h" |
| #include "wtf/text/CString.h" |
| #include "wtf/text/CharacterNames.h" |
| #include "wtf/text/ParsingUtilities.h" |
| #include "wtf/text/StringBuilder.h" |
| #include "wtf/text/StringUTF8Adaptor.h" |
| #include "wtf/text/WTFString.h" |
| |
| using namespace WTF; |
| |
| namespace blink { |
| |
| namespace { |
| |
| const Vector<AtomicString>& replaceHeaders() { |
| // The list of response headers that we do not copy from the original |
| // response when generating a ResourceResponse for a MIME payload. |
| // Note: this is called only on the main thread. |
| DEFINE_STATIC_LOCAL(Vector<AtomicString>, headers, |
| ({"content-type", "content-length", "content-disposition", |
| "content-range", "range", "set-cookie"})); |
| return headers; |
| } |
| |
| bool isWhitespace(UChar chr) { |
| return (chr == ' ') || (chr == '\t'); |
| } |
| |
| // true if there is more to parse, after incrementing pos past whitespace. |
| // Note: Might return pos == str.length() |
| // if |matcher| is nullptr, isWhitespace() is used. |
| inline bool skipWhiteSpace(const String& str, |
| unsigned& pos, |
| CharacterMatchFunctionPtr matcher = nullptr) { |
| unsigned len = str.length(); |
| |
| if (matcher) { |
| while (pos < len && matcher(str[pos])) |
| ++pos; |
| } else { |
| while (pos < len && isWhitespace(str[pos])) |
| ++pos; |
| } |
| |
| return pos < len; |
| } |
| |
| // Returns true if the function can match the whole token (case insensitive) |
| // incrementing pos on match, otherwise leaving pos unchanged. |
| // Note: Might return pos == str.length() |
| inline bool skipToken(const String& str, unsigned& pos, const char* token) { |
| unsigned len = str.length(); |
| unsigned current = pos; |
| |
| while (current < len && *token) { |
| if (toASCIILower(str[current]) != *token++) |
| return false; |
| ++current; |
| } |
| |
| if (*token) |
| return false; |
| |
| pos = current; |
| return true; |
| } |
| |
| // True if the expected equals sign is seen and there is more to follow. |
| inline bool skipEquals(const String& str, unsigned& pos) { |
| return skipWhiteSpace(str, pos) && str[pos++] == '=' && |
| skipWhiteSpace(str, pos); |
| } |
| |
| // True if a value present, incrementing pos to next space or semicolon, if any. |
| // Note: might return pos == str.length(). |
| inline bool skipValue(const String& str, unsigned& pos) { |
| unsigned start = pos; |
| unsigned len = str.length(); |
| while (pos < len) { |
| if (str[pos] == ' ' || str[pos] == '\t' || str[pos] == ';') |
| break; |
| ++pos; |
| } |
| return pos != start; |
| } |
| |
| template <typename CharType> |
| inline bool isASCIILowerAlphaOrDigit(CharType c) { |
| return isASCIILower(c) || isASCIIDigit(c); |
| } |
| |
| template <typename CharType> |
| inline bool isASCIILowerAlphaOrDigitOrHyphen(CharType c) { |
| return isASCIILowerAlphaOrDigit(c) || c == '-'; |
| } |
| |
| Suborigin::SuboriginPolicyOptions getSuboriginPolicyOptionFromString( |
| const String& policyOptionName) { |
| if (policyOptionName == "'unsafe-postmessage-send'") |
| return Suborigin::SuboriginPolicyOptions::UnsafePostMessageSend; |
| |
| if (policyOptionName == "'unsafe-postmessage-receive'") |
| return Suborigin::SuboriginPolicyOptions::UnsafePostMessageReceive; |
| |
| if (policyOptionName == "'unsafe-cookies'") |
| return Suborigin::SuboriginPolicyOptions::UnsafeCookies; |
| |
| if (policyOptionName == "'unsafe-credentials'") |
| return Suborigin::SuboriginPolicyOptions::UnsafeCredentials; |
| |
| return Suborigin::SuboriginPolicyOptions::None; |
| } |
| |
| // suborigin-name = LOWERALPHA *( LOWERALPHA / DIGIT ) |
| // |
| // Does not trim whitespace before or after the suborigin-name. |
| const UChar* parseSuboriginName(const UChar* begin, |
| const UChar* end, |
| String& name, |
| WTF::Vector<String>& messages) { |
| // Parse the name of the suborigin (no spaces, single string) |
| if (begin == end) { |
| messages.append(String("No Suborigin name specified.")); |
| return nullptr; |
| } |
| |
| const UChar* position = begin; |
| |
| if (!skipExactly<UChar, isASCIILower>(position, end)) { |
| messages.append("Invalid character \'" + String(position, 1) + |
| "\' in suborigin. First character must be a lower case " |
| "alphabetic character."); |
| return nullptr; |
| } |
| |
| skipWhile<UChar, isASCIILowerAlphaOrDigit>(position, end); |
| if (position != end && !isASCIISpace(*position)) { |
| messages.append("Invalid character \'" + String(position, 1) + |
| "\' in suborigin."); |
| return nullptr; |
| } |
| |
| size_t length = position - begin; |
| name = String(begin, length).lower(); |
| return position; |
| } |
| |
| const UChar* parseSuboriginPolicyOption(const UChar* begin, |
| const UChar* end, |
| String& option, |
| WTF::Vector<String>& messages) { |
| const UChar* position = begin; |
| |
| if (*position != '\'') { |
| messages.append("Invalid character \'" + String(position, 1) + |
| "\' in suborigin policy. Suborigin policy options must " |
| "start and end with a single quote."); |
| return nullptr; |
| } |
| position = position + 1; |
| |
| skipWhile<UChar, isASCIILowerAlphaOrDigitOrHyphen>(position, end); |
| if (position == end || isASCIISpace(*position)) { |
| messages.append(String("Expected \' to end policy option.")); |
| return nullptr; |
| } |
| |
| if (*position != '\'') { |
| messages.append("Invalid character \'" + String(position, 1) + |
| "\' in suborigin policy."); |
| return nullptr; |
| } |
| |
| ASSERT(position > begin); |
| size_t length = (position + 1) - begin; |
| |
| option = String(begin, length); |
| return position + 1; |
| } |
| |
| } // namespace |
| |
| bool isValidHTTPHeaderValue(const String& name) { |
| // FIXME: This should really match name against |
| // field-value in section 4.2 of RFC 2616. |
| |
| return name.containsOnlyLatin1() && !name.contains('\r') && |
| !name.contains('\n') && !name.contains('\0'); |
| } |
| |
| // See RFC 7230, Section 3.2. |
| // Checks whether |value| matches field-content in RFC 7230. |
| // link: http://tools.ietf.org/html/rfc7230#section-3.2 |
| bool isValidHTTPFieldContentRFC7230(const String& value) { |
| if (value.isEmpty()) |
| return false; |
| |
| UChar firstCharacter = value[0]; |
| if (firstCharacter == ' ' || firstCharacter == '\t') |
| return false; |
| |
| UChar lastCharacter = value[value.length() - 1]; |
| if (lastCharacter == ' ' || lastCharacter == '\t') |
| return false; |
| |
| for (unsigned i = 0; i < value.length(); ++i) { |
| UChar c = value[i]; |
| // TODO(mkwst): Extract this character class to a central location, |
| // https://crbug.com/527324. |
| if (c == 0x7F || c > 0xFF || (c < 0x20 && c != '\t')) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // See RFC 7230, Section 3.2.6. |
| bool isValidHTTPToken(const String& characters) { |
| if (characters.isEmpty()) |
| return false; |
| for (unsigned i = 0; i < characters.length(); ++i) { |
| UChar c = characters[i]; |
| if (c > 0x7F || !net::HttpUtil::IsTokenChar(c)) |
| return false; |
| } |
| return true; |
| } |
| |
| ContentDispositionType getContentDispositionType( |
| const String& contentDisposition) { |
| if (contentDisposition.isEmpty()) |
| return ContentDispositionNone; |
| |
| Vector<String> parameters; |
| contentDisposition.split(';', parameters); |
| |
| if (parameters.isEmpty()) |
| return ContentDispositionNone; |
| |
| String dispositionType = parameters[0]; |
| dispositionType.stripWhiteSpace(); |
| |
| if (equalIgnoringCase(dispositionType, "inline")) |
| return ContentDispositionInline; |
| |
| // Some broken sites just send bogus headers like |
| // |
| // Content-Disposition: ; filename="file" |
| // Content-Disposition: filename="file" |
| // Content-Disposition: name="file" |
| // |
| // without a disposition token... screen those out. |
| if (!isValidHTTPToken(dispositionType)) |
| return ContentDispositionNone; |
| |
| // We have a content-disposition of "attachment" or unknown. |
| // RFC 2183, section 2.8 says that an unknown disposition |
| // value should be treated as "attachment" |
| return ContentDispositionAttachment; |
| } |
| |
| bool parseHTTPRefresh(const String& refresh, |
| CharacterMatchFunctionPtr matcher, |
| double& delay, |
| String& url) { |
| unsigned len = refresh.length(); |
| unsigned pos = 0; |
| matcher = matcher ? matcher : isWhitespace; |
| |
| if (!skipWhiteSpace(refresh, pos, matcher)) |
| return false; |
| |
| while (pos != len && refresh[pos] != ',' && refresh[pos] != ';' && |
| !matcher(refresh[pos])) |
| ++pos; |
| |
| if (pos == len) { // no URL |
| url = String(); |
| bool ok; |
| delay = refresh.stripWhiteSpace().toDouble(&ok); |
| return ok; |
| } else { |
| bool ok; |
| delay = refresh.left(pos).stripWhiteSpace().toDouble(&ok); |
| if (!ok) |
| return false; |
| |
| skipWhiteSpace(refresh, pos, matcher); |
| if (pos < len && (refresh[pos] == ',' || refresh[pos] == ';')) |
| ++pos; |
| skipWhiteSpace(refresh, pos, matcher); |
| unsigned urlStartPos = pos; |
| if (refresh.find("url", urlStartPos, TextCaseInsensitive) == urlStartPos) { |
| urlStartPos += 3; |
| skipWhiteSpace(refresh, urlStartPos, matcher); |
| if (refresh[urlStartPos] == '=') { |
| ++urlStartPos; |
| skipWhiteSpace(refresh, urlStartPos, matcher); |
| } else { |
| urlStartPos = pos; // e.g. "Refresh: 0; url.html" |
| } |
| } |
| |
| unsigned urlEndPos = len; |
| |
| if (refresh[urlStartPos] == '"' || refresh[urlStartPos] == '\'') { |
| UChar quotationMark = refresh[urlStartPos]; |
| urlStartPos++; |
| while (urlEndPos > urlStartPos) { |
| urlEndPos--; |
| if (refresh[urlEndPos] == quotationMark) |
| break; |
| } |
| |
| // https://bugs.webkit.org/show_bug.cgi?id=27868 |
| // Sometimes there is no closing quote for the end of the URL even though |
| // there was an opening quote. If we looped over the entire alleged URL |
| // string back to the opening quote, just go ahead and use everything |
| // after the opening quote instead. |
| if (urlEndPos == urlStartPos) |
| urlEndPos = len; |
| } |
| |
| url = refresh.substring(urlStartPos, urlEndPos - urlStartPos) |
| .stripWhiteSpace(); |
| return true; |
| } |
| } |
| |
| double parseDate(const String& value) { |
| return parseDateFromNullTerminatedCharacters(value.utf8().data()); |
| } |
| |
| AtomicString extractMIMETypeFromMediaType(const AtomicString& mediaType) { |
| unsigned length = mediaType.length(); |
| |
| unsigned pos = 0; |
| |
| while (pos < length) { |
| UChar c = mediaType[pos]; |
| if (c != '\t' && c != ' ') |
| break; |
| ++pos; |
| } |
| |
| if (pos == length) |
| return mediaType; |
| |
| unsigned typeStart = pos; |
| |
| unsigned typeEnd = pos; |
| while (pos < length) { |
| UChar c = mediaType[pos]; |
| |
| // While RFC 2616 does not allow it, other browsers allow multiple values in |
| // the HTTP media type header field, Content-Type. In such cases, the media |
| // type string passed here may contain the multiple values separated by |
| // commas. For now, this code ignores text after the first comma, which |
| // prevents it from simply failing to parse such types altogether. Later |
| // for better compatibility we could consider using the first or last valid |
| // MIME type instead. |
| // See https://bugs.webkit.org/show_bug.cgi?id=25352 for more discussion. |
| if (c == ',' || c == ';') |
| break; |
| |
| if (c != '\t' && c != ' ') |
| typeEnd = pos + 1; |
| |
| ++pos; |
| } |
| |
| return AtomicString( |
| mediaType.getString().substring(typeStart, typeEnd - typeStart)); |
| } |
| |
| String extractCharsetFromMediaType(const String& mediaType) { |
| unsigned pos, len; |
| findCharsetInMediaType(mediaType, pos, len); |
| return mediaType.substring(pos, len); |
| } |
| |
| void findCharsetInMediaType(const String& mediaType, |
| unsigned& charsetPos, |
| unsigned& charsetLen, |
| unsigned start) { |
| charsetPos = start; |
| charsetLen = 0; |
| |
| size_t pos = start; |
| unsigned length = mediaType.length(); |
| |
| while (pos < length) { |
| pos = mediaType.find("charset", pos, TextCaseInsensitive); |
| if (pos == kNotFound || !pos) { |
| charsetLen = 0; |
| return; |
| } |
| |
| // is what we found a beginning of a word? |
| if (mediaType[pos - 1] > ' ' && mediaType[pos - 1] != ';') { |
| pos += 7; |
| continue; |
| } |
| |
| pos += 7; |
| |
| // skip whitespace |
| while (pos != length && mediaType[pos] <= ' ') |
| ++pos; |
| |
| 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] == '\'')) |
| ++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] != ';') |
| ++endpos; |
| |
| charsetPos = pos; |
| charsetLen = endpos - pos; |
| return; |
| } |
| } |
| |
| ReflectedXSSDisposition parseXSSProtectionHeader(const String& header, |
| String& failureReason, |
| unsigned& failurePosition, |
| String& reportURL) { |
| DEFINE_STATIC_LOCAL(String, failureReasonInvalidToggle, ("expected 0 or 1")); |
| DEFINE_STATIC_LOCAL(String, failureReasonInvalidSeparator, |
| ("expected semicolon")); |
| DEFINE_STATIC_LOCAL(String, failureReasonInvalidEquals, |
| ("expected equals sign")); |
| DEFINE_STATIC_LOCAL(String, failureReasonInvalidMode, |
| ("invalid mode directive")); |
| DEFINE_STATIC_LOCAL(String, failureReasonInvalidReport, |
| ("invalid report directive")); |
| DEFINE_STATIC_LOCAL(String, failureReasonDuplicateMode, |
| ("duplicate mode directive")); |
| DEFINE_STATIC_LOCAL(String, failureReasonDuplicateReport, |
| ("duplicate report directive")); |
| DEFINE_STATIC_LOCAL(String, failureReasonInvalidDirective, |
| ("unrecognized directive")); |
| |
| unsigned pos = 0; |
| |
| if (!skipWhiteSpace(header, pos)) |
| return ReflectedXSSUnset; |
| |
| if (header[pos] == '0') |
| return AllowReflectedXSS; |
| |
| if (header[pos++] != '1') { |
| failureReason = failureReasonInvalidToggle; |
| return ReflectedXSSInvalid; |
| } |
| |
| ReflectedXSSDisposition result = FilterReflectedXSS; |
| bool modeDirectiveSeen = false; |
| bool reportDirectiveSeen = false; |
| |
| while (1) { |
| // At end of previous directive: consume whitespace, semicolon, and |
| // whitespace. |
| if (!skipWhiteSpace(header, pos)) |
| return result; |
| |
| if (header[pos++] != ';') { |
| failureReason = failureReasonInvalidSeparator; |
| failurePosition = pos; |
| return ReflectedXSSInvalid; |
| } |
| |
| if (!skipWhiteSpace(header, pos)) |
| return result; |
| |
| // At start of next directive. |
| if (skipToken(header, pos, "mode")) { |
| if (modeDirectiveSeen) { |
| failureReason = failureReasonDuplicateMode; |
| failurePosition = pos; |
| return ReflectedXSSInvalid; |
| } |
| modeDirectiveSeen = true; |
| if (!skipEquals(header, pos)) { |
| failureReason = failureReasonInvalidEquals; |
| failurePosition = pos; |
| return ReflectedXSSInvalid; |
| } |
| if (!skipToken(header, pos, "block")) { |
| failureReason = failureReasonInvalidMode; |
| failurePosition = pos; |
| return ReflectedXSSInvalid; |
| } |
| result = BlockReflectedXSS; |
| } else if (skipToken(header, pos, "report")) { |
| if (reportDirectiveSeen) { |
| failureReason = failureReasonDuplicateReport; |
| failurePosition = pos; |
| return ReflectedXSSInvalid; |
| } |
| reportDirectiveSeen = true; |
| if (!skipEquals(header, pos)) { |
| failureReason = failureReasonInvalidEquals; |
| failurePosition = pos; |
| return ReflectedXSSInvalid; |
| } |
| size_t startPos = pos; |
| if (!skipValue(header, pos)) { |
| failureReason = failureReasonInvalidReport; |
| failurePosition = pos; |
| return ReflectedXSSInvalid; |
| } |
| reportURL = header.substring(startPos, pos - startPos); |
| failurePosition = |
| startPos; // If later semantic check deems unacceptable. |
| } else { |
| failureReason = failureReasonInvalidDirective; |
| failurePosition = pos; |
| return ReflectedXSSInvalid; |
| } |
| } |
| } |
| |
| ContentTypeOptionsDisposition parseContentTypeOptionsHeader( |
| const String& header) { |
| if (header.stripWhiteSpace().lower() == "nosniff") |
| return ContentTypeOptionsNosniff; |
| return ContentTypeOptionsNone; |
| } |
| |
| XFrameOptionsDisposition parseXFrameOptionsHeader(const String& header) { |
| XFrameOptionsDisposition result = XFrameOptionsInvalid; |
| |
| if (header.isEmpty()) |
| return result; |
| |
| Vector<String> headers; |
| header.split(',', headers); |
| |
| bool hasValue = false; |
| for (size_t i = 0; i < headers.size(); i++) { |
| String currentHeader = headers[i].stripWhiteSpace(); |
| XFrameOptionsDisposition currentValue = XFrameOptionsInvalid; |
| if (equalIgnoringCase(currentHeader, "deny")) |
| currentValue = XFrameOptionsDeny; |
| else if (equalIgnoringCase(currentHeader, "sameorigin")) |
| currentValue = XFrameOptionsSameOrigin; |
| else if (equalIgnoringCase(currentHeader, "allowall")) |
| currentValue = XFrameOptionsAllowAll; |
| |
| if (!hasValue) |
| result = currentValue; |
| else if (result != currentValue) |
| return XFrameOptionsConflict; |
| hasValue = true; |
| } |
| return result; |
| } |
| |
| static bool isCacheHeaderSeparator(UChar c) { |
| // See RFC 2616, Section 2.2 |
| switch (c) { |
| case '(': |
| case ')': |
| case '<': |
| case '>': |
| case '@': |
| case ',': |
| case ';': |
| case ':': |
| case '\\': |
| case '"': |
| case '/': |
| case '[': |
| case ']': |
| case '?': |
| case '=': |
| case '{': |
| case '}': |
| case ' ': |
| case '\t': |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static bool isControlCharacter(UChar c) { |
| return c < ' ' || c == 127; |
| } |
| |
| static inline String trimToNextSeparator(const String& str) { |
| return str.substring(0, str.find(isCacheHeaderSeparator)); |
| } |
| |
| static void parseCacheHeader(const String& header, |
| Vector<std::pair<String, String>>& result) { |
| const String safeHeader = header.removeCharacters(isControlCharacter); |
| unsigned max = safeHeader.length(); |
| for (unsigned pos = 0; pos < max; /* pos incremented in loop */) { |
| size_t nextCommaPosition = safeHeader.find(',', pos); |
| size_t nextEqualSignPosition = safeHeader.find('=', pos); |
| if (nextEqualSignPosition != kNotFound && |
| (nextEqualSignPosition < nextCommaPosition || |
| nextCommaPosition == kNotFound)) { |
| // Get directive name, parse right hand side of equal sign, then add to |
| // map |
| String directive = trimToNextSeparator( |
| safeHeader.substring(pos, nextEqualSignPosition - pos) |
| .stripWhiteSpace()); |
| pos += nextEqualSignPosition - pos + 1; |
| |
| String value = safeHeader.substring(pos, max - pos).stripWhiteSpace(); |
| if (value[0] == '"') { |
| // The value is a quoted string |
| size_t nextDoubleQuotePosition = value.find('"', 1); |
| if (nextDoubleQuotePosition != kNotFound) { |
| // Store the value as a quoted string without quotes |
| result.append(std::pair<String, String>( |
| directive, value.substring(1, nextDoubleQuotePosition - 1) |
| .stripWhiteSpace())); |
| pos += |
| (safeHeader.find('"', pos) - pos) + nextDoubleQuotePosition + 1; |
| // Move past next comma, if there is one |
| size_t nextCommaPosition2 = safeHeader.find(',', pos); |
| if (nextCommaPosition2 != kNotFound) |
| pos += nextCommaPosition2 - pos + 1; |
| else |
| return; // Parse error if there is anything left with no comma |
| } else { |
| // Parse error; just use the rest as the value |
| result.append(std::pair<String, String>( |
| directive, |
| trimToNextSeparator( |
| value.substring(1, value.length() - 1).stripWhiteSpace()))); |
| return; |
| } |
| } else { |
| // The value is a token until the next comma |
| size_t nextCommaPosition2 = value.find(','); |
| if (nextCommaPosition2 != kNotFound) { |
| // The value is delimited by the next comma |
| result.append(std::pair<String, String>( |
| directive, |
| trimToNextSeparator( |
| value.substring(0, nextCommaPosition2).stripWhiteSpace()))); |
| pos += (safeHeader.find(',', pos) - pos) + 1; |
| } else { |
| // The rest is the value; no change to value needed |
| result.append( |
| std::pair<String, String>(directive, trimToNextSeparator(value))); |
| return; |
| } |
| } |
| } else if (nextCommaPosition != kNotFound && |
| (nextCommaPosition < nextEqualSignPosition || |
| nextEqualSignPosition == kNotFound)) { |
| // Add directive to map with empty string as value |
| result.append(std::pair<String, String>( |
| trimToNextSeparator(safeHeader.substring(pos, nextCommaPosition - pos) |
| .stripWhiteSpace()), |
| "")); |
| pos += nextCommaPosition - pos + 1; |
| } else { |
| // Add last directive to map with empty string as value |
| result.append(std::pair<String, String>( |
| trimToNextSeparator( |
| safeHeader.substring(pos, max - pos).stripWhiteSpace()), |
| "")); |
| return; |
| } |
| } |
| } |
| |
| CacheControlHeader parseCacheControlDirectives( |
| const AtomicString& cacheControlValue, |
| const AtomicString& pragmaValue) { |
| CacheControlHeader cacheControlHeader; |
| cacheControlHeader.parsed = true; |
| cacheControlHeader.maxAge = std::numeric_limits<double>::quiet_NaN(); |
| cacheControlHeader.staleWhileRevalidate = |
| std::numeric_limits<double>::quiet_NaN(); |
| |
| DEFINE_STATIC_LOCAL(const AtomicString, noCacheDirective, ("no-cache")); |
| DEFINE_STATIC_LOCAL(const AtomicString, noStoreDirective, ("no-store")); |
| DEFINE_STATIC_LOCAL(const AtomicString, mustRevalidateDirective, |
| ("must-revalidate")); |
| DEFINE_STATIC_LOCAL(const AtomicString, maxAgeDirective, ("max-age")); |
| DEFINE_STATIC_LOCAL(const AtomicString, staleWhileRevalidateDirective, |
| ("stale-while-revalidate")); |
| |
| if (!cacheControlValue.isEmpty()) { |
| Vector<std::pair<String, String>> directives; |
| parseCacheHeader(cacheControlValue, directives); |
| |
| size_t directivesSize = directives.size(); |
| for (size_t i = 0; i < directivesSize; ++i) { |
| // RFC2616 14.9.1: A no-cache directive with a value is only meaningful |
| // for proxy caches. It should be ignored by a browser level cache. |
| if (equalIgnoringCase(directives[i].first, noCacheDirective) && |
| directives[i].second.isEmpty()) { |
| cacheControlHeader.containsNoCache = true; |
| } else if (equalIgnoringCase(directives[i].first, noStoreDirective)) { |
| cacheControlHeader.containsNoStore = true; |
| } else if (equalIgnoringCase(directives[i].first, |
| mustRevalidateDirective)) { |
| cacheControlHeader.containsMustRevalidate = true; |
| } else if (equalIgnoringCase(directives[i].first, maxAgeDirective)) { |
| if (!std::isnan(cacheControlHeader.maxAge)) { |
| // First max-age directive wins if there are multiple ones. |
| continue; |
| } |
| bool ok; |
| double maxAge = directives[i].second.toDouble(&ok); |
| if (ok) |
| cacheControlHeader.maxAge = maxAge; |
| } else if (equalIgnoringCase(directives[i].first, |
| staleWhileRevalidateDirective)) { |
| if (!std::isnan(cacheControlHeader.staleWhileRevalidate)) { |
| // First stale-while-revalidate directive wins if there are multiple |
| // ones. |
| continue; |
| } |
| bool ok; |
| double staleWhileRevalidate = directives[i].second.toDouble(&ok); |
| if (ok) |
| cacheControlHeader.staleWhileRevalidate = staleWhileRevalidate; |
| } |
| } |
| } |
| |
| if (!cacheControlHeader.containsNoCache) { |
| // Handle Pragma: no-cache |
| // This is deprecated and equivalent to Cache-control: no-cache |
| // Don't bother tokenizing the value, it is not important |
| cacheControlHeader.containsNoCache = |
| pragmaValue.lower().contains(noCacheDirective); |
| } |
| return cacheControlHeader; |
| } |
| |
| void parseCommaDelimitedHeader(const String& headerValue, |
| CommaDelimitedHeaderSet& headerSet) { |
| Vector<String> results; |
| headerValue.split(",", results); |
| for (auto& value : results) |
| headerSet.add(value.stripWhiteSpace(isWhitespace)); |
| } |
| |
| bool parseSuboriginHeader(const String& header, |
| Suborigin* suborigin, |
| WTF::Vector<String>& messages) { |
| Vector<String> headers; |
| header.split(',', true, headers); |
| |
| if (headers.size() > 1) |
| messages.append( |
| "Multiple Suborigin headers found. Ignoring all but the first."); |
| |
| Vector<UChar> characters; |
| headers[0].appendTo(characters); |
| |
| const UChar* position = characters.data(); |
| const UChar* end = position + characters.size(); |
| |
| skipWhile<UChar, isASCIISpace>(position, end); |
| |
| String name; |
| position = parseSuboriginName(position, end, name, messages); |
| // For now it is appropriate to simply return false if the name is empty and |
| // act as if the header doesn't exist. If suborigin policy options are created |
| // that can apply to the empty suborigin, than this will have to change. |
| if (!position || name.isEmpty()) |
| return false; |
| |
| suborigin->setName(name); |
| |
| while (position < end) { |
| skipWhile<UChar, isASCIISpace>(position, end); |
| if (position == end) |
| return true; |
| |
| String optionName; |
| position = parseSuboriginPolicyOption(position, end, optionName, messages); |
| |
| if (!position) { |
| suborigin->clear(); |
| return false; |
| } |
| |
| Suborigin::SuboriginPolicyOptions option = |
| getSuboriginPolicyOptionFromString(optionName); |
| if (option == Suborigin::SuboriginPolicyOptions::None) |
| messages.append("Ignoring unknown suborigin policy option " + optionName + |
| "."); |
| else |
| suborigin->addPolicyOption(option); |
| } |
| |
| return true; |
| } |
| |
| bool parseMultipartHeadersFromBody(const char* bytes, |
| size_t size, |
| ResourceResponse* response, |
| size_t* end) { |
| DCHECK(isMainThread()); |
| |
| int headersEndPos = |
| net::HttpUtil::LocateEndOfAdditionalHeaders(bytes, size, 0); |
| |
| if (headersEndPos < 0) |
| return false; |
| |
| *end = headersEndPos; |
| |
| // Eat headers and prepend a status line as is required by |
| // HttpResponseHeaders. |
| std::string headers("HTTP/1.1 200 OK\r\n"); |
| headers.append(bytes, headersEndPos); |
| |
| scoped_refptr<net::HttpResponseHeaders> responseHeaders = |
| new net::HttpResponseHeaders( |
| net::HttpUtil::AssembleRawHeaders(headers.data(), headers.length())); |
| |
| std::string mimeType; |
| responseHeaders->GetMimeType(&mimeType); |
| response->setMimeType(WebString::fromUTF8(mimeType)); |
| |
| std::string charset; |
| responseHeaders->GetCharset(&charset); |
| response->setTextEncodingName(WebString::fromUTF8(charset)); |
| |
| // Copy headers listed in replaceHeaders to the response. |
| for (const AtomicString& header : replaceHeaders()) { |
| std::string value; |
| StringUTF8Adaptor adaptor(header); |
| base::StringPiece headerStringPiece(adaptor.asStringPiece()); |
| size_t iterator = 0; |
| |
| response->clearHTTPHeaderField(header); |
| while (responseHeaders->EnumerateHeader(&iterator, headerStringPiece, |
| &value)) { |
| response->addHTTPHeaderField(header, WebString::fromLatin1(value)); |
| } |
| } |
| return true; |
| } |
| |
| // See https://tools.ietf.org/html/draft-ietf-httpbis-jfv-01, Section 4. |
| std::unique_ptr<JSONArray> parseJSONHeader(const String& header) { |
| StringBuilder sb; |
| sb.append("["); |
| sb.append(header); |
| sb.append("]"); |
| std::unique_ptr<JSONValue> headerValue = parseJSON(sb.toString()); |
| return JSONArray::cast(std::move(headerValue)); |
| } |
| |
| } // namespace blink |