blob: 32a338b5af308961b5fbf23209d684828e690737 [file] [log] [blame]
/*
* Copyright 2015 The Chromium Authors. All rights reserved.
* Copyright (C) 2016 Akamai Technologies 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 chr)
{
return (chr == ' ') || (chr == '\t');
}
template <typename CharacterType>
static bool isNotURLTerminatingChar(CharacterType chr)
{
return (chr != '>');
}
template <typename CharacterType>
static bool isValidParameterNameChar(CharacterType chr)
{
// 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 ((chr >= '^' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || (chr >= '0' && chr <= '9') || (chr >= '!' && chr <= '$') || chr == '&' || chr == '+' || chr == '-' || chr == '.')
return true;
return false;
}
template <typename CharacterType>
static bool isParameterValueEnd(CharacterType chr)
{
return chr == ';' || chr == ',';
}
template <typename CharacterType>
static bool isParameterValueChar(CharacterType chr)
{
return !isSpaceOrTab(chr) && !isParameterValueEnd(chr);
}
// 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 Optional<String> findURLBoundaries(CharacterType*& position, CharacterType* const end)
{
ASSERT(position <= end);
skipWhile<CharacterType, isSpaceOrTab>(position, end);
if (!skipExactly<CharacterType>(position, end, '<'))
return WTF::nullopt;
skipWhile<CharacterType, isSpaceOrTab>(position, end);
CharacterType* urlStart = position;
skipWhile<CharacterType, isNotURLTerminatingChar>(position, end);
CharacterType* urlEnd = position;
skipUntil<CharacterType>(position, end, '>');
if (!skipExactly<CharacterType>(position, end, '>'))
return WTF::nullopt;
return String(urlStart, urlEnd - urlStart);
}
template <typename CharacterType>
static bool invalidParameterDelimiter(CharacterType*& position, CharacterType* const end)
{
ASSERT(position <= end);
return (!skipExactly<CharacterType>(position, end, ';') && (position < end) && (*position != ','));
}
template <typename CharacterType>
static bool validFieldEnd(CharacterType*& position, CharacterType* const end)
{
ASSERT(position <= end);
return (position == end || *position == ',');
}
// 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(CharacterType*& position, CharacterType* const end, bool& isValid)
{
ASSERT(position <= end);
isValid = true;
skipWhile<CharacterType, isSpaceOrTab>(position, end);
if (invalidParameterDelimiter(position, end)) {
isValid = false;
return false;
}
skipWhile<CharacterType, isSpaceOrTab>(position, end);
if (validFieldEnd(position, end))
return false;
return true;
}
static LinkHeader::LinkParameterName paramterNameFromString(String 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 bool parseParameterName(CharacterType*& position, CharacterType* const end, LinkHeader::LinkParameterName& name)
{
ASSERT(position <= end);
CharacterType* nameStart = position;
skipWhile<CharacterType, isValidParameterNameChar>(position, end);
CharacterType* nameEnd = position;
skipWhile<CharacterType, isSpaceOrTab>(position, end);
bool hasEqual = skipExactly<CharacterType>(position, end, '=');
skipWhile<CharacterType, isSpaceOrTab>(position, end);
name = paramterNameFromString(String(nameStart, nameEnd - nameStart));
if (hasEqual)
return true;
bool validParameterValueEnd = (position == end) || isParameterValueEnd(*position);
return validParameterValueEnd && isExtensionParameter(name);
}
// 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(CharacterType*& position, CharacterType* const end, bool& completeQuotes)
{
ASSERT(position <= end);
unsigned char quote;
if (skipExactly<CharacterType>(position, end, '\''))
quote = '\'';
else if (skipExactly<CharacterType>(position, end, '"'))
quote = '"';
else
return false;
while (!completeQuotes && position < end) {
skipUntil(position, end, static_cast<CharacterType>(quote));
if (*(position - 1) != '\\')
completeQuotes = true;
completeQuotes = skipExactly(position, end, 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(CharacterType*& position, CharacterType* const end, String& value)
{
ASSERT(position <= end);
CharacterType* valueStart = position;
CharacterType* valueEnd = position;
bool completeQuotes = false;
bool hasQuotes = skipQuotesIfNeeded(position, end, completeQuotes);
if (!hasQuotes)
skipWhile<CharacterType, isParameterValueChar>(position, end);
valueEnd = position;
skipWhile<CharacterType, isSpaceOrTab>(position, end);
if ((!completeQuotes && valueStart == valueEnd) || (position != end && !isParameterValueEnd(*position))) {
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(CharacterType*& position, CharacterType* const end)
{
ASSERT(position <= end);
skipUntil<CharacterType>(position, end, ',');
skipExactly<CharacterType>(position, end, ',');
}
template <typename CharacterType>
LinkHeader::LinkHeader(CharacterType*& position, CharacterType* const end)
{
ASSERT(position <= end);
auto urlResult = findURLBoundaries(position, end);
if (urlResult == WTF::nullopt) {
m_isValid = false;
findNextHeader(position, end);
return;
}
m_url = urlResult.value();
while (m_isValid && position < end) {
if (!parseParameterDelimiter(position, end, m_isValid)) {
findNextHeader(position, end);
return;
}
LinkParameterName parameterName;
if (!parseParameterName(position, end, parameterName)) {
findNextHeader(position, end);
m_isValid = false;
return;
}
String parameterValue;
if (!parseParameterValue(position, end, parameterValue) && !isExtensionParameter(parameterName)) {
findNextHeader(position, end);
m_isValid = false;
return;
}
setValue(parameterName, WTFMove(parameterValue));
}
findNextHeader(position, end);
}
LinkHeaderSet::LinkHeaderSet(const String& header)
{
if (header.isNull())
return;
if (header.is8Bit())
init(header.characters8(), header.length());
else
init(header.characters16(), header.length());
}
template <typename CharacterType>
void LinkHeaderSet::init(CharacterType* headerValue, size_t length)
{
CharacterType* position = headerValue;
CharacterType* const end = headerValue + length;
while (position < end)
m_headerSet.append(LinkHeader(position, end));
}
} // namespace WebCore