fune/toolkit/components/url-classifier/ProtocolParser.cpp
Phil Ringnalda 35869db574 Backed out 5 changesets (bug 1305801) for ASan gtest bustage
Backed out changeset 0c95d5dec6d9 (bug 1305801)
Backed out changeset bca0e706dbc5 (bug 1305801)
Backed out changeset def8da367beb (bug 1305801)
Backed out changeset 56ceae52d847 (bug 1305801)
Backed out changeset 14457cc4c325 (bug 1305801)
2016-10-03 22:14:49 -07:00

980 lines
28 KiB
C++

//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ProtocolParser.h"
#include "LookupCache.h"
#include "nsNetCID.h"
#include "mozilla/Logging.h"
#include "prnetdb.h"
#include "prprf.h"
#include "nsUrlClassifierUtils.h"
#include "nsPrintfCString.h"
#include "mozilla/Base64.h"
// MOZ_LOG=UrlClassifierProtocolParser:5
mozilla::LazyLogModule gUrlClassifierProtocolParserLog("UrlClassifierProtocolParser");
#define PARSER_LOG(args) MOZ_LOG(gUrlClassifierProtocolParserLog, mozilla::LogLevel::Debug, args)
namespace mozilla {
namespace safebrowsing {
// Updates will fail if fed chunks larger than this
const uint32_t MAX_CHUNK_SIZE = (1024 * 1024);
// Updates will fail if the total number of tocuhed chunks is larger than this
const uint32_t MAX_CHUNK_RANGE = 1000000;
const uint32_t DOMAIN_SIZE = 4;
// Parse one stringified range of chunks of the form "n" or "n-m" from a
// comma-separated list of chunks. Upon return, 'begin' will point to the
// next range of chunks in the list of chunks.
static bool
ParseChunkRange(nsACString::const_iterator& aBegin,
const nsACString::const_iterator& aEnd,
uint32_t* aFirst, uint32_t* aLast)
{
nsACString::const_iterator iter = aBegin;
FindCharInReadable(',', iter, aEnd);
nsAutoCString element(Substring(aBegin, iter));
aBegin = iter;
if (aBegin != aEnd)
aBegin++;
uint32_t numRead = PR_sscanf(element.get(), "%u-%u", aFirst, aLast);
if (numRead == 2) {
if (*aFirst > *aLast) {
uint32_t tmp = *aFirst;
*aFirst = *aLast;
*aLast = tmp;
}
return true;
}
if (numRead == 1) {
*aLast = *aFirst;
return true;
}
return false;
}
///////////////////////////////////////////////////////////////
// ProtocolParser implementation
ProtocolParser::ProtocolParser()
: mUpdateStatus(NS_OK)
{
}
ProtocolParser::~ProtocolParser()
{
CleanupUpdates();
}
nsresult
ProtocolParser::Init(nsICryptoHash* aHasher)
{
mCryptoHash = aHasher;
return NS_OK;
}
void
ProtocolParser::CleanupUpdates()
{
for (uint32_t i = 0; i < mTableUpdates.Length(); i++) {
delete mTableUpdates[i];
}
mTableUpdates.Clear();
}
TableUpdate *
ProtocolParser::GetTableUpdate(const nsACString& aTable)
{
for (uint32_t i = 0; i < mTableUpdates.Length(); i++) {
if (aTable.Equals(mTableUpdates[i]->TableName())) {
return mTableUpdates[i];
}
}
// We free automatically on destruction, ownership of these
// updates can be transferred to DBServiceWorker, which passes
// them back to Classifier when doing the updates, and that
// will free them.
TableUpdate *update = CreateTableUpdate(aTable);
mTableUpdates.AppendElement(update);
return update;
}
///////////////////////////////////////////////////////////////////////
// ProtocolParserV2
ProtocolParserV2::ProtocolParserV2()
: mState(PROTOCOL_STATE_CONTROL)
, mUpdateWait(0)
, mResetRequested(false)
, mTableUpdate(nullptr)
{
}
ProtocolParserV2::~ProtocolParserV2()
{
}
void
ProtocolParserV2::SetCurrentTable(const nsACString& aTable)
{
auto update = GetTableUpdate(aTable);
mTableUpdate = TableUpdate::Cast<TableUpdateV2>(update);
}
nsresult
ProtocolParserV2::AppendStream(const nsACString& aData)
{
if (NS_FAILED(mUpdateStatus))
return mUpdateStatus;
nsresult rv;
mPending.Append(aData);
bool done = false;
while (!done) {
if (mState == PROTOCOL_STATE_CONTROL) {
rv = ProcessControl(&done);
} else if (mState == PROTOCOL_STATE_CHUNK) {
rv = ProcessChunk(&done);
} else {
NS_ERROR("Unexpected protocol state");
rv = NS_ERROR_FAILURE;
}
if (NS_FAILED(rv)) {
mUpdateStatus = rv;
return rv;
}
}
return NS_OK;
}
void
ProtocolParserV2::End()
{
// Inbound data has already been processed in every AppendStream() call.
}
nsresult
ProtocolParserV2::ProcessControl(bool* aDone)
{
nsresult rv;
nsAutoCString line;
*aDone = true;
while (NextLine(line)) {
PARSER_LOG(("Processing %s\n", line.get()));
if (StringBeginsWith(line, NS_LITERAL_CSTRING("i:"))) {
// Set the table name from the table header line.
SetCurrentTable(Substring(line, 2));
} else if (StringBeginsWith(line, NS_LITERAL_CSTRING("n:"))) {
if (PR_sscanf(line.get(), "n:%d", &mUpdateWait) != 1) {
PARSER_LOG(("Error parsing n: '%s' (%d)", line.get(), mUpdateWait));
return NS_ERROR_FAILURE;
}
} else if (line.EqualsLiteral("r:pleasereset")) {
mResetRequested = true;
} else if (StringBeginsWith(line, NS_LITERAL_CSTRING("u:"))) {
rv = ProcessForward(line);
NS_ENSURE_SUCCESS(rv, rv);
} else if (StringBeginsWith(line, NS_LITERAL_CSTRING("a:")) ||
StringBeginsWith(line, NS_LITERAL_CSTRING("s:"))) {
rv = ProcessChunkControl(line);
NS_ENSURE_SUCCESS(rv, rv);
*aDone = false;
return NS_OK;
} else if (StringBeginsWith(line, NS_LITERAL_CSTRING("ad:")) ||
StringBeginsWith(line, NS_LITERAL_CSTRING("sd:"))) {
rv = ProcessExpirations(line);
NS_ENSURE_SUCCESS(rv, rv);
}
}
*aDone = true;
return NS_OK;
}
nsresult
ProtocolParserV2::ProcessExpirations(const nsCString& aLine)
{
if (!mTableUpdate) {
NS_WARNING("Got an expiration without a table.");
return NS_ERROR_FAILURE;
}
const nsCSubstring &list = Substring(aLine, 3);
nsACString::const_iterator begin, end;
list.BeginReading(begin);
list.EndReading(end);
while (begin != end) {
uint32_t first, last;
if (ParseChunkRange(begin, end, &first, &last)) {
if (last < first) return NS_ERROR_FAILURE;
if (last - first > MAX_CHUNK_RANGE) return NS_ERROR_FAILURE;
for (uint32_t num = first; num <= last; num++) {
if (aLine[0] == 'a') {
nsresult rv = mTableUpdate->NewAddExpiration(num);
if (NS_FAILED(rv)) {
return rv;
}
} else {
nsresult rv = mTableUpdate->NewSubExpiration(num);
if (NS_FAILED(rv)) {
return rv;
}
}
}
} else {
return NS_ERROR_FAILURE;
}
}
return NS_OK;
}
nsresult
ProtocolParserV2::ProcessChunkControl(const nsCString& aLine)
{
if (!mTableUpdate) {
NS_WARNING("Got a chunk before getting a table.");
return NS_ERROR_FAILURE;
}
mState = PROTOCOL_STATE_CHUNK;
char command;
mChunkState.Clear();
if (PR_sscanf(aLine.get(),
"%c:%d:%d:%d",
&command,
&mChunkState.num, &mChunkState.hashSize, &mChunkState.length)
!= 4)
{
NS_WARNING(("PR_sscanf failed"));
return NS_ERROR_FAILURE;
}
if (mChunkState.length > MAX_CHUNK_SIZE) {
NS_WARNING("Invalid length specified in update.");
return NS_ERROR_FAILURE;
}
if (!(mChunkState.hashSize == PREFIX_SIZE || mChunkState.hashSize == COMPLETE_SIZE)) {
NS_WARNING("Invalid hash size specified in update.");
return NS_ERROR_FAILURE;
}
if (StringEndsWith(mTableUpdate->TableName(),
NS_LITERAL_CSTRING("-shavar")) ||
StringEndsWith(mTableUpdate->TableName(),
NS_LITERAL_CSTRING("-simple"))) {
// Accommodate test tables ending in -simple for now.
mChunkState.type = (command == 'a') ? CHUNK_ADD : CHUNK_SUB;
} else if (StringEndsWith(mTableUpdate->TableName(),
NS_LITERAL_CSTRING("-digest256"))) {
mChunkState.type = (command == 'a') ? CHUNK_ADD_DIGEST : CHUNK_SUB_DIGEST;
}
nsresult rv;
switch (mChunkState.type) {
case CHUNK_ADD:
rv = mTableUpdate->NewAddChunk(mChunkState.num);
if (NS_FAILED(rv)) {
return rv;
}
break;
case CHUNK_SUB:
rv = mTableUpdate->NewSubChunk(mChunkState.num);
if (NS_FAILED(rv)) {
return rv;
}
break;
case CHUNK_ADD_DIGEST:
rv = mTableUpdate->NewAddChunk(mChunkState.num);
if (NS_FAILED(rv)) {
return rv;
}
break;
case CHUNK_SUB_DIGEST:
rv = mTableUpdate->NewSubChunk(mChunkState.num);
if (NS_FAILED(rv)) {
return rv;
}
break;
}
return NS_OK;
}
nsresult
ProtocolParserV2::ProcessForward(const nsCString& aLine)
{
const nsCSubstring &forward = Substring(aLine, 2);
return AddForward(forward);
}
nsresult
ProtocolParserV2::AddForward(const nsACString& aUrl)
{
if (!mTableUpdate) {
NS_WARNING("Forward without a table name.");
return NS_ERROR_FAILURE;
}
ForwardedUpdate *forward = mForwards.AppendElement();
forward->table = mTableUpdate->TableName();
forward->url.Assign(aUrl);
return NS_OK;
}
nsresult
ProtocolParserV2::ProcessChunk(bool* aDone)
{
if (!mTableUpdate) {
NS_WARNING("Processing chunk without an active table.");
return NS_ERROR_FAILURE;
}
NS_ASSERTION(mChunkState.num != 0, "Must have a chunk number.");
if (mPending.Length() < mChunkState.length) {
*aDone = true;
return NS_OK;
}
// Pull the chunk out of the pending stream data.
nsAutoCString chunk;
chunk.Assign(Substring(mPending, 0, mChunkState.length));
mPending.Cut(0, mChunkState.length);
*aDone = false;
mState = PROTOCOL_STATE_CONTROL;
if (StringEndsWith(mTableUpdate->TableName(),
NS_LITERAL_CSTRING("-shavar"))) {
return ProcessShaChunk(chunk);
}
if (StringEndsWith(mTableUpdate->TableName(),
NS_LITERAL_CSTRING("-digest256"))) {
return ProcessDigestChunk(chunk);
}
return ProcessPlaintextChunk(chunk);
}
/**
* Process a plaintext chunk (currently only used in unit tests).
*/
nsresult
ProtocolParserV2::ProcessPlaintextChunk(const nsACString& aChunk)
{
if (!mTableUpdate) {
NS_WARNING("Chunk received with no table.");
return NS_ERROR_FAILURE;
}
PARSER_LOG(("Handling a %d-byte simple chunk", aChunk.Length()));
nsTArray<nsCString> lines;
ParseString(PromiseFlatCString(aChunk), '\n', lines);
// non-hashed tables need to be hashed
for (uint32_t i = 0; i < lines.Length(); i++) {
nsCString& line = lines[i];
if (mChunkState.type == CHUNK_ADD) {
if (mChunkState.hashSize == COMPLETE_SIZE) {
Completion hash;
hash.FromPlaintext(line, mCryptoHash);
nsresult rv = mTableUpdate->NewAddComplete(mChunkState.num, hash);
if (NS_FAILED(rv)) {
return rv;
}
} else {
NS_ASSERTION(mChunkState.hashSize == 4, "Only 32- or 4-byte hashes can be used for add chunks.");
Prefix hash;
hash.FromPlaintext(line, mCryptoHash);
nsresult rv = mTableUpdate->NewAddPrefix(mChunkState.num, hash);
if (NS_FAILED(rv)) {
return rv;
}
}
} else {
nsCString::const_iterator begin, iter, end;
line.BeginReading(begin);
line.EndReading(end);
iter = begin;
uint32_t addChunk;
if (!FindCharInReadable(':', iter, end) ||
PR_sscanf(lines[i].get(), "%d:", &addChunk) != 1) {
NS_WARNING("Received sub chunk without associated add chunk.");
return NS_ERROR_FAILURE;
}
iter++;
if (mChunkState.hashSize == COMPLETE_SIZE) {
Completion hash;
hash.FromPlaintext(Substring(iter, end), mCryptoHash);
nsresult rv = mTableUpdate->NewSubComplete(addChunk, hash, mChunkState.num);
if (NS_FAILED(rv)) {
return rv;
}
} else {
NS_ASSERTION(mChunkState.hashSize == 4, "Only 32- or 4-byte hashes can be used for add chunks.");
Prefix hash;
hash.FromPlaintext(Substring(iter, end), mCryptoHash);
nsresult rv = mTableUpdate->NewSubPrefix(addChunk, hash, mChunkState.num);
if (NS_FAILED(rv)) {
return rv;
}
}
}
}
return NS_OK;
}
nsresult
ProtocolParserV2::ProcessShaChunk(const nsACString& aChunk)
{
uint32_t start = 0;
while (start < aChunk.Length()) {
// First four bytes are the domain key.
Prefix domain;
domain.Assign(Substring(aChunk, start, DOMAIN_SIZE));
start += DOMAIN_SIZE;
// Then a count of entries.
uint8_t numEntries = static_cast<uint8_t>(aChunk[start]);
start++;
PARSER_LOG(("Handling a %d-byte shavar chunk containing %u entries"
" for domain %X", aChunk.Length(), numEntries,
domain.ToUint32()));
nsresult rv;
if (mChunkState.type == CHUNK_ADD && mChunkState.hashSize == PREFIX_SIZE) {
rv = ProcessHostAdd(domain, numEntries, aChunk, &start);
} else if (mChunkState.type == CHUNK_ADD && mChunkState.hashSize == COMPLETE_SIZE) {
rv = ProcessHostAddComplete(numEntries, aChunk, &start);
} else if (mChunkState.type == CHUNK_SUB && mChunkState.hashSize == PREFIX_SIZE) {
rv = ProcessHostSub(domain, numEntries, aChunk, &start);
} else if (mChunkState.type == CHUNK_SUB && mChunkState.hashSize == COMPLETE_SIZE) {
rv = ProcessHostSubComplete(numEntries, aChunk, &start);
} else {
NS_WARNING("Unexpected chunk type/hash size!");
PARSER_LOG(("Got an unexpected chunk type/hash size: %s:%d",
mChunkState.type == CHUNK_ADD ? "add" : "sub",
mChunkState.hashSize));
return NS_ERROR_FAILURE;
}
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
ProtocolParserV2::ProcessDigestChunk(const nsACString& aChunk)
{
PARSER_LOG(("Handling a %d-byte digest256 chunk", aChunk.Length()));
if (mChunkState.type == CHUNK_ADD_DIGEST) {
return ProcessDigestAdd(aChunk);
}
if (mChunkState.type == CHUNK_SUB_DIGEST) {
return ProcessDigestSub(aChunk);
}
return NS_ERROR_UNEXPECTED;
}
nsresult
ProtocolParserV2::ProcessDigestAdd(const nsACString& aChunk)
{
// The ABNF format for add chunks is (HASH)+, where HASH is 32 bytes.
MOZ_ASSERT(aChunk.Length() % 32 == 0,
"Chunk length in bytes must be divisible by 4");
uint32_t start = 0;
while (start < aChunk.Length()) {
Completion hash;
hash.Assign(Substring(aChunk, start, COMPLETE_SIZE));
start += COMPLETE_SIZE;
nsresult rv = mTableUpdate->NewAddComplete(mChunkState.num, hash);
if (NS_FAILED(rv)) {
return rv;
}
}
return NS_OK;
}
nsresult
ProtocolParserV2::ProcessDigestSub(const nsACString& aChunk)
{
// The ABNF format for sub chunks is (ADDCHUNKNUM HASH)+, where ADDCHUNKNUM
// is a 4 byte chunk number, and HASH is 32 bytes.
MOZ_ASSERT(aChunk.Length() % 36 == 0,
"Chunk length in bytes must be divisible by 36");
uint32_t start = 0;
while (start < aChunk.Length()) {
// Read ADDCHUNKNUM
const nsCSubstring& addChunkStr = Substring(aChunk, start, 4);
start += 4;
uint32_t addChunk;
memcpy(&addChunk, addChunkStr.BeginReading(), 4);
addChunk = PR_ntohl(addChunk);
// Read the hash
Completion hash;
hash.Assign(Substring(aChunk, start, COMPLETE_SIZE));
start += COMPLETE_SIZE;
nsresult rv = mTableUpdate->NewSubComplete(addChunk, hash, mChunkState.num);
if (NS_FAILED(rv)) {
return rv;
}
}
return NS_OK;
}
nsresult
ProtocolParserV2::ProcessHostAdd(const Prefix& aDomain, uint8_t aNumEntries,
const nsACString& aChunk, uint32_t* aStart)
{
NS_ASSERTION(mChunkState.hashSize == PREFIX_SIZE,
"ProcessHostAdd should only be called for prefix hashes.");
if (aNumEntries == 0) {
nsresult rv = mTableUpdate->NewAddPrefix(mChunkState.num, aDomain);
if (NS_FAILED(rv)) {
return rv;
}
return NS_OK;
}
if (*aStart + (PREFIX_SIZE * aNumEntries) > aChunk.Length()) {
NS_WARNING("Chunk is not long enough to contain the expected entries.");
return NS_ERROR_FAILURE;
}
for (uint8_t i = 0; i < aNumEntries; i++) {
Prefix hash;
hash.Assign(Substring(aChunk, *aStart, PREFIX_SIZE));
PARSER_LOG(("Add prefix %X", hash.ToUint32()));
nsresult rv = mTableUpdate->NewAddPrefix(mChunkState.num, hash);
if (NS_FAILED(rv)) {
return rv;
}
*aStart += PREFIX_SIZE;
}
return NS_OK;
}
nsresult
ProtocolParserV2::ProcessHostSub(const Prefix& aDomain, uint8_t aNumEntries,
const nsACString& aChunk, uint32_t *aStart)
{
NS_ASSERTION(mChunkState.hashSize == PREFIX_SIZE,
"ProcessHostSub should only be called for prefix hashes.");
if (aNumEntries == 0) {
if ((*aStart) + 4 > aChunk.Length()) {
NS_WARNING("Received a zero-entry sub chunk without an associated add.");
return NS_ERROR_FAILURE;
}
const nsCSubstring& addChunkStr = Substring(aChunk, *aStart, 4);
*aStart += 4;
uint32_t addChunk;
memcpy(&addChunk, addChunkStr.BeginReading(), 4);
addChunk = PR_ntohl(addChunk);
PARSER_LOG(("Sub prefix (addchunk=%u)", addChunk));
nsresult rv = mTableUpdate->NewSubPrefix(addChunk, aDomain, mChunkState.num);
if (NS_FAILED(rv)) {
return rv;
}
return NS_OK;
}
if (*aStart + ((PREFIX_SIZE + 4) * aNumEntries) > aChunk.Length()) {
NS_WARNING("Chunk is not long enough to contain the expected entries.");
return NS_ERROR_FAILURE;
}
for (uint8_t i = 0; i < aNumEntries; i++) {
const nsCSubstring& addChunkStr = Substring(aChunk, *aStart, 4);
*aStart += 4;
uint32_t addChunk;
memcpy(&addChunk, addChunkStr.BeginReading(), 4);
addChunk = PR_ntohl(addChunk);
Prefix prefix;
prefix.Assign(Substring(aChunk, *aStart, PREFIX_SIZE));
*aStart += PREFIX_SIZE;
PARSER_LOG(("Sub prefix %X (addchunk=%u)", prefix.ToUint32(), addChunk));
nsresult rv = mTableUpdate->NewSubPrefix(addChunk, prefix, mChunkState.num);
if (NS_FAILED(rv)) {
return rv;
}
}
return NS_OK;
}
nsresult
ProtocolParserV2::ProcessHostAddComplete(uint8_t aNumEntries,
const nsACString& aChunk, uint32_t* aStart)
{
NS_ASSERTION(mChunkState.hashSize == COMPLETE_SIZE,
"ProcessHostAddComplete should only be called for complete hashes.");
if (aNumEntries == 0) {
// this is totally comprehensible.
// My sarcasm detector is going off!
NS_WARNING("Expected > 0 entries for a 32-byte hash add.");
return NS_OK;
}
if (*aStart + (COMPLETE_SIZE * aNumEntries) > aChunk.Length()) {
NS_WARNING("Chunk is not long enough to contain the expected entries.");
return NS_ERROR_FAILURE;
}
for (uint8_t i = 0; i < aNumEntries; i++) {
Completion hash;
hash.Assign(Substring(aChunk, *aStart, COMPLETE_SIZE));
nsresult rv = mTableUpdate->NewAddComplete(mChunkState.num, hash);
if (NS_FAILED(rv)) {
return rv;
}
*aStart += COMPLETE_SIZE;
}
return NS_OK;
}
nsresult
ProtocolParserV2::ProcessHostSubComplete(uint8_t aNumEntries,
const nsACString& aChunk, uint32_t* aStart)
{
NS_ASSERTION(mChunkState.hashSize == COMPLETE_SIZE,
"ProcessHostSubComplete should only be called for complete hashes.");
if (aNumEntries == 0) {
// this is totally comprehensible.
NS_WARNING("Expected > 0 entries for a 32-byte hash sub.");
return NS_OK;
}
if (*aStart + ((COMPLETE_SIZE + 4) * aNumEntries) > aChunk.Length()) {
NS_WARNING("Chunk is not long enough to contain the expected entries.");
return NS_ERROR_FAILURE;
}
for (uint8_t i = 0; i < aNumEntries; i++) {
Completion hash;
hash.Assign(Substring(aChunk, *aStart, COMPLETE_SIZE));
*aStart += COMPLETE_SIZE;
const nsCSubstring& addChunkStr = Substring(aChunk, *aStart, 4);
*aStart += 4;
uint32_t addChunk;
memcpy(&addChunk, addChunkStr.BeginReading(), 4);
addChunk = PR_ntohl(addChunk);
nsresult rv = mTableUpdate->NewSubComplete(addChunk, hash, mChunkState.num);
if (NS_FAILED(rv)) {
return rv;
}
}
return NS_OK;
}
bool
ProtocolParserV2::NextLine(nsACString& aLine)
{
int32_t newline = mPending.FindChar('\n');
if (newline == kNotFound) {
return false;
}
aLine.Assign(Substring(mPending, 0, newline));
mPending.Cut(0, newline + 1);
return true;
}
TableUpdate*
ProtocolParserV2::CreateTableUpdate(const nsACString& aTableName) const
{
return new TableUpdateV2(aTableName);
}
///////////////////////////////////////////////////////////////////////
// ProtocolParserProtobuf
ProtocolParserProtobuf::ProtocolParserProtobuf()
{
}
ProtocolParserProtobuf::~ProtocolParserProtobuf()
{
}
void
ProtocolParserProtobuf::SetCurrentTable(const nsACString& aTable)
{
// Should never occur.
MOZ_ASSERT_UNREACHABLE("SetCurrentTable shouldn't be called");
}
TableUpdate*
ProtocolParserProtobuf::CreateTableUpdate(const nsACString& aTableName) const
{
return new TableUpdateV4(aTableName);
}
nsresult
ProtocolParserProtobuf::AppendStream(const nsACString& aData)
{
// Protobuf data cannot be parsed progressively. Just save the incoming data.
mPending.Append(aData);
return NS_OK;
}
void
ProtocolParserProtobuf::End()
{
// mUpdateStatus will be updated to success as long as not all
// the responses are invalid.
mUpdateStatus = NS_ERROR_FAILURE;
FetchThreatListUpdatesResponse response;
if (!response.ParseFromArray(mPending.get(), mPending.Length())) {
NS_WARNING("ProtocolParserProtobuf failed parsing data.");
return;
}
for (int i = 0; i < response.list_update_responses_size(); i++) {
auto r = response.list_update_responses(i);
nsresult rv = ProcessOneResponse(r);
if (NS_SUCCEEDED(rv)) {
mUpdateStatus = rv;
} else {
NS_WARNING("Failed to process one response.");
}
}
}
// Save state of |aListName| to the following pref:
//
// "browser.safebrowsing.provider.google4.state.[aListName]"
//
static nsresult
SaveStateToPref(const nsACString& aListName, const nsACString& aState)
{
nsresult rv;
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
NS_ENSURE_SUCCESS(rv, rv);
nsCString prefName("browser.safebrowsing.provider.google4.state.");
prefName.Append(aListName);
nsCString stateBase64;
rv = Base64Encode(aState, stateBase64);
NS_ENSURE_SUCCESS(rv, rv);
return prefs->SetCharPref(prefName.get(), stateBase64.get());
}
nsresult
ProtocolParserProtobuf::ProcessOneResponse(const ListUpdateResponse& aResponse)
{
// A response must have a threat type.
if (!aResponse.has_threat_type()) {
NS_WARNING("Threat type not initialized. This seems to be an invalid response.");
return NS_ERROR_FAILURE;
}
// Convert threat type to list name.
nsCOMPtr<nsIUrlClassifierUtils> urlUtil =
do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);
nsCString possibleListNames;
nsresult rv = urlUtil->ConvertThreatTypeToListNames(aResponse.threat_type(),
possibleListNames);
if (NS_FAILED(rv)) {
PARSER_LOG((nsPrintfCString("Threat type to list name conversion error: %d",
aResponse.threat_type())).get());
return NS_ERROR_FAILURE;
}
// Match the table name we received with one of the ones we requested.
// We ignore the case where a threat type matches more than one list
// per provider and return the first one. See bug 1287059."
nsCString listName;
nsTArray<nsCString> possibleListNameArray;
Classifier::SplitTables(possibleListNames, possibleListNameArray);
for (auto possibleName : possibleListNameArray) {
if (mRequestedTables.Contains(possibleName)) {
listName = possibleName;
break;
}
}
if (listName.IsEmpty()) {
PARSER_LOG(("We received an update for a list we didn't ask for. Ignoring it."));
return NS_ERROR_FAILURE;
}
// Test if this is a full update.
bool isFullUpdate = false;
if (aResponse.has_response_type()) {
isFullUpdate =
aResponse.response_type() == ListUpdateResponse::FULL_UPDATE;
} else {
NS_WARNING("Response type not initialized.");
return NS_ERROR_FAILURE;
}
// Warn if there's no new state.
if (!aResponse.has_new_client_state()) {
NS_WARNING("New state not initialized.");
return NS_ERROR_FAILURE;
}
auto tu = GetTableUpdate(nsCString(listName.get()));
auto tuV4 = TableUpdate::Cast<TableUpdateV4>(tu);
NS_ENSURE_TRUE(tuV4, NS_ERROR_FAILURE);
// See Bug 1287059. We save the state to prefs until we support
// "saving states to HashStore".
nsCString state(aResponse.new_client_state().c_str(),
aResponse.new_client_state().size());
NS_DispatchToMainThread(NS_NewRunnableFunction([listName, state] () {
DebugOnly<nsresult> rv = SaveStateToPref(listName, state);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SaveStateToPref failed");
}));
PARSER_LOG(("==== Update for threat type '%d' ====", aResponse.threat_type()));
PARSER_LOG(("* listName: %s\n", listName.get()));
PARSER_LOG(("* newState: %s\n", aResponse.new_client_state().c_str()));
PARSER_LOG(("* isFullUpdate: %s\n", (isFullUpdate ? "yes" : "no")));
ProcessAdditionOrRemoval(*tuV4, aResponse.additions(), true /*aIsAddition*/);
ProcessAdditionOrRemoval(*tuV4, aResponse.removals(), false);
PARSER_LOG(("\n\n"));
return NS_OK;
}
nsresult
ProtocolParserProtobuf::ProcessAdditionOrRemoval(TableUpdateV4& aTableUpdate,
const ThreatEntrySetList& aUpdate,
bool aIsAddition)
{
nsresult ret = NS_OK;
for (int i = 0; i < aUpdate.size(); i++) {
auto update = aUpdate.Get(i);
if (!update.has_compression_type()) {
NS_WARNING(nsPrintfCString("%s with no compression type.",
aIsAddition ? "Addition" : "Removal").get());
continue;
}
switch (update.compression_type()) {
case COMPRESSION_TYPE_UNSPECIFIED:
NS_WARNING("Unspecified compression type.");
break;
case RAW:
ret = (aIsAddition ? ProcessRawAddition(aTableUpdate, update)
: ProcessRawRemoval(aTableUpdate, update));
break;
case RICE:
// Not implemented yet (see bug 1285848),
NS_WARNING("Encoded table update is not supported yet.");
break;
}
}
return ret;
}
nsresult
ProtocolParserProtobuf::ProcessRawAddition(TableUpdateV4& aTableUpdate,
const ThreatEntrySet& aAddition)
{
if (!aAddition.has_raw_hashes()) {
PARSER_LOG(("* No raw addition."));
return NS_OK;
}
auto rawHashes = aAddition.raw_hashes();
if (!rawHashes.has_prefix_size()) {
NS_WARNING("Raw hash has no prefix size");
return NS_OK;
}
auto prefixes = rawHashes.raw_hashes();
if (4 == rawHashes.prefix_size()) {
// Process fixed length prefixes separately.
uint32_t* fixedLengthPrefixes = (uint32_t*)prefixes.c_str();
size_t numOfFixedLengthPrefixes = prefixes.size() / 4;
PARSER_LOG(("* Raw addition (4 bytes)"));
PARSER_LOG((" - # of prefixes: %d", numOfFixedLengthPrefixes));
PARSER_LOG((" - Memory address: 0x%p", fixedLengthPrefixes));
} else {
// TODO: Process variable length prefixes including full hashes.
// See Bug 1283009.
PARSER_LOG((" Raw addition (%d bytes)", rawHashes.prefix_size()));
}
if (!rawHashes.mutable_raw_hashes()) {
PARSER_LOG(("Unable to get mutable raw hashes. Can't perform a string move."));
return NS_ERROR_FAILURE;
}
aTableUpdate.NewPrefixes(rawHashes.prefix_size(),
*rawHashes.mutable_raw_hashes());
return NS_OK;
}
nsresult
ProtocolParserProtobuf::ProcessRawRemoval(TableUpdateV4& aTableUpdate,
const ThreatEntrySet& aRemoval)
{
if (!aRemoval.has_raw_indices()) {
NS_WARNING("A removal has no indices.");
return NS_OK;
}
// indices is an array of int32.
auto indices = aRemoval.raw_indices().indices();
PARSER_LOG(("* Raw removal"));
PARSER_LOG((" - # of removal: %d", indices.size()));
aTableUpdate.NewRemovalIndices((const uint32_t*)indices.data(),
indices.size());
return NS_OK;
}
} // namespace safebrowsing
} // namespace mozilla