ApplicationCacheManifestParser.cpp [plain text]
#include "config.h"
#include "ApplicationCacheManifestParser.h"
#include "ParsingUtilities.h"
#include "TextResourceDecoder.h"
#include <wtf/text/StringHash.h>
#include <wtf/text/StringParsingBuffer.h>
#include <wtf/text/StringView.h>
namespace WebCore {
enum class ApplicationCacheParserMode { Explicit, Fallback, OnlineAllowlist, Unknown };
static StringView manifestPath(const URL& manifestURL)
{
auto manifestPath = manifestURL.path();
ASSERT(manifestPath[0] == '/');
manifestPath = manifestPath.substring(0, manifestPath.reverseFind('/') + 1);
ASSERT(manifestPath[0] == manifestPath[manifestPath.length() - 1]);
return manifestPath;
}
template<typename CharacterType> static constexpr bool isManifestWhitespace(CharacterType character)
{
return character == ' ' || character == '\t';
}
template<typename CharacterType> static constexpr bool isManifestNewline(CharacterType character)
{
return character == '\n' || character == '\r';
}
template<typename CharacterType> static constexpr bool isManifestWhitespaceOrNewline(CharacterType character)
{
return isManifestWhitespace(character) || isManifestNewline(character);
}
template<typename CharacterType> static URL makeManifestURL(const URL& manifestURL, const CharacterType* start, const CharacterType* end)
{
URL url(manifestURL, String(start, end - start));
url.removeFragmentIdentifier();
return url;
}
template<typename CharacterType> static constexpr CharacterType cacheManifestIdentifier[] = { 'C', 'A', 'C', 'H', 'E', ' ', 'M', 'A', 'N', 'I', 'F', 'E', 'S', 'T' };
template<typename CharacterType> static constexpr CharacterType cacheModeIdentifier[] = { 'C', 'A', 'C', 'H', 'E' };
template<typename CharacterType> static constexpr CharacterType fallbackModeIdentifier[] = { 'F', 'A', 'L', 'L', 'B', 'A', 'C', 'K' };
template<typename CharacterType> static constexpr CharacterType networkModeIdentifier[] = { 'N', 'E', 'T', 'W', 'O', 'R', 'K' };
Optional<ApplicationCacheManifest> parseApplicationCacheManifest(const URL& manifestURL, const String& manifestMIMEType, const char* data, int length)
{
static constexpr const char cacheManifestMIMEType[] = "text/cache-manifest";
bool allowFallbackNamespaceOutsideManifestPath = equalLettersIgnoringASCIICase(manifestMIMEType, cacheManifestMIMEType);
auto manifestPath = WebCore::manifestPath(manifestURL);
auto manifestString = TextResourceDecoder::create(ASCIILiteral::fromLiteralUnsafe(cacheManifestMIMEType), "UTF-8")->decodeAndFlush(data, length);
return readCharactersForParsing(manifestString, [&](auto buffer) -> Optional<ApplicationCacheManifest> {
using CharacterType = typename decltype(buffer)::CharacterType;
ApplicationCacheManifest manifest;
auto mode = ApplicationCacheParserMode::Explicit;
if (!skipCharactersExactly(buffer, cacheManifestIdentifier<CharacterType>))
return WTF::nullopt;
if (buffer.hasCharactersRemaining() && !isManifestWhitespaceOrNewline(*buffer))
return WTF::nullopt;
skipUntil<isManifestNewline>(buffer);
while (1) {
skipWhile<isManifestWhitespaceOrNewline>(buffer);
if (buffer.atEnd())
break;
auto lineStart = buffer.position();
skipUntil<isManifestNewline>(buffer);
if (*lineStart == '#')
continue;
auto lineEnd = buffer.position() - 1;
while (lineEnd > lineStart && isManifestWhitespace(*lineEnd))
--lineEnd;
auto lineBuffer = StringParsingBuffer { lineStart, lineEnd + 1 };
if (lineBuffer[lineBuffer.lengthRemaining() - 1] == ':') {
if (skipCharactersExactly(lineBuffer, cacheModeIdentifier<CharacterType>) && lineBuffer.lengthRemaining() == 1) {
mode = ApplicationCacheParserMode::Explicit;
continue;
}
if (skipCharactersExactly(lineBuffer, fallbackModeIdentifier<CharacterType>) && lineBuffer.lengthRemaining() == 1) {
mode = ApplicationCacheParserMode::Fallback;
continue;
}
if (skipCharactersExactly(lineBuffer, networkModeIdentifier<CharacterType>) && lineBuffer.lengthRemaining() == 1) {
mode = ApplicationCacheParserMode::OnlineAllowlist;
continue;
}
mode = ApplicationCacheParserMode::Unknown;
continue;
}
switch (mode) {
case ApplicationCacheParserMode::Unknown:
continue;
case ApplicationCacheParserMode::Explicit: {
skipUntil<isManifestWhitespace>(lineBuffer);
auto url = makeManifestURL(manifestURL, lineStart, lineBuffer.position());
if (!url.isValid())
continue;
if (!equalIgnoringASCIICase(url.protocol(), manifestURL.protocol()))
continue;
if (manifestURL.protocolIs("https") && !protocolHostAndPortAreEqual(manifestURL, url))
continue;
manifest.explicitURLs.add(url.string());
continue;
}
case ApplicationCacheParserMode::OnlineAllowlist: {
skipUntil<isManifestWhitespace>(lineBuffer);
if (lineBuffer.position() - lineStart == 1 && *lineStart == '*') {
manifest.allowAllNetworkRequests = true;
continue;
}
auto url = makeManifestURL(manifestURL, lineStart, lineBuffer.position());
if (!url.isValid())
continue;
if (!equalIgnoringASCIICase(url.protocol(), manifestURL.protocol()))
continue;
manifest.onlineAllowedURLs.append(url);
continue;
}
case ApplicationCacheParserMode::Fallback: {
skipUntil<isManifestWhitespace>(lineBuffer);
if (lineBuffer.atEnd()) {
continue;
}
auto namespaceURL = makeManifestURL(manifestURL, lineStart, lineBuffer.position());
if (!namespaceURL.isValid())
continue;
if (!protocolHostAndPortAreEqual(manifestURL, namespaceURL))
continue;
if (!allowFallbackNamespaceOutsideManifestPath && !namespaceURL.path().startsWith(manifestPath))
continue;
skipWhile<isManifestWhitespace>(lineBuffer);
auto fallbackStart = lineBuffer.position();
skipUntil<isManifestWhitespace>(lineBuffer);
auto fallbackURL = makeManifestURL(manifestURL, fallbackStart, lineBuffer.position());
if (!fallbackURL.isValid())
continue;
if (!protocolHostAndPortAreEqual(manifestURL, fallbackURL))
continue;
manifest.fallbackURLs.append(std::make_pair(namespaceURL, fallbackURL));
continue;
}
}
ASSERT_NOT_REACHED();
}
return manifest;
});
}
}