blob: c9e7cc157218620b36d0d0b6e2b6fbaa84c80767 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Copyright (C) 2016-2021 Apple Inc. All rights reserved.
// Copyright (C) 2021 Metrological Group B.V.
// Copyright (C) 2021 Igalia S.L.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * 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.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// 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 "CSSPropertyParserWorkerSafe.h"
#include "CSSFontFaceSrcValue.h"
#include "CSSFontFeatureValue.h"
#include "CSSFontStyleValue.h"
#include "CSSImageValue.h"
#include "CSSParserFastPaths.h"
#include "CSSParserImpl.h"
#include "CSSParserTokenRange.h"
#include "CSSPropertyParser.h"
#include "CSSTokenizer.h"
#include "CSSUnicodeRangeValue.h"
#include "Document.h"
#include "ParsingUtilities.h"
#include "ScriptExecutionContext.h"
#include "StyleSheetContents.h"
#if ENABLE(VARIATION_FONTS)
#include "CSSFontStyleRangeValue.h"
#endif
namespace WebCore {
std::optional<CSSPropertyParserHelpers::FontRaw> CSSPropertyParserWorkerSafe::parseFont(const String& string, CSSParserMode mode)
{
CSSTokenizer tokenizer(string);
CSSParserTokenRange range(tokenizer.tokenRange());
range.consumeWhitespace();
return CSSPropertyParserHelpers::consumeFontRaw(range, mode);
}
Color CSSPropertyParserWorkerSafe::parseColor(const String& string)
{
if (auto color = CSSParserFastPaths::parseSimpleColor(string))
return *color;
CSSTokenizer tokenizer(string);
CSSParserTokenRange range(tokenizer.tokenRange());
range.consumeWhitespace();
return CSSPropertyParserHelpers::consumeColorWorkerSafe(range, CSSParserContext(HTMLStandardMode));
}
static CSSParserMode parserMode(ScriptExecutionContext& context)
{
return (is<Document>(context) && downcast<Document>(context).inQuirksMode()) ? HTMLQuirksMode : HTMLStandardMode;
}
RefPtr<CSSValueList> CSSPropertyParserWorkerSafe::parseFontFaceSrc(const String& string, const CSSParserContext& context)
{
CSSParserImpl parser(context, string);
CSSParserTokenRange range = parser.tokenizer()->tokenRange();
range.consumeWhitespace();
if (range.atEnd())
return nullptr;
auto parsedValue = CSSPropertyParserHelpersWorkerSafe::consumeFontFaceSrc(range, parser.context());
if (!parsedValue || !range.atEnd())
return nullptr;
return parsedValue;
}
RefPtr<CSSValue> CSSPropertyParserWorkerSafe::parseFontFaceStyle(const String& string, ScriptExecutionContext& context)
{
CSSParserContext parserContext(parserMode(context));
CSSParserImpl parser(parserContext, string);
CSSParserTokenRange range = parser.tokenizer()->tokenRange();
range.consumeWhitespace();
if (range.atEnd())
return nullptr;
auto parsedValue =
#if ENABLE(VARIATION_FONTS)
CSSPropertyParserHelpersWorkerSafe::consumeFontStyleRange(range, parserContext.mode, context.cssValuePool());
#else
CSSPropertyParserHelpersWorkerSafe::consumeFontStyle(range, parserContext.mode, context.cssValuePool());
#endif
if (!parsedValue || !range.atEnd())
return nullptr;
return parsedValue;
}
RefPtr<CSSValue> CSSPropertyParserWorkerSafe::parseFontFaceWeight(const String& string, ScriptExecutionContext& context)
{
CSSParserContext parserContext(parserMode(context));
CSSParserImpl parser(parserContext, string);
CSSParserTokenRange range = parser.tokenizer()->tokenRange();
range.consumeWhitespace();
if (range.atEnd())
return nullptr;
auto parsedValue =
#if ENABLE(VARIATION_FONTS)
CSSPropertyParserHelpersWorkerSafe::consumeFontWeightAbsoluteRange(range, context.cssValuePool());
#else
CSSPropertyParserHelpersWorkerSafe::consumeFontWeightAbsolute(range, context.cssValuePool());
#endif
if (!parsedValue || !range.atEnd())
return nullptr;
return parsedValue;
}
RefPtr<CSSValue> CSSPropertyParserWorkerSafe::parseFontFaceStretch(const String& string, ScriptExecutionContext& context)
{
CSSParserContext parserContext(parserMode(context));
CSSParserImpl parser(parserContext, string);
CSSParserTokenRange range = parser.tokenizer()->tokenRange();
range.consumeWhitespace();
if (range.atEnd())
return nullptr;
auto parsedValue =
#if ENABLE(VARIATION_FONTS)
CSSPropertyParserHelpersWorkerSafe::consumeFontStretchRange(range, context.cssValuePool());
#else
CSSPropertyParserHelpersWorkerSafe::consumeFontStretch(range, context.cssValuePool());
#endif
if (!parsedValue || !range.atEnd())
return nullptr;
return parsedValue;
}
RefPtr<CSSValue> CSSPropertyParserWorkerSafe::parseFontFaceUnicodeRange(const String& string, ScriptExecutionContext& context)
{
CSSParserContext parserContext(parserMode(context));
CSSParserImpl parser(parserContext, string);
CSSParserTokenRange range = parser.tokenizer()->tokenRange();
range.consumeWhitespace();
if (range.atEnd())
return nullptr;
auto parsedValue = CSSPropertyParserHelpersWorkerSafe::consumeFontFaceUnicodeRange(range);
if (!parsedValue || !range.atEnd())
return nullptr;
return parsedValue;
}
RefPtr<CSSValue> CSSPropertyParserWorkerSafe::parseFontFaceFeatureSettings(const String& string, ScriptExecutionContext& context)
{
CSSParserContext parserContext(parserMode(context));
CSSParserImpl parser(parserContext, string);
CSSParserTokenRange range = parser.tokenizer()->tokenRange();
range.consumeWhitespace();
if (range.atEnd())
return nullptr;
auto parsedValue = CSSPropertyParserHelpersWorkerSafe::consumeFontFeatureSettings(range, context.cssValuePool());
if (!parsedValue || !range.atEnd())
return nullptr;
return parsedValue;
}
RefPtr<CSSValue> CSSPropertyParserWorkerSafe::parseFontFaceDisplay(const String& string, ScriptExecutionContext& context)
{
CSSParserContext parserContext(parserMode(context));
CSSParserImpl parser(parserContext, string);
CSSParserTokenRange range = parser.tokenizer()->tokenRange();
range.consumeWhitespace();
if (range.atEnd())
return nullptr;
auto parsedValue = CSSPropertyParserHelpersWorkerSafe::consumeFontFaceFontDisplay(range, context.cssValuePool());
if (!parsedValue || !range.atEnd())
return nullptr;
return parsedValue;
}
namespace CSSPropertyParserHelpersWorkerSafe {
static RefPtr<CSSFontFaceSrcValue> consumeFontFaceSrcURI(CSSParserTokenRange& range, const CSSParserContext& context)
{
auto location = context.completeURL(CSSPropertyParserHelpers::consumeUrlAsStringView(range).toString()).resolvedURL.string();
if (location.isNull())
return nullptr;
String format;
if (range.peek().functionId() == CSSValueFormat) {
// https://drafts.csswg.org/css-fonts/#descdef-font-face-src
// FIXME: The format should be a comma-separated list; at this time we support only one.
// FIXME: We allow any identifier here and convert all to strings; specification calls for only certain identifiers.
auto args = CSSPropertyParserHelpers::consumeFunction(range);
auto& arg = args.consumeIncludingWhitespace();
if ((arg.type() != StringToken && arg.type() != IdentToken) || !args.atEnd())
return nullptr;
format = arg.value().toString();
}
// FIXME: Change CSSFontFaceSrcValue::create to take format so we don't need a separate setFormat call.
auto srcValue = CSSFontFaceSrcValue::create(location, context.isContentOpaque ? LoadedFromOpaqueSource::Yes : LoadedFromOpaqueSource::No);
srcValue->setFormat(format);
return srcValue;
}
static RefPtr<CSSValue> consumeFontFaceSrcLocal(CSSParserTokenRange& range)
{
CSSParserTokenRange args = CSSPropertyParserHelpers::consumeFunction(range);
if (args.peek().type() == StringToken) {
const CSSParserToken& arg = args.consumeIncludingWhitespace();
if (!args.atEnd())
return nullptr;
return CSSFontFaceSrcValue::createLocal(arg.value().toString());
}
if (args.peek().type() == IdentToken) {
String familyName = CSSPropertyParserHelpers::concatenateFamilyName(args);
if (!args.atEnd())
return nullptr;
return CSSFontFaceSrcValue::createLocal(familyName);
}
return nullptr;
}
RefPtr<CSSValueList> consumeFontFaceSrc(CSSParserTokenRange& range, const CSSParserContext& context)
{
RefPtr<CSSValueList> values = CSSValueList::createCommaSeparated();
do {
const CSSParserToken& token = range.peek();
RefPtr<CSSValue> parsedValue;
if (token.functionId() == CSSValueLocal)
parsedValue = consumeFontFaceSrcLocal(range);
else
parsedValue = consumeFontFaceSrcURI(range, context);
if (!parsedValue)
return nullptr;
values->append(parsedValue.releaseNonNull());
} while (CSSPropertyParserHelpers::consumeCommaIncludingWhitespace(range));
return values;
}
RefPtr<CSSFontStyleValue> consumeFontStyle(CSSParserTokenRange& range, CSSParserMode cssParserMode, CSSValuePool& pool)
{
if (auto result = CSSPropertyParserHelpers::consumeFontStyleRaw(range, cssParserMode)) {
#if ENABLE(VARIATION_FONTS)
if (result->style == CSSValueOblique && result->angle) {
return CSSFontStyleValue::create(pool.createIdentifierValue(CSSValueOblique),
pool.createValue(result->angle->value, result->angle->type));
}
#endif
return CSSFontStyleValue::create(pool.createIdentifierValue(result->style));
}
return nullptr;
}
#if ENABLE(VARIATION_FONTS)
static RefPtr<CSSPrimitiveValue> consumeFontStyleKeywordValue(CSSParserTokenRange& range, CSSValuePool& pool)
{
return CSSPropertyParserHelpers::consumeIdentWorkerSafe<CSSValueNormal, CSSValueItalic, CSSValueOblique>(range, pool);
}
RefPtr<CSSFontStyleRangeValue> consumeFontStyleRange(CSSParserTokenRange& range, CSSParserMode cssParserMode, CSSValuePool& pool)
{
auto keyword = consumeFontStyleKeywordValue(range, pool);
if (!keyword)
return nullptr;
if (keyword->valueID() != CSSValueOblique || range.atEnd())
return CSSFontStyleRangeValue::create(keyword.releaseNonNull());
// FIXME: This should probably not allow the unitless zero.
if (auto firstAngle = CSSPropertyParserHelpers::consumeAngleWorkerSafe(range, cssParserMode, pool, CSSPropertyParserHelpers::UnitlessQuirk::Forbid, CSSPropertyParserHelpers::UnitlessZeroQuirk::Allow)) {
auto firstAngleInDegrees = firstAngle->doubleValue(CSSUnitType::CSS_DEG);
if (!CSSPropertyParserHelpers::isFontStyleAngleInRange(firstAngleInDegrees))
return nullptr;
if (range.atEnd()) {
auto result = CSSValueList::createSpaceSeparated();
result->append(firstAngle.releaseNonNull());
return CSSFontStyleRangeValue::create(keyword.releaseNonNull(), WTFMove(result));
}
// FIXME: This should probably not allow the unitless zero.
auto secondAngle = CSSPropertyParserHelpers::consumeAngleWorkerSafe(range, cssParserMode, pool, CSSPropertyParserHelpers::UnitlessQuirk::Forbid, CSSPropertyParserHelpers::UnitlessZeroQuirk::Allow);
if (!secondAngle)
return nullptr;
auto secondAngleInDegrees = secondAngle->doubleValue(CSSUnitType::CSS_DEG);
if (!CSSPropertyParserHelpers::isFontStyleAngleInRange(secondAngleInDegrees) || firstAngleInDegrees > secondAngleInDegrees)
return nullptr;
auto result = CSSValueList::createSpaceSeparated();
result->append(firstAngle.releaseNonNull());
result->append(secondAngle.releaseNonNull());
return CSSFontStyleRangeValue::create(keyword.releaseNonNull(), WTFMove(result));
}
return nullptr;
}
#endif
static RefPtr<CSSPrimitiveValue> consumeFontWeightAbsoluteKeywordValue(CSSParserTokenRange& range, CSSValuePool& pool)
{
return CSSPropertyParserHelpers::consumeIdentWorkerSafe<CSSValueNormal, CSSValueBold>(range, pool);
}
#if ENABLE(VARIATION_FONTS)
RefPtr<CSSValue> consumeFontWeightAbsoluteRange(CSSParserTokenRange& range, CSSValuePool& pool)
{
if (auto result = consumeFontWeightAbsoluteKeywordValue(range, pool))
return result;
auto firstNumber = CSSPropertyParserHelpers::consumeFontWeightNumberWorkerSafe(range, pool);
if (!firstNumber)
return nullptr;
if (range.atEnd())
return firstNumber;
auto secondNumber = CSSPropertyParserHelpers::consumeFontWeightNumberWorkerSafe(range, pool);
if (!secondNumber || firstNumber->floatValue() > secondNumber->floatValue())
return nullptr;
auto result = CSSValueList::createSpaceSeparated();
result->append(firstNumber.releaseNonNull());
result->append(secondNumber.releaseNonNull());
return RefPtr<CSSValue>(WTFMove(result));
}
#else
RefPtr<CSSPrimitiveValue> consumeFontWeightAbsolute(CSSParserTokenRange& range, CSSValuePool& pool)
{
if (auto result = consumeFontWeightAbsoluteKeywordValue(range, pool))
return result;
return CSSPropertyParserHelpers::consumeFontWeightNumberWorkerSafe(range, pool);
}
#endif
RefPtr<CSSPrimitiveValue> consumeFontStretchKeywordValue(CSSParserTokenRange& range, CSSValuePool& pool)
{
if (auto valueID = CSSPropertyParserHelpers::consumeFontStretchKeywordValueRaw(range))
return pool.createIdentifierValue(*valueID);
return nullptr;
}
#if ENABLE(VARIATION_FONTS)
static bool fontStretchIsWithinRange(float stretch)
{
return stretch >= 0;
}
#endif
RefPtr<CSSPrimitiveValue> consumeFontStretch(CSSParserTokenRange& range, CSSValuePool& pool)
{
if (auto result = consumeFontStretchKeywordValue(range, pool))
return result;
#if ENABLE(VARIATION_FONTS)
if (auto percent = CSSPropertyParserHelpers::consumePercentWorkerSafe(range, ValueRange::NonNegative, pool))
return fontStretchIsWithinRange(percent->value<float>()) ? percent : nullptr;
#endif
return nullptr;
}
#if ENABLE(VARIATION_FONTS)
RefPtr<CSSValue> consumeFontStretchRange(CSSParserTokenRange& range, CSSValuePool& pool)
{
if (auto result = consumeFontStretchKeywordValue(range, pool))
return result;
auto firstPercent = CSSPropertyParserHelpers::consumePercentWorkerSafe(range, ValueRange::NonNegative, pool);
if (!firstPercent || !fontStretchIsWithinRange(firstPercent->value<float>()))
return nullptr;
if (range.atEnd())
return firstPercent;
auto secondPercent = CSSPropertyParserHelpers::consumePercentWorkerSafe(range, ValueRange::NonNegative, pool);
if (!secondPercent || !fontStretchIsWithinRange(secondPercent->value<float>()) || firstPercent->floatValue() > secondPercent->floatValue())
return nullptr;
auto result = CSSValueList::createSpaceSeparated();
result->append(firstPercent.releaseNonNull());
result->append(secondPercent.releaseNonNull());
return RefPtr<CSSValue>(WTFMove(result));
}
#endif
static bool consumeOptionalDelimiter(CSSParserTokenRange& range, UChar value)
{
if (!(range.peek().type() == DelimiterToken && range.peek().delimiter() == value))
return false;
range.consume();
return true;
}
static StringView consumeIdentifier(CSSParserTokenRange& range)
{
if (range.peek().type() != IdentToken)
return { };
return range.consume().value();
}
static bool consumeAndAppendOptionalNumber(StringBuilder& builder, CSSParserTokenRange& range, CSSParserTokenType type = NumberToken)
{
if (range.peek().type() != type)
return false;
auto originalText = range.consume().originalText();
if (originalText.isNull())
return false;
builder.append(originalText);
return true;
}
static bool consumeAndAppendOptionalDelimiter(StringBuilder& builder, CSSParserTokenRange& range, UChar value)
{
if (!consumeOptionalDelimiter(range, value))
return false;
builder.append(value);
return true;
}
static void consumeAndAppendOptionalQuestionMarks(StringBuilder& builder, CSSParserTokenRange& range)
{
while (consumeAndAppendOptionalDelimiter(builder, range, '?')) { }
}
static String consumeUnicodeRangeString(CSSParserTokenRange& range)
{
if (!equalLettersIgnoringASCIICase(consumeIdentifier(range), "u"))
return { };
StringBuilder builder;
if (consumeAndAppendOptionalNumber(builder, range, DimensionToken))
consumeAndAppendOptionalQuestionMarks(builder, range);
else if (consumeAndAppendOptionalNumber(builder, range)) {
if (!(consumeAndAppendOptionalNumber(builder, range, DimensionToken) || consumeAndAppendOptionalNumber(builder, range)))
consumeAndAppendOptionalQuestionMarks(builder, range);
} else if (consumeOptionalDelimiter(range, '+')) {
builder.append('+');
if (auto identifier = consumeIdentifier(range); !identifier.isNull())
builder.append(identifier);
else if (!consumeAndAppendOptionalDelimiter(builder, range, '?'))
return { };
consumeAndAppendOptionalQuestionMarks(builder, range);
} else
return { };
return builder.toString();
}
struct UnicodeRange {
UChar32 start;
UChar32 end;
};
static std::optional<UnicodeRange> consumeUnicodeRange(CSSParserTokenRange& range)
{
return readCharactersForParsing(consumeUnicodeRangeString(range), [&](auto buffer) -> std::optional<UnicodeRange> {
if (!skipExactly(buffer, '+'))
return std::nullopt;
UChar32 start = 0;
unsigned hexDigitCount = 0;
while (buffer.hasCharactersRemaining() && isASCIIHexDigit(*buffer)) {
if (++hexDigitCount > 6)
return std::nullopt;
start <<= 4;
start |= toASCIIHexValue(*buffer++);
}
auto end = start;
while (skipExactly(buffer, '?')) {
if (++hexDigitCount > 6)
return std::nullopt;
start <<= 4;
end <<= 4;
end |= 0xF;
}
if (!hexDigitCount)
return std::nullopt;
if (start == end && buffer.hasCharactersRemaining()) {
if (!skipExactly(buffer, '-'))
return std::nullopt;
end = 0;
hexDigitCount = 0;
while (buffer.hasCharactersRemaining() && isASCIIHexDigit(*buffer)) {
if (++hexDigitCount > 6)
return std::nullopt;
end <<= 4;
end |= toASCIIHexValue(*buffer++);
}
if (!hexDigitCount)
return std::nullopt;
}
if (buffer.hasCharactersRemaining())
return std::nullopt;
return { { start, end } };
});
}
RefPtr<CSSValueList> consumeFontFaceUnicodeRange(CSSParserTokenRange& range)
{
auto values = CSSValueList::createCommaSeparated();
do {
auto unicodeRange = consumeUnicodeRange(range);
range.consumeWhitespace();
if (!unicodeRange || unicodeRange->end > UCHAR_MAX_VALUE || unicodeRange->start > unicodeRange->end)
return nullptr;
values->append(CSSUnicodeRangeValue::create(unicodeRange->start, unicodeRange->end));
} while (CSSPropertyParserHelpers::consumeCommaIncludingWhitespace(range));
return values;
}
static RefPtr<CSSFontFeatureValue> consumeFontFeatureTag(CSSParserTokenRange& range)
{
// Feature tag name consists of 4-letter characters.
static const unsigned tagNameLength = 4;
const CSSParserToken& token = range.consumeIncludingWhitespace();
// Feature tag name comes first
if (token.type() != StringToken)
return nullptr;
if (token.value().length() != tagNameLength)
return nullptr;
FontTag tag;
for (unsigned i = 0; i < tag.size(); ++i) {
// Limits the range of characters to 0x20-0x7E, following the tag name rules defiend in the OpenType specification.
UChar character = token.value()[i];
if (character < 0x20 || character > 0x7E)
return nullptr;
tag[i] = toASCIILower(character);
}
int tagValue = 1;
if (!range.atEnd() && range.peek().type() != CommaToken) {
// Feature tag values could follow: <integer> | on | off
if (auto integer = CSSPropertyParserHelpers::consumeIntegerRaw(range, 0))
tagValue = *integer;
else if (range.peek().id() == CSSValueOn || range.peek().id() == CSSValueOff)
tagValue = range.consumeIncludingWhitespace().id() == CSSValueOn;
else
return nullptr;
}
return CSSFontFeatureValue::create(WTFMove(tag), tagValue);
}
RefPtr<CSSValue> consumeFontFeatureSettings(CSSParserTokenRange& range, CSSValuePool& pool)
{
if (range.peek().id() == CSSValueNormal)
return CSSPropertyParserHelpers::consumeIdentWorkerSafe(range, pool);
RefPtr<CSSValueList> settings = CSSValueList::createCommaSeparated();
do {
RefPtr<CSSFontFeatureValue> fontFeatureValue = consumeFontFeatureTag(range);
if (!fontFeatureValue)
return nullptr;
settings->append(fontFeatureValue.releaseNonNull());
} while (CSSPropertyParserHelpers::consumeCommaIncludingWhitespace(range));
return settings;
}
RefPtr<CSSPrimitiveValue> consumeFontFaceFontDisplay(CSSParserTokenRange& range, CSSValuePool& pool)
{
return CSSPropertyParserHelpers::consumeIdentWorkerSafe<CSSValueAuto, CSSValueBlock, CSSValueSwap, CSSValueFallback, CSSValueOptional>(range, pool);
}
} // namespace CSSPropertyParserHelpersWorkerSafe
} // namespace WebCore