344 lines
12 KiB
C++
344 lines
12 KiB
C++
/*
|
|
* 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"))
|
|
return LinkHeader::LinkParameterRel;
|
|
if (equalLettersIgnoringASCIICase(name, "anchor"))
|
|
return LinkHeader::LinkParameterAnchor;
|
|
if (equalLettersIgnoringASCIICase(name, "crossorigin"))
|
|
return LinkHeader::LinkParameterCrossOrigin;
|
|
if (equalLettersIgnoringASCIICase(name, "title"))
|
|
return LinkHeader::LinkParameterTitle;
|
|
if (equalLettersIgnoringASCIICase(name, "media"))
|
|
return LinkHeader::LinkParameterMedia;
|
|
if (equalLettersIgnoringASCIICase(name, "type"))
|
|
return LinkHeader::LinkParameterType;
|
|
if (equalLettersIgnoringASCIICase(name, "rev"))
|
|
return LinkHeader::LinkParameterRev;
|
|
if (equalLettersIgnoringASCIICase(name, "hreflang"))
|
|
return LinkHeader::LinkParameterHreflang;
|
|
if (equalLettersIgnoringASCIICase(name, "as"))
|
|
return LinkHeader::LinkParameterAs;
|
|
if (equalLettersIgnoringASCIICase(name, "imagesrcset"))
|
|
return LinkHeader::LinkParameterImageSrcSet;
|
|
if (equalLettersIgnoringASCIICase(name, "imagesizes"))
|
|
return LinkHeader::LinkParameterImageSizes;
|
|
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 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
|
|
|