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"
namespace WebCore {
namespace {
template<typename CharacterType>
static bool isVCHAR(CharacterType c)
{
return c >= 0x21 && c <= 0x7e;
}
template<typename CharacterType>
struct IntegrityMetadataParser {
public:
IntegrityMetadataParser(std::optional<Vector<EncodedResourceCryptographicDigest>>& digests)
: m_digests(digests)
{
}
bool operator()(const CharacterType*& position, const CharacterType* end)
{
if (!m_digests)
m_digests = Vector<EncodedResourceCryptographicDigest> { };
auto digest = parseEncodedCryptographicDigest(position, end);
if (!digest)
return false;
if (skipExactly<CharacterType>(position, end, '?'))
skipWhile<CharacterType, isVCHAR>(position, end);
if (position != end && !isHTMLSpace(*position))
return false;
m_digests->append(WTFMove(*digest));
return true;
}
private:
std::optional<Vector<EncodedResourceCryptographicDigest>>& m_digests;
};
}
template <typename CharacterType, typename Functor>
static inline void splitOnSpaces(const CharacterType* begin, const CharacterType* end, Functor&& functor)
{
const CharacterType* position = begin;
skipWhile<CharacterType, isHTMLSpace>(position, end);
while (position < end) {
if (!functor(position, end))
skipWhile<CharacterType, isNotHTMLSpace>(position, end);
skipWhile<CharacterType, isHTMLSpace>(position, end);
}
}
static std::optional<Vector<EncodedResourceCryptographicDigest>> parseIntegrityMetadata(const String& integrityMetadata)
{
if (integrityMetadata.isEmpty())
return std::nullopt;
std::optional<Vector<EncodedResourceCryptographicDigest>> result;
const StringImpl& stringImpl = *integrityMetadata.impl();
if (stringImpl.is8Bit())
splitOnSpaces(stringImpl.characters8(), stringImpl.characters8() + stringImpl.length(), IntegrityMetadataParser<LChar> { result });
else
splitOnSpaces(stringImpl.characters16(), stringImpl.characters16() + stringImpl.length(), IntegrityMetadataParser<UChar> { result });
return result;
}
static bool isResponseEligible(const CachedResource& resource)
{
return resource.isCORSSameOrigin();
}
static std::optional<EncodedResourceCryptographicDigest::Algorithm> prioritizedHashFunction(EncodedResourceCryptographicDigest::Algorithm a, EncodedResourceCryptographicDigest::Algorithm b)
{
if (a == b)
return std::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;
}
}