| /* |
| * Copyright (C) 2017 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. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 INC. OR |
| * 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 "config.h" |
| #include "HTTPHeaderField.h" |
| |
| namespace WebCore { |
| |
| namespace RFC7230 { |
| |
| bool isTokenCharacter(UChar c) |
| { |
| return isASCIIAlpha(c) || isASCIIDigit(c) |
| || c == '!' || c == '#' || c == '$' |
| || c == '%' || c == '&' || c == '\'' |
| || c == '*' || c == '+' || c == '-' |
| || c == '.' || c == '^' || c == '_' |
| || c == '`' || c == '|' || c == '~'; |
| } |
| |
| static bool isDelimiter(UChar c) |
| { |
| return c == '(' || c == ')' || c == ',' |
| || c == '/' || c == ':' || c == ';' |
| || c == '<' || c == '=' || c == '>' |
| || c == '?' || c == '@' || c == '[' |
| || c == '\\' || c == ']' || c == '{' |
| || c == '}' || c == '"'; |
| } |
| |
| static bool isVisibleCharacter(UChar c) |
| { |
| return isTokenCharacter(c) || isDelimiter(c); |
| } |
| |
| bool isWhitespace(UChar c) |
| { |
| return c == ' ' || c == '\t'; |
| } |
| |
| template<size_t min, size_t max> |
| static bool isInRange(UChar c) |
| { |
| return c >= min && c <= max; |
| } |
| |
| static bool isOBSText(UChar c) |
| { |
| return isInRange<0x80, 0xFF>(c); |
| } |
| |
| static bool isQuotedTextCharacter(UChar c) |
| { |
| return isWhitespace(c) |
| || c == 0x21 |
| || isInRange<0x23, 0x5B>(c) |
| || isInRange<0x5D, 0x7E>(c) |
| || isOBSText(c); |
| } |
| |
| static bool isQuotedPairSecondOctet(UChar c) |
| { |
| return isWhitespace(c) |
| || isVisibleCharacter(c) |
| || isOBSText(c); |
| } |
| |
| static bool isCommentText(UChar c) |
| { |
| return isWhitespace(c) |
| || isInRange<0x21, 0x27>(c) |
| || isInRange<0x2A, 0x5B>(c) |
| || isInRange<0x5D, 0x7E>(c) |
| || isOBSText(c); |
| } |
| |
| static bool isValidName(StringView name) |
| { |
| if (!name.length()) |
| return false; |
| for (size_t i = 0; i < name.length(); ++i) { |
| if (!isTokenCharacter(name[i])) |
| return false; |
| } |
| return true; |
| } |
| |
| static bool isValidValue(StringView value) |
| { |
| enum class State { |
| OptionalWhitespace, |
| Token, |
| QuotedString, |
| Comment, |
| }; |
| State state = State::OptionalWhitespace; |
| size_t commentDepth = 0; |
| bool hadNonWhitespace = false; |
| |
| for (size_t i = 0; i < value.length(); ++i) { |
| UChar c = value[i]; |
| switch (state) { |
| case State::OptionalWhitespace: |
| if (isWhitespace(c)) |
| continue; |
| hadNonWhitespace = true; |
| if (isTokenCharacter(c)) { |
| state = State::Token; |
| continue; |
| } |
| if (c == '"') { |
| state = State::QuotedString; |
| continue; |
| } |
| if (c == '(') { |
| ASSERT(!commentDepth); |
| ++commentDepth; |
| state = State::Comment; |
| continue; |
| } |
| return false; |
| |
| case State::Token: |
| if (isTokenCharacter(c)) |
| continue; |
| state = State::OptionalWhitespace; |
| continue; |
| case State::QuotedString: |
| if (c == '"') { |
| state = State::OptionalWhitespace; |
| continue; |
| } |
| if (c == '\\') { |
| ++i; |
| if (i == value.length()) |
| return false; |
| if (!isQuotedPairSecondOctet(value[i])) |
| return false; |
| continue; |
| } |
| if (!isQuotedTextCharacter(c)) |
| return false; |
| continue; |
| case State::Comment: |
| if (c == '(') { |
| ++commentDepth; |
| continue; |
| } |
| if (c == ')') { |
| --commentDepth; |
| if (!commentDepth) |
| state = State::OptionalWhitespace; |
| continue; |
| } |
| if (c == '\\') { |
| ++i; |
| if (i == value.length()) |
| return false; |
| if (!isQuotedPairSecondOctet(value[i])) |
| return false; |
| continue; |
| } |
| if (!isCommentText(c)) |
| return false; |
| continue; |
| } |
| } |
| |
| switch (state) { |
| case State::OptionalWhitespace: |
| case State::Token: |
| return hadNonWhitespace; |
| case State::QuotedString: |
| case State::Comment: |
| // Unclosed comments or quotes are invalid values. |
| break; |
| } |
| return false; |
| } |
| |
| } // namespace RFC7230 |
| |
| Optional<HTTPHeaderField> HTTPHeaderField::create(String&& unparsedName, String&& unparsedValue) |
| { |
| StringView strippedName = StringView(unparsedName).stripLeadingAndTrailingMatchedCharacters(RFC7230::isWhitespace); |
| StringView strippedValue = StringView(unparsedValue).stripLeadingAndTrailingMatchedCharacters(RFC7230::isWhitespace); |
| if (!RFC7230::isValidName(strippedName) || !RFC7230::isValidValue(strippedValue)) |
| return WTF::nullopt; |
| |
| String name = strippedName.length() == unparsedName.length() ? WTFMove(unparsedName) : strippedName.toString(); |
| String value = strippedValue.length() == unparsedValue.length() ? WTFMove(unparsedValue) : strippedValue.toString(); |
| return {{ WTFMove(name), WTFMove(value) }}; |
| } |
| |
| } |