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
29namespace WebCore {
30
31namespace RFC7230 {
32
33bool 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
43static 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
53static bool isVisibleCharacter(UChar c)
54{
55 return isTokenCharacter(c) || isDelimiter(c);
56}
57
58bool isWhitespace(UChar c)
59{
60 return c == ' ' || c == '\t';
61}
62
63template<size_t min, size_t max>
64static bool isInRange(UChar c)
65{
66 return c >= min && c <= max;
67}
68
69static bool isOBSText(UChar c)
70{
71 return isInRange<0x80, 0xFF>(c);
72}
73
74static 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
83static bool isQuotedPairSecondOctet(UChar c)
84{
85 return isWhitespace(c)
86 || isVisibleCharacter(c)
87 || isOBSText(c);
88}
89
90static 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
99static 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
110static 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
205Optional<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 WTF::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}
218