/* * 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 static bool isSpaceOrTab(CharacterType character) { return character == ' ' || character == '\t'; } template static bool isNotURLTerminatingChar(CharacterType character) { return character != '>'; } template 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 static bool isParameterValueEnd(CharacterType character) { return character == ';' || character == ','; } template 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: // // ; rel=preload // ^ ^ // position end // // After (if successful: otherwise the method returns false) // // ; rel=preload // ^ ^ // position end template static std::optional findURLBoundaries(StringParsingBuffer& buffer) { skipWhile(buffer); if (!skipExactly(buffer, '<')) return std::nullopt; skipWhile(buffer); auto urlStart = buffer.position(); skipWhile(buffer); auto urlEnd = buffer.position(); skipUntil(buffer, '>'); if (!skipExactly(buffer, '>')) return std::nullopt; return String(urlStart, urlEnd - urlStart); } template static bool invalidParameterDelimiter(StringParsingBuffer& buffer) { return !skipExactly(buffer, ';') && buffer.hasCharactersRemaining() && *buffer != ','; } template static bool validFieldEnd(StringParsingBuffer& buffer) { return buffer.atEnd() || *buffer == ','; } // Before: // // ; rel=preload // ^ ^ // position end // // After (if successful: otherwise the method returns false, and modifies the isValid boolean accordingly) // // ; rel=preload // ^ ^ // position end template static bool parseParameterDelimiter(StringParsingBuffer& buffer, bool& isValid) { isValid = true; skipWhile(buffer); if (invalidParameterDelimiter(buffer)) { isValid = false; return false; } skipWhile(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: // // ; rel=preload // ^ ^ // position end // // After (if successful: otherwise the method returns false) // // ; rel=preload // ^ ^ // position end template static std::optional parseParameterName(StringParsingBuffer& buffer) { auto nameStart = buffer.position(); skipWhile(buffer); auto nameEnd = buffer.position(); skipWhile(buffer); bool hasEqual = skipExactly(buffer, '='); skipWhile(buffer); auto name = paramterNameFromString(StringView { nameStart, static_cast(nameEnd - nameStart) }); if (hasEqual) return name; bool validParameterValueEnd = buffer.atEnd() || isParameterValueEnd(*buffer); if (validParameterValueEnd && isExtensionParameter(name)) return name; return std::nullopt; } // Before: // // ; rel="preload"; type="image/jpeg"; // ^ ^ // position end // // After (if the parameter starts with a quote, otherwise the method returns false) // // ; rel="preload"; type="image/jpeg"; // ^ ^ // position end template static bool skipQuotesIfNeeded(StringParsingBuffer& 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(quote)); if (*(buffer.position() - 1) != '\\') completeQuotes = true; completeQuotes = skipExactly(buffer, static_cast(quote)) && completeQuotes; } return true; } // Before: // // ; rel=preload; foo=bar // ^ ^ // position end // // After (if successful: otherwise the method returns false) // // ; rel=preload; foo=bar // ^ ^ // position end template static bool parseParameterValue(StringParsingBuffer& buffer, String& value) { auto valueStart = buffer.position(); auto valueEnd = buffer.position(); bool completeQuotes = false; bool hasQuotes = skipQuotesIfNeeded(buffer, completeQuotes); if (!hasQuotes) skipWhile(buffer); valueEnd = buffer.position(); skipWhile(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 static void findNextHeader(StringParsingBuffer& buffer) { skipUntil(buffer, ','); skipExactly(buffer, ','); } template LinkHeader::LinkHeader(StringParsingBuffer& 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