blob: eb7f7fc568672a9468beb8962e1af8a6e66a0585 [file] [log] [blame]
/*
* Copyright 2015 The Chromium Authors. All rights reserved.
* Copyright (C) 2016 Akamai Technologies Inc. All rights reserved.
* Copyright (C) 2020 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 THE COPYRIGHT
* OWNER 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 "LinkHeader.h"
#include "ParsingUtilities.h"
namespace WebCore {
// LWSP definition in https://www.ietf.org/rfc/rfc0822.txt
template<typename CharacterType> static bool isSpaceOrTab(CharacterType character)
{
return character == ' ' || character == '\t';
}
template<typename CharacterType> static bool isNotURLTerminatingChar(CharacterType character)
{
return character != '>';
}
template<typename CharacterType> static bool isValidParameterNameChar(CharacterType character)
{
// A separator, CTL or '%', '*' or '\'' means the char is not valid.
// Definition as attr-char at https://tools.ietf.org/html/rfc5987
// CTL and separators are defined in https://tools.ietf.org/html/rfc2616#section-2.2
// Valid chars are alpha-numeric and any of !#$&+-.^_`|~"
if ((character >= '^' && character <= 'z') || (character >= 'A' && character <= 'Z') || (character >= '0' && character <= '9') || (character >= '!' && character <= '$') || character == '&' || character == '+' || character == '-' || character == '.')
return true;
return false;
}
template<typename CharacterType> static bool isParameterValueEnd(CharacterType character)
{
return character == ';' || character == ',';
}
template<typename CharacterType> static bool isParameterValueChar(CharacterType character)
{
return !isSpaceOrTab(character) && !isParameterValueEnd(character);
}
// Verify that the parameter is a link-extension which according to spec doesn't have to have a value.
static bool isExtensionParameter(LinkHeader::LinkParameterName name)
{
return name >= LinkHeader::LinkParameterUnknown;
}
// Before:
//
// <cat.jpg>; rel=preload
// ^ ^
// position end
//
// After (if successful: otherwise the method returns false)
//
// <cat.jpg>; rel=preload
// ^ ^
// position end
template<typename CharacterType> static std::optional<String> findURLBoundaries(StringParsingBuffer<CharacterType>& buffer)
{
skipWhile<isSpaceOrTab>(buffer);
if (!skipExactly(buffer, '<'))
return std::nullopt;
skipWhile<isSpaceOrTab>(buffer);
auto urlStart = buffer.position();
skipWhile<isNotURLTerminatingChar>(buffer);
auto urlEnd = buffer.position();
skipUntil(buffer, '>');
if (!skipExactly(buffer, '>'))
return std::nullopt;
return String(urlStart, urlEnd - urlStart);
}
template<typename CharacterType> static bool invalidParameterDelimiter(StringParsingBuffer<CharacterType>& buffer)
{
return !skipExactly(buffer, ';') && buffer.hasCharactersRemaining() && *buffer != ',';
}
template<typename CharacterType> static bool validFieldEnd(StringParsingBuffer<CharacterType>& buffer)
{
return buffer.atEnd() || *buffer == ',';
}
// Before:
//
// <cat.jpg>; rel=preload
// ^ ^
// position end
//
// After (if successful: otherwise the method returns false, and modifies the isValid boolean accordingly)
//
// <cat.jpg>; rel=preload
// ^ ^
// position end
template<typename CharacterType> static bool parseParameterDelimiter(StringParsingBuffer<CharacterType>& buffer, bool& isValid)
{
isValid = true;
skipWhile<isSpaceOrTab>(buffer);
if (invalidParameterDelimiter(buffer)) {
isValid = false;
return false;
}
skipWhile<isSpaceOrTab>(buffer);
if (validFieldEnd(buffer))
return false;
return true;
}
static LinkHeader::LinkParameterName paramterNameFromString(StringView name)
{
if (equalLettersIgnoringASCIICase(name, "rel"_s))
return LinkHeader::LinkParameterRel;
if (equalLettersIgnoringASCIICase(name, "anchor"_s))
return LinkHeader::LinkParameterAnchor;
if (equalLettersIgnoringASCIICase(name, "crossorigin"_s))
return LinkHeader::LinkParameterCrossOrigin;
if (equalLettersIgnoringASCIICase(name, "title"_s))
return LinkHeader::LinkParameterTitle;
if (equalLettersIgnoringASCIICase(name, "media"_s))
return LinkHeader::LinkParameterMedia;
if (equalLettersIgnoringASCIICase(name, "type"_s))
return LinkHeader::LinkParameterType;
if (equalLettersIgnoringASCIICase(name, "rev"_s))
return LinkHeader::LinkParameterRev;
if (equalLettersIgnoringASCIICase(name, "hreflang"_s))
return LinkHeader::LinkParameterHreflang;
if (equalLettersIgnoringASCIICase(name, "as"_s))
return LinkHeader::LinkParameterAs;
if (equalLettersIgnoringASCIICase(name, "imagesrcset"_s))
return LinkHeader::LinkParameterImageSrcSet;
if (equalLettersIgnoringASCIICase(name, "imagesizes"_s))
return LinkHeader::LinkParameterImageSizes;
if (equalLettersIgnoringASCIICase(name, "nonce"_s))
return LinkHeader::LinkParameterNonce;
return LinkHeader::LinkParameterUnknown;
}
// Before:
//
// <cat.jpg>; rel=preload
// ^ ^
// position end
//
// After (if successful: otherwise the method returns false)
//
// <cat.jpg>; rel=preload
// ^ ^
// position end
template<typename CharacterType> static std::optional<LinkHeader::LinkParameterName> parseParameterName(StringParsingBuffer<CharacterType>& buffer)
{
auto nameStart = buffer.position();
skipWhile<isValidParameterNameChar>(buffer);
auto nameEnd = buffer.position();
skipWhile<isSpaceOrTab>(buffer);
bool hasEqual = skipExactly(buffer, '=');
skipWhile<isSpaceOrTab>(buffer);
auto name = paramterNameFromString(StringView { nameStart, static_cast<unsigned>(nameEnd - nameStart) });
if (hasEqual)
return name;
bool validParameterValueEnd = buffer.atEnd() || isParameterValueEnd(*buffer);
if (validParameterValueEnd && isExtensionParameter(name))
return name;
return std::nullopt;
}
// Before:
//
// <cat.jpg>; rel="preload"; type="image/jpeg";
// ^ ^
// position end
//
// After (if the parameter starts with a quote, otherwise the method returns false)
//
// <cat.jpg>; rel="preload"; type="image/jpeg";
// ^ ^
// position end
template<typename CharacterType> static bool skipQuotesIfNeeded(StringParsingBuffer<CharacterType>& buffer, bool& completeQuotes)
{
unsigned char quote;
if (skipExactly(buffer, '\''))
quote = '\'';
else if (skipExactly(buffer, '"'))
quote = '"';
else
return false;
while (!completeQuotes && buffer.hasCharactersRemaining()) {
skipUntil(buffer, static_cast<CharacterType>(quote));
if (*(buffer.position() - 1) != '\\')
completeQuotes = true;
completeQuotes = skipExactly(buffer, static_cast<CharacterType>(quote)) && completeQuotes;
}
return true;
}
// Before:
//
// <cat.jpg>; rel=preload; foo=bar
// ^ ^
// position end
//
// After (if successful: otherwise the method returns false)
//
// <cat.jpg>; rel=preload; foo=bar
// ^ ^
// position end
template<typename CharacterType> static bool parseParameterValue(StringParsingBuffer<CharacterType>& buffer, String& value)
{
auto valueStart = buffer.position();
auto valueEnd = buffer.position();
bool completeQuotes = false;
bool hasQuotes = skipQuotesIfNeeded(buffer, completeQuotes);
if (!hasQuotes)
skipWhile<isParameterValueChar>(buffer);
valueEnd = buffer.position();
skipWhile<isSpaceOrTab>(buffer);
if ((!completeQuotes && valueStart == valueEnd) || (!buffer.atEnd() && !isParameterValueEnd(*buffer))) {
value = emptyString();
return false;
}
if (hasQuotes)
++valueStart;
if (completeQuotes)
--valueEnd;
ASSERT(valueEnd >= valueStart);
value = String(valueStart, valueEnd - valueStart);
return !hasQuotes || completeQuotes;
}
void LinkHeader::setValue(LinkParameterName name, String&& value)
{
switch (name) {
case LinkParameterRel:
if (!m_rel)
m_rel = WTFMove(value);
break;
case LinkParameterAnchor:
m_isValid = false;
break;
case LinkParameterCrossOrigin:
m_crossOrigin = WTFMove(value);
break;
case LinkParameterAs:
m_as = WTFMove(value);
break;
case LinkParameterType:
m_mimeType = WTFMove(value);
break;
case LinkParameterMedia:
m_media = WTFMove(value);
break;
case LinkParameterImageSrcSet:
m_imageSrcSet = WTFMove(value);
break;
case LinkParameterImageSizes:
m_imageSizes = WTFMove(value);
break;
case LinkParameterNonce:
m_nonce = WTFMove(value);
break;
case LinkParameterTitle:
case LinkParameterRev:
case LinkParameterHreflang:
case LinkParameterUnknown:
// These parameters are not yet supported, so they are currently ignored.
break;
}
// FIXME: Add support for more header parameters as neccessary.
}
template<typename CharacterType> static void findNextHeader(StringParsingBuffer<CharacterType>& buffer)
{
skipUntil(buffer, ',');
skipExactly(buffer, ',');
}
template<typename CharacterType> LinkHeader::LinkHeader(StringParsingBuffer<CharacterType>& buffer)
{
auto urlResult = findURLBoundaries(buffer);
if (urlResult == std::nullopt) {
m_isValid = false;
findNextHeader(buffer);
return;
}
m_url = urlResult.value();
while (m_isValid && buffer.hasCharactersRemaining()) {
if (!parseParameterDelimiter(buffer, m_isValid)) {
findNextHeader(buffer);
return;
}
auto parameterName = parseParameterName(buffer);
if (!parameterName) {
findNextHeader(buffer);
m_isValid = false;
return;
}
String parameterValue;
if (!parseParameterValue(buffer, parameterValue) && !isExtensionParameter(*parameterName)) {
findNextHeader(buffer);
m_isValid = false;
return;
}
setValue(*parameterName, WTFMove(parameterValue));
}
findNextHeader(buffer);
}
LinkHeaderSet::LinkHeaderSet(const String& header)
{
readCharactersForParsing(header, [&](auto buffer) {
while (buffer.hasCharactersRemaining())
m_headerSet.append(LinkHeader { buffer });
});
}
} // namespace WebCore