SubresourceIntegrity.cpp [plain text]
#include "config.h"
#include "SubresourceIntegrity.h"
#include "CachedResource.h"
#include "HTMLParserIdioms.h"
#include "ParsingUtilities.h"
#include "ResourceCryptographicDigest.h"
#include "SharedBuffer.h"
#include <wtf/text/StringParsingBuffer.h>
namespace WebCore {
namespace {
template<typename CharacterType>
static bool isVCHAR(CharacterType c)
{
return c >= 0x21 && c <= 0x7e;
}
template<typename CharacterType>
struct IntegrityMetadataParser {
public:
IntegrityMetadataParser(Optional<Vector<EncodedResourceCryptographicDigest>>& digests)
: m_digests(digests)
{
}
bool operator()(StringParsingBuffer<CharacterType>& buffer)
{
if (!m_digests)
m_digests = Vector<EncodedResourceCryptographicDigest> { };
auto digest = parseEncodedCryptographicDigest(buffer);
if (!digest)
return false;
if (skipExactly(buffer, '?'))
skipWhile<isVCHAR>(buffer);
if (!buffer.atEnd() && !isHTMLSpace(*buffer))
return false;
m_digests->append(WTFMove(*digest));
return true;
}
private:
Optional<Vector<EncodedResourceCryptographicDigest>>& m_digests;
};
}
template <typename CharacterType, typename Functor>
static inline void splitOnSpaces(StringParsingBuffer<CharacterType> buffer, Functor&& functor)
{
skipWhile<isHTMLSpace>(buffer);
while (buffer.hasCharactersRemaining()) {
if (!functor(buffer))
skipWhile<isNotHTMLSpace>(buffer);
skipWhile<isHTMLSpace>(buffer);
}
}
static Optional<Vector<EncodedResourceCryptographicDigest>> parseIntegrityMetadata(const String& integrityMetadata)
{
if (integrityMetadata.isEmpty())
return WTF::nullopt;
Optional<Vector<EncodedResourceCryptographicDigest>> result;
readCharactersForParsing(integrityMetadata, [&result] (auto buffer) {
using CharacterType = typename decltype(buffer)::CharacterType;
splitOnSpaces(buffer, IntegrityMetadataParser<CharacterType> { result });
});
return result;
}
static bool isResponseEligible(const CachedResource& resource)
{
return resource.isCORSSameOrigin();
}
static Optional<EncodedResourceCryptographicDigest::Algorithm> prioritizedHashFunction(EncodedResourceCryptographicDigest::Algorithm a, EncodedResourceCryptographicDigest::Algorithm b)
{
if (a == b)
return WTF::nullopt;
return (a > b) ? a : b;
}
static Vector<EncodedResourceCryptographicDigest> strongestMetadataFromSet(Vector<EncodedResourceCryptographicDigest>&& set)
{
Vector<EncodedResourceCryptographicDigest> result;
auto strongest = EncodedResourceCryptographicDigest::Algorithm::SHA256;
for (auto& item : set) {
if (result.isEmpty()) {
strongest = item.algorithm;
result.append(WTFMove(item));
continue;
}
auto currentAlgorithm = strongest;
auto newAlgorithm = item.algorithm;
auto priority = prioritizedHashFunction(currentAlgorithm, newAlgorithm);
if (!priority)
result.append(WTFMove(item));
else if (priority.value() == newAlgorithm) {
strongest = item.algorithm;
result.clear();
result.append(WTFMove(item));
}
}
return result;
}
bool matchIntegrityMetadata(const CachedResource& resource, const String& integrityMetadataList)
{
auto parsedMetadata = parseIntegrityMetadata(integrityMetadataList);
if (!parsedMetadata)
return true;
if (!isResponseEligible(resource))
return false;
if (parsedMetadata->isEmpty())
return true;
auto metadata = strongestMetadataFromSet(WTFMove(*parsedMetadata));
const auto* sharedBuffer = resource.resourceBuffer();
for (auto& item : metadata) {
auto algorithm = item.algorithm;
auto expectedValue = decodeEncodedResourceCryptographicDigest(item);
auto actualValue = cryptographicDigestForBytes(algorithm, sharedBuffer ? sharedBuffer->data() : nullptr, sharedBuffer ? sharedBuffer->size() : 0);
if (expectedValue && actualValue.value == expectedValue->value)
return true;
}
return false;
}
String integrityMismatchDescription(const CachedResource& resource, const String& integrityMetadata)
{
StringBuilder builder;
builder.append(resource.url().stringCenterEllipsizedToLength());
builder.append(". Failed integrity metadata check. ");
builder.append("Content length: ");
if (auto* resourceBuffer = resource.resourceBuffer())
builder.appendNumber(resourceBuffer->size());
else
builder.append("(no content)");
builder.append(", Expected content length: ");
builder.appendNumber(resource.response().expectedContentLength());
builder.append(", Expected metadata: ");
builder.append(integrityMetadata);
return builder.toString();
}
}