source: webkit/trunk/Source/WebCore/loader/LinkHeader.cpp

Last change on this file was 294819, checked in by Patrick Griffis, 3 years ago

Add support for Link nonces
https://bugs.webkit.org/show_bug.cgi?id=240817

This reads the nonce from link elements and Link headers.

This was implemented by Chromium in 2017 to be consistent with the HTMLPreloader:
https://chromium-review.googlesource.com/c/chromium/src/+/676769/

Reviewed by Kate Cheney.

  • LayoutTests/imported/w3c/web-platform-tests/preload/link-header-preload-nonce-expected.txt:
  • LayoutTests/imported/w3c/web-platform-tests/preload/link-header-preload-nonce.html:

These test changes were already upstream: https://github.com/web-platform-tests/wpt/commit/306dc506adba97ca84ada67bdab6227dba65bbcb

  • Source/WebCore/html/HTMLLinkElement.cpp:

(WebCore::HTMLLinkElement::process):

  • Source/WebCore/loader/LinkHeader.cpp:

(WebCore::paramterNameFromString):
(WebCore::LinkHeader::setValue):

  • Source/WebCore/loader/LinkHeader.h:

(WebCore::LinkHeader::nonce const):

  • Source/WebCore/loader/LinkLoader.cpp:

(WebCore::LinkLoader::loadLinksFromHeader):
(WebCore::LinkLoader::preloadIfNeeded):
(WebCore::LinkLoader::prefetchIfNeeded):

  • Source/WebCore/loader/LinkLoader.h:

Canonical link: https://commits.webkit.org/250972@main

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