1 | /*
|
---|
2 | * Copyright (C) 2017 Apple Inc. All rights reserved.
|
---|
3 | *
|
---|
4 | * Redistribution and use in source and binary forms, with or without
|
---|
5 | * modification, are permitted provided that the following conditions
|
---|
6 | * are met:
|
---|
7 | * 1. Redistributions of source code must retain the above copyright
|
---|
8 | * notice, this list of conditions and the following disclaimer.
|
---|
9 | * 2. Redistributions in binary form must reproduce the above copyright
|
---|
10 | * notice, this list of conditions and the following disclaimer in the
|
---|
11 | * documentation and/or other materials provided with the distribution.
|
---|
12 | *
|
---|
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
|
---|
14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
---|
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
---|
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
|
---|
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
---|
18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
---|
19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
---|
20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
---|
21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
---|
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
---|
23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
---|
24 | */
|
---|
25 |
|
---|
26 | #include "config.h"
|
---|
27 | #include "HTTPHeaderField.h"
|
---|
28 |
|
---|
29 | namespace WebCore {
|
---|
30 |
|
---|
31 | namespace RFC7230 {
|
---|
32 |
|
---|
33 | bool isTokenCharacter(UChar c)
|
---|
34 | {
|
---|
35 | return isASCIIAlpha(c) || isASCIIDigit(c)
|
---|
36 | || c == '!' || c == '#' || c == '$'
|
---|
37 | || c == '%' || c == '&' || c == '\''
|
---|
38 | || c == '*' || c == '+' || c == '-'
|
---|
39 | || c == '.' || c == '^' || c == '_'
|
---|
40 | || c == '`' || c == '|' || c == '~';
|
---|
41 | }
|
---|
42 |
|
---|
43 | bool isDelimiter(UChar c)
|
---|
44 | {
|
---|
45 | return c == '(' || c == ')' || c == ','
|
---|
46 | || c == '/' || c == ':' || c == ';'
|
---|
47 | || c == '<' || c == '=' || c == '>'
|
---|
48 | || c == '?' || c == '@' || c == '['
|
---|
49 | || c == '\\' || c == ']' || c == '{'
|
---|
50 | || c == '}' || c == '"';
|
---|
51 | }
|
---|
52 |
|
---|
53 | static bool isVisibleCharacter(UChar c)
|
---|
54 | {
|
---|
55 | return isTokenCharacter(c) || isDelimiter(c);
|
---|
56 | }
|
---|
57 |
|
---|
58 | bool isWhitespace(UChar c)
|
---|
59 | {
|
---|
60 | return c == ' ' || c == '\t';
|
---|
61 | }
|
---|
62 |
|
---|
63 | template<size_t min, size_t max>
|
---|
64 | static bool isInRange(UChar c)
|
---|
65 | {
|
---|
66 | return c >= min && c <= max;
|
---|
67 | }
|
---|
68 |
|
---|
69 | static bool isOBSText(UChar c)
|
---|
70 | {
|
---|
71 | return isInRange<0x80, 0xFF>(c);
|
---|
72 | }
|
---|
73 |
|
---|
74 | static bool isQuotedTextCharacter(UChar c)
|
---|
75 | {
|
---|
76 | return isWhitespace(c)
|
---|
77 | || c == 0x21
|
---|
78 | || isInRange<0x23, 0x5B>(c)
|
---|
79 | || isInRange<0x5D, 0x7E>(c)
|
---|
80 | || isOBSText(c);
|
---|
81 | }
|
---|
82 |
|
---|
83 | bool isQuotedPairSecondOctet(UChar c)
|
---|
84 | {
|
---|
85 | return isWhitespace(c)
|
---|
86 | || isVisibleCharacter(c)
|
---|
87 | || isOBSText(c);
|
---|
88 | }
|
---|
89 |
|
---|
90 | bool isCommentText(UChar c)
|
---|
91 | {
|
---|
92 | return isWhitespace(c)
|
---|
93 | || isInRange<0x21, 0x27>(c)
|
---|
94 | || isInRange<0x2A, 0x5B>(c)
|
---|
95 | || isInRange<0x5D, 0x7E>(c)
|
---|
96 | || isOBSText(c);
|
---|
97 | }
|
---|
98 |
|
---|
99 | static bool isValidName(StringView name)
|
---|
100 | {
|
---|
101 | if (!name.length())
|
---|
102 | return false;
|
---|
103 | for (size_t i = 0; i < name.length(); ++i) {
|
---|
104 | if (!isTokenCharacter(name[i]))
|
---|
105 | return false;
|
---|
106 | }
|
---|
107 | return true;
|
---|
108 | }
|
---|
109 |
|
---|
110 | static bool isValidValue(StringView value)
|
---|
111 | {
|
---|
112 | enum class State {
|
---|
113 | OptionalWhitespace,
|
---|
114 | Token,
|
---|
115 | QuotedString,
|
---|
116 | Comment,
|
---|
117 | };
|
---|
118 | State state = State::OptionalWhitespace;
|
---|
119 | size_t commentDepth = 0;
|
---|
120 | bool hadNonWhitespace = false;
|
---|
121 |
|
---|
122 | for (size_t i = 0; i < value.length(); ++i) {
|
---|
123 | UChar c = value[i];
|
---|
124 | switch (state) {
|
---|
125 | case State::OptionalWhitespace:
|
---|
126 | if (isWhitespace(c))
|
---|
127 | continue;
|
---|
128 | hadNonWhitespace = true;
|
---|
129 | if (isTokenCharacter(c)) {
|
---|
130 | state = State::Token;
|
---|
131 | continue;
|
---|
132 | }
|
---|
133 | if (c == '"') {
|
---|
134 | state = State::QuotedString;
|
---|
135 | continue;
|
---|
136 | }
|
---|
137 | if (c == '(') {
|
---|
138 | ASSERT(!commentDepth);
|
---|
139 | ++commentDepth;
|
---|
140 | state = State::Comment;
|
---|
141 | continue;
|
---|
142 | }
|
---|
143 | return false;
|
---|
144 |
|
---|
145 | case State::Token:
|
---|
146 | if (isTokenCharacter(c))
|
---|
147 | continue;
|
---|
148 | state = State::OptionalWhitespace;
|
---|
149 | continue;
|
---|
150 | case State::QuotedString:
|
---|
151 | if (c == '"') {
|
---|
152 | state = State::OptionalWhitespace;
|
---|
153 | continue;
|
---|
154 | }
|
---|
155 | if (c == '\\') {
|
---|
156 | ++i;
|
---|
157 | if (i == value.length())
|
---|
158 | return false;
|
---|
159 | if (!isQuotedPairSecondOctet(value[i]))
|
---|
160 | return false;
|
---|
161 | continue;
|
---|
162 | }
|
---|
163 | if (!isQuotedTextCharacter(c))
|
---|
164 | return false;
|
---|
165 | continue;
|
---|
166 | case State::Comment:
|
---|
167 | if (c == '(') {
|
---|
168 | ++commentDepth;
|
---|
169 | continue;
|
---|
170 | }
|
---|
171 | if (c == ')') {
|
---|
172 | --commentDepth;
|
---|
173 | if (!commentDepth)
|
---|
174 | state = State::OptionalWhitespace;
|
---|
175 | continue;
|
---|
176 | }
|
---|
177 | if (c == '\\') {
|
---|
178 | ++i;
|
---|
179 | if (i == value.length())
|
---|
180 | return false;
|
---|
181 | if (!isQuotedPairSecondOctet(value[i]))
|
---|
182 | return false;
|
---|
183 | continue;
|
---|
184 | }
|
---|
185 | if (!isCommentText(c))
|
---|
186 | return false;
|
---|
187 | continue;
|
---|
188 | }
|
---|
189 | }
|
---|
190 |
|
---|
191 | switch (state) {
|
---|
192 | case State::OptionalWhitespace:
|
---|
193 | case State::Token:
|
---|
194 | return hadNonWhitespace;
|
---|
195 | case State::QuotedString:
|
---|
196 | case State::Comment:
|
---|
197 | // Unclosed comments or quotes are invalid values.
|
---|
198 | break;
|
---|
199 | }
|
---|
200 | return false;
|
---|
201 | }
|
---|
202 |
|
---|
203 | } // namespace RFC7230
|
---|
204 |
|
---|
205 | std::optional<HTTPHeaderField> HTTPHeaderField::create(String&& unparsedName, String&& unparsedValue)
|
---|
206 | {
|
---|
207 | StringView strippedName = StringView(unparsedName).stripLeadingAndTrailingMatchedCharacters(RFC7230::isWhitespace);
|
---|
208 | StringView strippedValue = StringView(unparsedValue).stripLeadingAndTrailingMatchedCharacters(RFC7230::isWhitespace);
|
---|
209 | if (!RFC7230::isValidName(strippedName) || !RFC7230::isValidValue(strippedValue))
|
---|
210 | return std::nullopt;
|
---|
211 |
|
---|
212 | String name = strippedName.length() == unparsedName.length() ? WTFMove(unparsedName) : strippedName.toString();
|
---|
213 | String value = strippedValue.length() == unparsedValue.length() ? WTFMove(unparsedValue) : strippedValue.toString();
|
---|
214 | return {{ WTFMove(name), WTFMove(value) }};
|
---|
215 | }
|
---|
216 |
|
---|
217 | }
|
---|