1/*
2 * Copyright 2015 The Chromium Authors. All rights reserved.
3 * Copyright (C) 2016 Akamai Technologies Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "LinkHeader.h"
29
30#include "ParsingUtilities.h"
31
32namespace WebCore {
33
34// LWSP definition in https://www.ietf.org/rfc/rfc0822.txt
35template <typename CharacterType>
36static bool isSpaceOrTab(CharacterType chr)
37{
38 return (chr == ' ') || (chr == '\t');
39}
40
41template <typename CharacterType>
42static bool isNotURLTerminatingChar(CharacterType chr)
43{
44 return (chr != '>');
45}
46
47template <typename CharacterType>
48static bool isValidParameterNameChar(CharacterType chr)
49{
50 // A separator, CTL or '%', '*' or '\'' means the char is not valid.
51 // Definition as attr-char at https://tools.ietf.org/html/rfc5987
52 // CTL and separators are defined in https://tools.ietf.org/html/rfc2616#section-2.2
53 // Valid chars are alpha-numeric and any of !#$&+-.^_`|~"
54 if ((chr >= '^' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || (chr >= '0' && chr <= '9') || (chr >= '!' && chr <= '$') || chr == '&' || chr == '+' || chr == '-' || chr == '.')
55 return true;
56 return false;
57}
58
59template <typename CharacterType>
60static bool isParameterValueEnd(CharacterType chr)
61{
62 return chr == ';' || chr == ',';
63}
64
65template <typename CharacterType>
66static bool isParameterValueChar(CharacterType chr)
67{
68 return !isSpaceOrTab(chr) && !isParameterValueEnd(chr);
69}
70
71// Verify that the parameter is a link-extension which according to spec doesn't have to have a value.
72static bool isExtensionParameter(LinkHeader::LinkParameterName name)
73{
74 return name >= LinkHeader::LinkParameterUnknown;
75}
76
77// Before:
78//
79// <cat.jpg>; rel=preload
80// ^ ^
81// position end
82//
83// After (if successful: otherwise the method returns false)
84//
85// <cat.jpg>; rel=preload
86// ^ ^
87// position end
88template <typename CharacterType>
89static Optional<String> findURLBoundaries(CharacterType*& position, CharacterType* const end)
90{
91 ASSERT(position <= end);
92 skipWhile<CharacterType, isSpaceOrTab>(position, end);
93 if (!skipExactly<CharacterType>(position, end, '<'))
94 return WTF::nullopt;
95 skipWhile<CharacterType, isSpaceOrTab>(position, end);
96
97 CharacterType* urlStart = position;
98 skipWhile<CharacterType, isNotURLTerminatingChar>(position, end);
99 CharacterType* urlEnd = position;
100 skipUntil<CharacterType>(position, end, '>');
101 if (!skipExactly<CharacterType>(position, end, '>'))
102 return WTF::nullopt;
103
104 return String(urlStart, urlEnd - urlStart);
105}
106
107template <typename CharacterType>
108static bool invalidParameterDelimiter(CharacterType*& position, CharacterType* const end)
109{
110 ASSERT(position <= end);
111 return (!skipExactly<CharacterType>(position, end, ';') && (position < end) && (*position != ','));
112}
113
114template <typename CharacterType>
115static bool validFieldEnd(CharacterType*& position, CharacterType* const end)
116{
117 ASSERT(position <= end);
118 return (position == end || *position == ',');
119}
120
121// Before:
122//
123// <cat.jpg>; rel=preload
124// ^ ^
125// position end
126//
127// After (if successful: otherwise the method returns false, and modifies the isValid boolean accordingly)
128//
129// <cat.jpg>; rel=preload
130// ^ ^
131// position end
132template <typename CharacterType>
133static bool parseParameterDelimiter(CharacterType*& position, CharacterType* const end, bool& isValid)
134{
135 ASSERT(position <= end);
136 isValid = true;
137 skipWhile<CharacterType, isSpaceOrTab>(position, end);
138 if (invalidParameterDelimiter(position, end)) {
139 isValid = false;
140 return false;
141 }
142 skipWhile<CharacterType, isSpaceOrTab>(position, end);
143 if (validFieldEnd(position, end))
144 return false;
145 return true;
146}
147
148static LinkHeader::LinkParameterName paramterNameFromString(String name)
149{
150 if (equalLettersIgnoringASCIICase(name, "rel"))
151 return LinkHeader::LinkParameterRel;
152 if (equalLettersIgnoringASCIICase(name, "anchor"))
153 return LinkHeader::LinkParameterAnchor;
154 if (equalLettersIgnoringASCIICase(name, "crossorigin"))
155 return LinkHeader::LinkParameterCrossOrigin;
156 if (equalLettersIgnoringASCIICase(name, "title"))
157 return LinkHeader::LinkParameterTitle;
158 if (equalLettersIgnoringASCIICase(name, "media"))
159 return LinkHeader::LinkParameterMedia;
160 if (equalLettersIgnoringASCIICase(name, "type"))
161 return LinkHeader::LinkParameterType;
162 if (equalLettersIgnoringASCIICase(name, "rev"))
163 return LinkHeader::LinkParameterRev;
164 if (equalLettersIgnoringASCIICase(name, "hreflang"))
165 return LinkHeader::LinkParameterHreflang;
166 if (equalLettersIgnoringASCIICase(name, "as"))
167 return LinkHeader::LinkParameterAs;
168 if (equalLettersIgnoringASCIICase(name, "imagesrcset"))
169 return LinkHeader::LinkParameterImageSrcSet;
170 if (equalLettersIgnoringASCIICase(name, "imagesizes"))
171 return LinkHeader::LinkParameterImageSizes;
172 return LinkHeader::LinkParameterUnknown;
173}
174
175// Before:
176//
177// <cat.jpg>; rel=preload
178// ^ ^
179// position end
180//
181// After (if successful: otherwise the method returns false)
182//
183// <cat.jpg>; rel=preload
184// ^ ^
185// position end
186template <typename CharacterType>
187static bool parseParameterName(CharacterType*& position, CharacterType* const end, LinkHeader::LinkParameterName& name)
188{
189 ASSERT(position <= end);
190 CharacterType* nameStart = position;
191 skipWhile<CharacterType, isValidParameterNameChar>(position, end);
192 CharacterType* nameEnd = position;
193 skipWhile<CharacterType, isSpaceOrTab>(position, end);
194 bool hasEqual = skipExactly<CharacterType>(position, end, '=');
195 skipWhile<CharacterType, isSpaceOrTab>(position, end);
196 name = paramterNameFromString(String(nameStart, nameEnd - nameStart));
197 if (hasEqual)
198 return true;
199 bool validParameterValueEnd = (position == end) || isParameterValueEnd(*position);
200 return validParameterValueEnd && isExtensionParameter(name);
201}
202
203// Before:
204//
205// <cat.jpg>; rel="preload"; type="image/jpeg";
206// ^ ^
207// position end
208//
209// After (if the parameter starts with a quote, otherwise the method returns false)
210//
211// <cat.jpg>; rel="preload"; type="image/jpeg";
212// ^ ^
213// position end
214template <typename CharacterType>
215static bool skipQuotesIfNeeded(CharacterType*& position, CharacterType* const end, bool& completeQuotes)
216{
217 ASSERT(position <= end);
218 unsigned char quote;
219 if (skipExactly<CharacterType>(position, end, '\''))
220 quote = '\'';
221 else if (skipExactly<CharacterType>(position, end, '"'))
222 quote = '"';
223 else
224 return false;
225
226 while (!completeQuotes && position < end) {
227 skipUntil(position, end, static_cast<CharacterType>(quote));
228 if (*(position - 1) != '\\')
229 completeQuotes = true;
230 completeQuotes = skipExactly(position, end, static_cast<CharacterType>(quote)) && completeQuotes;
231 }
232 return true;
233}
234
235// Before:
236//
237// <cat.jpg>; rel=preload; foo=bar
238// ^ ^
239// position end
240//
241// After (if successful: otherwise the method returns false)
242//
243// <cat.jpg>; rel=preload; foo=bar
244// ^ ^
245// position end
246template <typename CharacterType>
247static bool parseParameterValue(CharacterType*& position, CharacterType* const end, String& value)
248{
249 ASSERT(position <= end);
250 CharacterType* valueStart = position;
251 CharacterType* valueEnd = position;
252 bool completeQuotes = false;
253 bool hasQuotes = skipQuotesIfNeeded(position, end, completeQuotes);
254 if (!hasQuotes)
255 skipWhile<CharacterType, isParameterValueChar>(position, end);
256 valueEnd = position;
257 skipWhile<CharacterType, isSpaceOrTab>(position, end);
258 if ((!completeQuotes && valueStart == valueEnd) || (position != end && !isParameterValueEnd(*position))) {
259 value = emptyString();
260 return false;
261 }
262 if (hasQuotes)
263 ++valueStart;
264 if (completeQuotes)
265 --valueEnd;
266 ASSERT(valueEnd >= valueStart);
267 value = String(valueStart, valueEnd - valueStart);
268 return !hasQuotes || completeQuotes;
269}
270
271void LinkHeader::setValue(LinkParameterName name, String&& value)
272{
273 switch (name) {
274 case LinkParameterRel:
275 if (!m_rel)
276 m_rel = WTFMove(value);
277 break;
278 case LinkParameterAnchor:
279 m_isValid = false;
280 break;
281 case LinkParameterCrossOrigin:
282 m_crossOrigin = WTFMove(value);
283 break;
284 case LinkParameterAs:
285 m_as = WTFMove(value);
286 break;
287 case LinkParameterType:
288 m_mimeType = WTFMove(value);
289 break;
290 case LinkParameterMedia:
291 m_media = WTFMove(value);
292 break;
293 case LinkParameterImageSrcSet:
294 m_imageSrcSet = WTFMove(value);
295 break;
296 case LinkParameterImageSizes:
297 m_imageSizes = WTFMove(value);
298 break;
299 case LinkParameterTitle:
300 case LinkParameterRev:
301 case LinkParameterHreflang:
302 case LinkParameterUnknown:
303 // These parameters are not yet supported, so they are currently ignored.
304 break;
305 }
306 // FIXME: Add support for more header parameters as neccessary.
307}
308
309template <typename CharacterType>
310static void findNextHeader(CharacterType*& position, CharacterType* const end)
311{
312 ASSERT(position <= end);
313 skipUntil<CharacterType>(position, end, ',');
314 skipExactly<CharacterType>(position, end, ',');
315}
316
317template <typename CharacterType>
318LinkHeader::LinkHeader(CharacterType*& position, CharacterType* const end)
319{
320 ASSERT(position <= end);
321 auto urlResult = findURLBoundaries(position, end);
322 if (urlResult == WTF::nullopt) {
323 m_isValid = false;
324 findNextHeader(position, end);
325 return;
326 }
327 m_url = urlResult.value();
328
329 while (m_isValid && position < end) {
330 if (!parseParameterDelimiter(position, end, m_isValid)) {
331 findNextHeader(position, end);
332 return;
333 }
334
335 LinkParameterName parameterName;
336 if (!parseParameterName(position, end, parameterName)) {
337 findNextHeader(position, end);
338 m_isValid = false;
339 return;
340 }
341
342 String parameterValue;
343 if (!parseParameterValue(position, end, parameterValue) && !isExtensionParameter(parameterName)) {
344 findNextHeader(position, end);
345 m_isValid = false;
346 return;
347 }
348
349 setValue(parameterName, WTFMove(parameterValue));
350 }
351 findNextHeader(position, end);
352}
353
354LinkHeaderSet::LinkHeaderSet(const String& header)
355{
356 if (header.isNull())
357 return;
358
359 if (header.is8Bit())
360 init(header.characters8(), header.length());
361 else
362 init(header.characters16(), header.length());
363}
364
365template <typename CharacterType>
366void LinkHeaderSet::init(CharacterType* headerValue, size_t length)
367{
368 CharacterType* position = headerValue;
369 CharacterType* const end = headerValue + length;
370 while (position < end)
371 m_headerSet.append(LinkHeader(position, end));
372}
373
374} // namespace WebCore
375
376