/src/mozilla-central/toolkit/components/url-classifier/ProtocolParser.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
3 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
4 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
5 | | |
6 | | #include "ProtocolParser.h" |
7 | | #include "LookupCache.h" |
8 | | #include "nsNetCID.h" |
9 | | #include "mozilla/Logging.h" |
10 | | #include "prnetdb.h" |
11 | | #include "prprf.h" |
12 | | |
13 | | #include "nsUrlClassifierDBService.h" |
14 | | #include "nsUrlClassifierUtils.h" |
15 | | #include "nsPrintfCString.h" |
16 | | #include "mozilla/Base64.h" |
17 | | #include "RiceDeltaDecoder.h" |
18 | | #include "mozilla/EndianUtils.h" |
19 | | #include "mozilla/ErrorNames.h" |
20 | | #include "mozilla/IntegerPrintfMacros.h" |
21 | | |
22 | | // MOZ_LOG=UrlClassifierProtocolParser:5 |
23 | | mozilla::LazyLogModule gUrlClassifierProtocolParserLog("UrlClassifierProtocolParser"); |
24 | 0 | #define PARSER_LOG(args) MOZ_LOG(gUrlClassifierProtocolParserLog, mozilla::LogLevel::Debug, args) |
25 | | |
26 | | namespace mozilla { |
27 | | namespace safebrowsing { |
28 | | |
29 | | // Updates will fail if fed chunks larger than this |
30 | | const uint32_t MAX_CHUNK_SIZE = (1024 * 1024); |
31 | | // Updates will fail if the total number of tocuhed chunks is larger than this |
32 | | const uint32_t MAX_CHUNK_RANGE = 1000000; |
33 | | |
34 | | const uint32_t DOMAIN_SIZE = 4; |
35 | | |
36 | | // Parse one stringified range of chunks of the form "n" or "n-m" from a |
37 | | // comma-separated list of chunks. Upon return, 'begin' will point to the |
38 | | // next range of chunks in the list of chunks. |
39 | | static bool |
40 | | ParseChunkRange(nsACString::const_iterator& aBegin, |
41 | | const nsACString::const_iterator& aEnd, |
42 | | uint32_t* aFirst, uint32_t* aLast) |
43 | 0 | { |
44 | 0 | nsACString::const_iterator iter = aBegin; |
45 | 0 | FindCharInReadable(',', iter, aEnd); |
46 | 0 |
|
47 | 0 | nsAutoCString element(Substring(aBegin, iter)); |
48 | 0 | aBegin = iter; |
49 | 0 | if (aBegin != aEnd) |
50 | 0 | aBegin++; |
51 | 0 |
|
52 | 0 | uint32_t numRead = PR_sscanf(element.get(), "%u-%u", aFirst, aLast); |
53 | 0 | if (numRead == 2) { |
54 | 0 | if (*aFirst > *aLast) { |
55 | 0 | uint32_t tmp = *aFirst; |
56 | 0 | *aFirst = *aLast; |
57 | 0 | *aLast = tmp; |
58 | 0 | } |
59 | 0 | return true; |
60 | 0 | } |
61 | 0 |
|
62 | 0 | if (numRead == 1) { |
63 | 0 | *aLast = *aFirst; |
64 | 0 | return true; |
65 | 0 | } |
66 | 0 | |
67 | 0 | return false; |
68 | 0 | } |
69 | | |
70 | | /////////////////////////////////////////////////////////////// |
71 | | // ProtocolParser implementation |
72 | | |
73 | | ProtocolParser::ProtocolParser() |
74 | | : mUpdateStatus(NS_OK) |
75 | | , mUpdateWaitSec(0) |
76 | 0 | { |
77 | 0 | } |
78 | | |
79 | | ProtocolParser::~ProtocolParser() |
80 | 0 | { |
81 | 0 | } |
82 | | |
83 | | nsresult |
84 | | ProtocolParser::Begin(const nsACString& aTable, |
85 | | const nsTArray<nsCString>& aUpdateTables) |
86 | 0 | { |
87 | 0 | // ProtocolParser objects should never be reused. |
88 | 0 | MOZ_ASSERT(mPending.IsEmpty()); |
89 | 0 | MOZ_ASSERT(mTableUpdates.IsEmpty()); |
90 | 0 | MOZ_ASSERT(mForwards.IsEmpty()); |
91 | 0 | MOZ_ASSERT(mRequestedTables.IsEmpty()); |
92 | 0 | MOZ_ASSERT(mTablesToReset.IsEmpty()); |
93 | 0 |
|
94 | 0 | if (!aTable.IsEmpty()) { |
95 | 0 | SetCurrentTable(aTable); |
96 | 0 | } |
97 | 0 | SetRequestedTables(aUpdateTables); |
98 | 0 |
|
99 | 0 | return NS_OK; |
100 | 0 | } |
101 | | |
102 | | RefPtr<TableUpdate> |
103 | | ProtocolParser::GetTableUpdate(const nsACString& aTable) |
104 | 0 | { |
105 | 0 | for (uint32_t i = 0; i < mTableUpdates.Length(); i++) { |
106 | 0 | if (aTable.Equals(mTableUpdates[i]->TableName())) { |
107 | 0 | return mTableUpdates[i]; |
108 | 0 | } |
109 | 0 | } |
110 | 0 |
|
111 | 0 | // We free automatically on destruction, ownership of these |
112 | 0 | // updates can be transferred to DBServiceWorker, which passes |
113 | 0 | // them back to Classifier when doing the updates, and that |
114 | 0 | // will free them. |
115 | 0 | RefPtr<TableUpdate> update = CreateTableUpdate(aTable); |
116 | 0 | mTableUpdates.AppendElement(update); |
117 | 0 | return update; |
118 | 0 | } |
119 | | |
120 | | /////////////////////////////////////////////////////////////////////// |
121 | | // ProtocolParserV2 |
122 | | |
123 | | ProtocolParserV2::ProtocolParserV2() |
124 | | : mState(PROTOCOL_STATE_CONTROL) |
125 | | , mTableUpdate(nullptr) |
126 | 0 | { |
127 | 0 | } |
128 | | |
129 | | ProtocolParserV2::~ProtocolParserV2() |
130 | 0 | { |
131 | 0 | } |
132 | | |
133 | | void |
134 | | ProtocolParserV2::SetCurrentTable(const nsACString& aTable) |
135 | 0 | { |
136 | 0 | RefPtr<TableUpdate> update = GetTableUpdate(aTable); |
137 | 0 | mTableUpdate = TableUpdate::Cast<TableUpdateV2>(update); |
138 | 0 | } |
139 | | |
140 | | nsresult |
141 | | ProtocolParserV2::AppendStream(const nsACString& aData) |
142 | 0 | { |
143 | 0 | if (NS_FAILED(mUpdateStatus)) |
144 | 0 | return mUpdateStatus; |
145 | 0 | |
146 | 0 | nsresult rv; |
147 | 0 | mPending.Append(aData); |
148 | 0 | #ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES |
149 | 0 | mRawUpdate.Append(aData); |
150 | 0 | #endif |
151 | 0 |
|
152 | 0 | bool done = false; |
153 | 0 | while (!done) { |
154 | 0 | if (nsUrlClassifierDBService::ShutdownHasStarted()) { |
155 | 0 | return NS_ERROR_ABORT; |
156 | 0 | } |
157 | 0 | |
158 | 0 | if (mState == PROTOCOL_STATE_CONTROL) { |
159 | 0 | rv = ProcessControl(&done); |
160 | 0 | } else if (mState == PROTOCOL_STATE_CHUNK) { |
161 | 0 | rv = ProcessChunk(&done); |
162 | 0 | } else { |
163 | 0 | NS_ERROR("Unexpected protocol state"); |
164 | 0 | rv = NS_ERROR_FAILURE; |
165 | 0 | } |
166 | 0 | if (NS_FAILED(rv)) { |
167 | 0 | mUpdateStatus = rv; |
168 | 0 | return rv; |
169 | 0 | } |
170 | 0 | } |
171 | 0 | return NS_OK; |
172 | 0 | } |
173 | | |
174 | | void |
175 | | ProtocolParserV2::End() |
176 | 0 | { |
177 | 0 | // Inbound data has already been processed in every AppendStream() call. |
178 | 0 | mTableUpdate = nullptr; |
179 | 0 | } |
180 | | |
181 | | nsresult |
182 | | ProtocolParserV2::ProcessControl(bool* aDone) |
183 | 0 | { |
184 | 0 | nsresult rv; |
185 | 0 |
|
186 | 0 | nsAutoCString line; |
187 | 0 | *aDone = true; |
188 | 0 | while (NextLine(line)) { |
189 | 0 | PARSER_LOG(("Processing %s\n", line.get())); |
190 | 0 |
|
191 | 0 | if (StringBeginsWith(line, NS_LITERAL_CSTRING("i:"))) { |
192 | 0 | // Set the table name from the table header line. |
193 | 0 | SetCurrentTable(Substring(line, 2)); |
194 | 0 | } else if (StringBeginsWith(line, NS_LITERAL_CSTRING("n:"))) { |
195 | 0 | if (PR_sscanf(line.get(), "n:%d", &mUpdateWaitSec) != 1) { |
196 | 0 | PARSER_LOG(("Error parsing n: '%s' (%d)", line.get(), mUpdateWaitSec)); |
197 | 0 | return NS_ERROR_FAILURE; |
198 | 0 | } |
199 | 0 | } else if (line.EqualsLiteral("r:pleasereset")) { |
200 | 0 | PARSER_LOG(("All tables will be reset.")); |
201 | 0 | mTablesToReset = mRequestedTables; |
202 | 0 | } else if (StringBeginsWith(line, NS_LITERAL_CSTRING("u:"))) { |
203 | 0 | rv = ProcessForward(line); |
204 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
205 | 0 | } else if (StringBeginsWith(line, NS_LITERAL_CSTRING("a:")) || |
206 | 0 | StringBeginsWith(line, NS_LITERAL_CSTRING("s:"))) { |
207 | 0 | rv = ProcessChunkControl(line); |
208 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
209 | 0 | *aDone = false; |
210 | 0 | return NS_OK; |
211 | 0 | } else if (StringBeginsWith(line, NS_LITERAL_CSTRING("ad:")) || |
212 | 0 | StringBeginsWith(line, NS_LITERAL_CSTRING("sd:"))) { |
213 | 0 | rv = ProcessExpirations(line); |
214 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
215 | 0 | } |
216 | 0 | } |
217 | 0 |
|
218 | 0 | *aDone = true; |
219 | 0 | return NS_OK; |
220 | 0 | } |
221 | | |
222 | | nsresult |
223 | | ProtocolParserV2::ProcessExpirations(const nsCString& aLine) |
224 | 0 | { |
225 | 0 | if (!mTableUpdate) { |
226 | 0 | NS_WARNING("Got an expiration without a table."); |
227 | 0 | return NS_ERROR_FAILURE; |
228 | 0 | } |
229 | 0 | const nsACString& list = Substring(aLine, 3); |
230 | 0 | nsACString::const_iterator begin, end; |
231 | 0 | list.BeginReading(begin); |
232 | 0 | list.EndReading(end); |
233 | 0 | while (begin != end) { |
234 | 0 | uint32_t first, last; |
235 | 0 | if (ParseChunkRange(begin, end, &first, &last)) { |
236 | 0 | if (last < first) return NS_ERROR_FAILURE; |
237 | 0 | if (last - first > MAX_CHUNK_RANGE) return NS_ERROR_FAILURE; |
238 | 0 | for (uint32_t num = first; num <= last; num++) { |
239 | 0 | if (aLine[0] == 'a') { |
240 | 0 | nsresult rv = mTableUpdate->NewAddExpiration(num); |
241 | 0 | if (NS_FAILED(rv)) { |
242 | 0 | return rv; |
243 | 0 | } |
244 | 0 | } else { |
245 | 0 | nsresult rv = mTableUpdate->NewSubExpiration(num); |
246 | 0 | if (NS_FAILED(rv)) { |
247 | 0 | return rv; |
248 | 0 | } |
249 | 0 | } |
250 | 0 | } |
251 | 0 | } else { |
252 | 0 | return NS_ERROR_FAILURE; |
253 | 0 | } |
254 | 0 | } |
255 | 0 | return NS_OK; |
256 | 0 | } |
257 | | |
258 | | nsresult |
259 | | ProtocolParserV2::ProcessChunkControl(const nsCString& aLine) |
260 | 0 | { |
261 | 0 | if (!mTableUpdate) { |
262 | 0 | NS_WARNING("Got a chunk before getting a table."); |
263 | 0 | return NS_ERROR_FAILURE; |
264 | 0 | } |
265 | 0 |
|
266 | 0 | mState = PROTOCOL_STATE_CHUNK; |
267 | 0 | char command; |
268 | 0 |
|
269 | 0 | mChunkState.Clear(); |
270 | 0 |
|
271 | 0 | if (PR_sscanf(aLine.get(), |
272 | 0 | "%c:%d:%d:%d", |
273 | 0 | &command, |
274 | 0 | &mChunkState.num, &mChunkState.hashSize, &mChunkState.length) |
275 | 0 | != 4) |
276 | 0 | { |
277 | 0 | NS_WARNING(("PR_sscanf failed")); |
278 | 0 | return NS_ERROR_FAILURE; |
279 | 0 | } |
280 | 0 |
|
281 | 0 | if (mChunkState.length > MAX_CHUNK_SIZE) { |
282 | 0 | NS_WARNING("Invalid length specified in update."); |
283 | 0 | return NS_ERROR_FAILURE; |
284 | 0 | } |
285 | 0 |
|
286 | 0 | if (!(mChunkState.hashSize == PREFIX_SIZE || mChunkState.hashSize == COMPLETE_SIZE)) { |
287 | 0 | NS_WARNING("Invalid hash size specified in update."); |
288 | 0 | return NS_ERROR_FAILURE; |
289 | 0 | } |
290 | 0 |
|
291 | 0 | if (StringEndsWith(mTableUpdate->TableName(), |
292 | 0 | NS_LITERAL_CSTRING("-shavar")) || |
293 | 0 | StringEndsWith(mTableUpdate->TableName(), |
294 | 0 | NS_LITERAL_CSTRING("-simple"))) { |
295 | 0 | // Accommodate test tables ending in -simple for now. |
296 | 0 | mChunkState.type = (command == 'a') ? CHUNK_ADD : CHUNK_SUB; |
297 | 0 | } else if (StringEndsWith(mTableUpdate->TableName(), |
298 | 0 | NS_LITERAL_CSTRING("-digest256"))) { |
299 | 0 | mChunkState.type = (command == 'a') ? CHUNK_ADD_DIGEST : CHUNK_SUB_DIGEST; |
300 | 0 | } |
301 | 0 | nsresult rv; |
302 | 0 | switch (mChunkState.type) { |
303 | 0 | case CHUNK_ADD: |
304 | 0 | rv = mTableUpdate->NewAddChunk(mChunkState.num); |
305 | 0 | if (NS_FAILED(rv)) { |
306 | 0 | return rv; |
307 | 0 | } |
308 | 0 | break; |
309 | 0 | case CHUNK_SUB: |
310 | 0 | rv = mTableUpdate->NewSubChunk(mChunkState.num); |
311 | 0 | if (NS_FAILED(rv)) { |
312 | 0 | return rv; |
313 | 0 | } |
314 | 0 | break; |
315 | 0 | case CHUNK_ADD_DIGEST: |
316 | 0 | rv = mTableUpdate->NewAddChunk(mChunkState.num); |
317 | 0 | if (NS_FAILED(rv)) { |
318 | 0 | return rv; |
319 | 0 | } |
320 | 0 | break; |
321 | 0 | case CHUNK_SUB_DIGEST: |
322 | 0 | rv = mTableUpdate->NewSubChunk(mChunkState.num); |
323 | 0 | if (NS_FAILED(rv)) { |
324 | 0 | return rv; |
325 | 0 | } |
326 | 0 | break; |
327 | 0 | } |
328 | 0 | |
329 | 0 | return NS_OK; |
330 | 0 | } |
331 | | |
332 | | nsresult |
333 | | ProtocolParserV2::ProcessForward(const nsCString& aLine) |
334 | 0 | { |
335 | 0 | const nsACString& forward = Substring(aLine, 2); |
336 | 0 | return AddForward(forward); |
337 | 0 | } |
338 | | |
339 | | nsresult |
340 | | ProtocolParserV2::AddForward(const nsACString& aUrl) |
341 | 0 | { |
342 | 0 | if (!mTableUpdate) { |
343 | 0 | NS_WARNING("Forward without a table name."); |
344 | 0 | return NS_ERROR_FAILURE; |
345 | 0 | } |
346 | 0 |
|
347 | 0 | ForwardedUpdate *forward = mForwards.AppendElement(); |
348 | 0 | forward->table = mTableUpdate->TableName(); |
349 | 0 | forward->url.Assign(aUrl); |
350 | 0 |
|
351 | 0 | return NS_OK; |
352 | 0 | } |
353 | | |
354 | | nsresult |
355 | | ProtocolParserV2::ProcessChunk(bool* aDone) |
356 | 0 | { |
357 | 0 | if (!mTableUpdate) { |
358 | 0 | NS_WARNING("Processing chunk without an active table."); |
359 | 0 | return NS_ERROR_FAILURE; |
360 | 0 | } |
361 | 0 |
|
362 | 0 | NS_ASSERTION(mChunkState.num != 0, "Must have a chunk number."); |
363 | 0 |
|
364 | 0 | if (mPending.Length() < mChunkState.length) { |
365 | 0 | *aDone = true; |
366 | 0 | return NS_OK; |
367 | 0 | } |
368 | 0 | |
369 | 0 | // Pull the chunk out of the pending stream data. |
370 | 0 | nsAutoCString chunk; |
371 | 0 | chunk.Assign(Substring(mPending, 0, mChunkState.length)); |
372 | 0 | mPending.Cut(0, mChunkState.length); |
373 | 0 |
|
374 | 0 | *aDone = false; |
375 | 0 | mState = PROTOCOL_STATE_CONTROL; |
376 | 0 |
|
377 | 0 | if (StringEndsWith(mTableUpdate->TableName(), |
378 | 0 | NS_LITERAL_CSTRING("-shavar"))) { |
379 | 0 | return ProcessShaChunk(chunk); |
380 | 0 | } |
381 | 0 | if (StringEndsWith(mTableUpdate->TableName(), |
382 | 0 | NS_LITERAL_CSTRING("-digest256"))) { |
383 | 0 | return ProcessDigestChunk(chunk); |
384 | 0 | } |
385 | 0 | return ProcessPlaintextChunk(chunk); |
386 | 0 | } |
387 | | |
388 | | /** |
389 | | * Process a plaintext chunk (currently only used in unit tests). |
390 | | */ |
391 | | nsresult |
392 | | ProtocolParserV2::ProcessPlaintextChunk(const nsACString& aChunk) |
393 | 0 | { |
394 | 0 | if (!mTableUpdate) { |
395 | 0 | NS_WARNING("Chunk received with no table."); |
396 | 0 | return NS_ERROR_FAILURE; |
397 | 0 | } |
398 | 0 |
|
399 | 0 | PARSER_LOG(("Handling a %d-byte simple chunk", aChunk.Length())); |
400 | 0 |
|
401 | 0 | nsTArray<nsCString> lines; |
402 | 0 | ParseString(PromiseFlatCString(aChunk), '\n', lines); |
403 | 0 |
|
404 | 0 | // non-hashed tables need to be hashed |
405 | 0 | for (uint32_t i = 0; i < lines.Length(); i++) { |
406 | 0 | nsCString& line = lines[i]; |
407 | 0 |
|
408 | 0 | if (mChunkState.type == CHUNK_ADD) { |
409 | 0 | if (mChunkState.hashSize == COMPLETE_SIZE) { |
410 | 0 | Completion hash; |
411 | 0 | hash.FromPlaintext(line); |
412 | 0 | nsresult rv = mTableUpdate->NewAddComplete(mChunkState.num, hash); |
413 | 0 | if (NS_FAILED(rv)) { |
414 | 0 | return rv; |
415 | 0 | } |
416 | 0 | } else { |
417 | 0 | NS_ASSERTION(mChunkState.hashSize == 4, "Only 32- or 4-byte hashes can be used for add chunks."); |
418 | 0 | Prefix hash; |
419 | 0 | hash.FromPlaintext(line); |
420 | 0 | nsresult rv = mTableUpdate->NewAddPrefix(mChunkState.num, hash); |
421 | 0 | if (NS_FAILED(rv)) { |
422 | 0 | return rv; |
423 | 0 | } |
424 | 0 | } |
425 | 0 | } else { |
426 | 0 | nsCString::const_iterator begin, iter, end; |
427 | 0 | line.BeginReading(begin); |
428 | 0 | line.EndReading(end); |
429 | 0 | iter = begin; |
430 | 0 | uint32_t addChunk; |
431 | 0 | if (!FindCharInReadable(':', iter, end) || |
432 | 0 | PR_sscanf(lines[i].get(), "%d:", &addChunk) != 1) { |
433 | 0 | NS_WARNING("Received sub chunk without associated add chunk."); |
434 | 0 | return NS_ERROR_FAILURE; |
435 | 0 | } |
436 | 0 | iter++; |
437 | 0 |
|
438 | 0 | if (mChunkState.hashSize == COMPLETE_SIZE) { |
439 | 0 | Completion hash; |
440 | 0 | hash.FromPlaintext(Substring(iter, end)); |
441 | 0 | nsresult rv = mTableUpdate->NewSubComplete(addChunk, hash, mChunkState.num); |
442 | 0 | if (NS_FAILED(rv)) { |
443 | 0 | return rv; |
444 | 0 | } |
445 | 0 | } else { |
446 | 0 | NS_ASSERTION(mChunkState.hashSize == 4, "Only 32- or 4-byte hashes can be used for add chunks."); |
447 | 0 | Prefix hash; |
448 | 0 | hash.FromPlaintext(Substring(iter, end)); |
449 | 0 | nsresult rv = mTableUpdate->NewSubPrefix(addChunk, hash, mChunkState.num); |
450 | 0 | if (NS_FAILED(rv)) { |
451 | 0 | return rv; |
452 | 0 | } |
453 | 0 | } |
454 | 0 | } |
455 | 0 | } |
456 | 0 |
|
457 | 0 | return NS_OK; |
458 | 0 | } |
459 | | |
460 | | nsresult |
461 | | ProtocolParserV2::ProcessShaChunk(const nsACString& aChunk) |
462 | 0 | { |
463 | 0 | uint32_t start = 0; |
464 | 0 | while (start < aChunk.Length()) { |
465 | 0 | // First four bytes are the domain key. |
466 | 0 | Prefix domain; |
467 | 0 | domain.Assign(Substring(aChunk, start, DOMAIN_SIZE)); |
468 | 0 | start += DOMAIN_SIZE; |
469 | 0 |
|
470 | 0 | // Then a count of entries. |
471 | 0 | uint8_t numEntries = static_cast<uint8_t>(aChunk[start]); |
472 | 0 | start++; |
473 | 0 |
|
474 | 0 | PARSER_LOG(("Handling a %d-byte shavar chunk containing %u entries" |
475 | 0 | " for domain %X", aChunk.Length(), numEntries, |
476 | 0 | domain.ToUint32())); |
477 | 0 |
|
478 | 0 | nsresult rv; |
479 | 0 | if (mChunkState.type == CHUNK_ADD && mChunkState.hashSize == PREFIX_SIZE) { |
480 | 0 | rv = ProcessHostAdd(domain, numEntries, aChunk, &start); |
481 | 0 | } else if (mChunkState.type == CHUNK_ADD && mChunkState.hashSize == COMPLETE_SIZE) { |
482 | 0 | rv = ProcessHostAddComplete(numEntries, aChunk, &start); |
483 | 0 | } else if (mChunkState.type == CHUNK_SUB && mChunkState.hashSize == PREFIX_SIZE) { |
484 | 0 | rv = ProcessHostSub(domain, numEntries, aChunk, &start); |
485 | 0 | } else if (mChunkState.type == CHUNK_SUB && mChunkState.hashSize == COMPLETE_SIZE) { |
486 | 0 | rv = ProcessHostSubComplete(numEntries, aChunk, &start); |
487 | 0 | } else { |
488 | 0 | NS_WARNING("Unexpected chunk type/hash size!"); |
489 | 0 | PARSER_LOG(("Got an unexpected chunk type/hash size: %s:%d", |
490 | 0 | mChunkState.type == CHUNK_ADD ? "add" : "sub", |
491 | 0 | mChunkState.hashSize)); |
492 | 0 | return NS_ERROR_FAILURE; |
493 | 0 | } |
494 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
495 | 0 | } |
496 | 0 |
|
497 | 0 | return NS_OK; |
498 | 0 | } |
499 | | |
500 | | nsresult |
501 | | ProtocolParserV2::ProcessDigestChunk(const nsACString& aChunk) |
502 | 0 | { |
503 | 0 | PARSER_LOG(("Handling a %d-byte digest256 chunk", aChunk.Length())); |
504 | 0 |
|
505 | 0 | if (mChunkState.type == CHUNK_ADD_DIGEST) { |
506 | 0 | return ProcessDigestAdd(aChunk); |
507 | 0 | } |
508 | 0 | if (mChunkState.type == CHUNK_SUB_DIGEST) { |
509 | 0 | return ProcessDigestSub(aChunk); |
510 | 0 | } |
511 | 0 | return NS_ERROR_UNEXPECTED; |
512 | 0 | } |
513 | | |
514 | | nsresult |
515 | | ProtocolParserV2::ProcessDigestAdd(const nsACString& aChunk) |
516 | 0 | { |
517 | 0 | MOZ_ASSERT(mTableUpdate); |
518 | 0 | // The ABNF format for add chunks is (HASH)+, where HASH is 32 bytes. |
519 | 0 | MOZ_ASSERT(aChunk.Length() % 32 == 0, |
520 | 0 | "Chunk length in bytes must be divisible by 4"); |
521 | 0 | uint32_t start = 0; |
522 | 0 | while (start < aChunk.Length()) { |
523 | 0 | Completion hash; |
524 | 0 | hash.Assign(Substring(aChunk, start, COMPLETE_SIZE)); |
525 | 0 | start += COMPLETE_SIZE; |
526 | 0 | nsresult rv = mTableUpdate->NewAddComplete(mChunkState.num, hash); |
527 | 0 | if (NS_FAILED(rv)) { |
528 | 0 | return rv; |
529 | 0 | } |
530 | 0 | } |
531 | 0 | return NS_OK; |
532 | 0 | } |
533 | | |
534 | | nsresult |
535 | | ProtocolParserV2::ProcessDigestSub(const nsACString& aChunk) |
536 | 0 | { |
537 | 0 | MOZ_ASSERT(mTableUpdate); |
538 | 0 | // The ABNF format for sub chunks is (ADDCHUNKNUM HASH)+, where ADDCHUNKNUM |
539 | 0 | // is a 4 byte chunk number, and HASH is 32 bytes. |
540 | 0 | MOZ_ASSERT(aChunk.Length() % 36 == 0, |
541 | 0 | "Chunk length in bytes must be divisible by 36"); |
542 | 0 | uint32_t start = 0; |
543 | 0 | while (start < aChunk.Length()) { |
544 | 0 | // Read ADDCHUNKNUM |
545 | 0 | const nsACString& addChunkStr = Substring(aChunk, start, 4); |
546 | 0 | start += 4; |
547 | 0 |
|
548 | 0 | uint32_t addChunk; |
549 | 0 | memcpy(&addChunk, addChunkStr.BeginReading(), 4); |
550 | 0 | addChunk = PR_ntohl(addChunk); |
551 | 0 |
|
552 | 0 | // Read the hash |
553 | 0 | Completion hash; |
554 | 0 | hash.Assign(Substring(aChunk, start, COMPLETE_SIZE)); |
555 | 0 | start += COMPLETE_SIZE; |
556 | 0 |
|
557 | 0 | nsresult rv = mTableUpdate->NewSubComplete(addChunk, hash, mChunkState.num); |
558 | 0 | if (NS_FAILED(rv)) { |
559 | 0 | return rv; |
560 | 0 | } |
561 | 0 | } |
562 | 0 | return NS_OK; |
563 | 0 | } |
564 | | |
565 | | nsresult |
566 | | ProtocolParserV2::ProcessHostAdd(const Prefix& aDomain, uint8_t aNumEntries, |
567 | | const nsACString& aChunk, uint32_t* aStart) |
568 | 0 | { |
569 | 0 | MOZ_ASSERT(mTableUpdate); |
570 | 0 | NS_ASSERTION(mChunkState.hashSize == PREFIX_SIZE, |
571 | 0 | "ProcessHostAdd should only be called for prefix hashes."); |
572 | 0 |
|
573 | 0 | if (aNumEntries == 0) { |
574 | 0 | nsresult rv = mTableUpdate->NewAddPrefix(mChunkState.num, aDomain); |
575 | 0 | if (NS_FAILED(rv)) { |
576 | 0 | return rv; |
577 | 0 | } |
578 | 0 | return NS_OK; |
579 | 0 | } |
580 | 0 | |
581 | 0 | if (*aStart + (PREFIX_SIZE * aNumEntries) > aChunk.Length()) { |
582 | 0 | NS_WARNING("Chunk is not long enough to contain the expected entries."); |
583 | 0 | return NS_ERROR_FAILURE; |
584 | 0 | } |
585 | 0 |
|
586 | 0 | for (uint8_t i = 0; i < aNumEntries; i++) { |
587 | 0 | Prefix hash; |
588 | 0 | hash.Assign(Substring(aChunk, *aStart, PREFIX_SIZE)); |
589 | 0 | PARSER_LOG(("Add prefix %X", hash.ToUint32())); |
590 | 0 | nsresult rv = mTableUpdate->NewAddPrefix(mChunkState.num, hash); |
591 | 0 | if (NS_FAILED(rv)) { |
592 | 0 | return rv; |
593 | 0 | } |
594 | 0 | *aStart += PREFIX_SIZE; |
595 | 0 | } |
596 | 0 |
|
597 | 0 | return NS_OK; |
598 | 0 | } |
599 | | |
600 | | nsresult |
601 | | ProtocolParserV2::ProcessHostSub(const Prefix& aDomain, uint8_t aNumEntries, |
602 | | const nsACString& aChunk, uint32_t *aStart) |
603 | 0 | { |
604 | 0 | MOZ_ASSERT(mTableUpdate); |
605 | 0 | NS_ASSERTION(mChunkState.hashSize == PREFIX_SIZE, |
606 | 0 | "ProcessHostSub should only be called for prefix hashes."); |
607 | 0 |
|
608 | 0 | if (aNumEntries == 0) { |
609 | 0 | if ((*aStart) + 4 > aChunk.Length()) { |
610 | 0 | NS_WARNING("Received a zero-entry sub chunk without an associated add."); |
611 | 0 | return NS_ERROR_FAILURE; |
612 | 0 | } |
613 | 0 |
|
614 | 0 | const nsACString& addChunkStr = Substring(aChunk, *aStart, 4); |
615 | 0 | *aStart += 4; |
616 | 0 |
|
617 | 0 | uint32_t addChunk; |
618 | 0 | memcpy(&addChunk, addChunkStr.BeginReading(), 4); |
619 | 0 | addChunk = PR_ntohl(addChunk); |
620 | 0 |
|
621 | 0 | PARSER_LOG(("Sub prefix (addchunk=%u)", addChunk)); |
622 | 0 | nsresult rv = mTableUpdate->NewSubPrefix(addChunk, aDomain, mChunkState.num); |
623 | 0 | if (NS_FAILED(rv)) { |
624 | 0 | return rv; |
625 | 0 | } |
626 | 0 | return NS_OK; |
627 | 0 | } |
628 | 0 | |
629 | 0 | if (*aStart + ((PREFIX_SIZE + 4) * aNumEntries) > aChunk.Length()) { |
630 | 0 | NS_WARNING("Chunk is not long enough to contain the expected entries."); |
631 | 0 | return NS_ERROR_FAILURE; |
632 | 0 | } |
633 | 0 |
|
634 | 0 | for (uint8_t i = 0; i < aNumEntries; i++) { |
635 | 0 | const nsACString& addChunkStr = Substring(aChunk, *aStart, 4); |
636 | 0 | *aStart += 4; |
637 | 0 |
|
638 | 0 | uint32_t addChunk; |
639 | 0 | memcpy(&addChunk, addChunkStr.BeginReading(), 4); |
640 | 0 | addChunk = PR_ntohl(addChunk); |
641 | 0 |
|
642 | 0 | Prefix prefix; |
643 | 0 | prefix.Assign(Substring(aChunk, *aStart, PREFIX_SIZE)); |
644 | 0 | *aStart += PREFIX_SIZE; |
645 | 0 |
|
646 | 0 | PARSER_LOG(("Sub prefix %X (addchunk=%u)", prefix.ToUint32(), addChunk)); |
647 | 0 | nsresult rv = mTableUpdate->NewSubPrefix(addChunk, prefix, mChunkState.num); |
648 | 0 | if (NS_FAILED(rv)) { |
649 | 0 | return rv; |
650 | 0 | } |
651 | 0 | } |
652 | 0 |
|
653 | 0 | return NS_OK; |
654 | 0 | } |
655 | | |
656 | | nsresult |
657 | | ProtocolParserV2::ProcessHostAddComplete(uint8_t aNumEntries, |
658 | | const nsACString& aChunk, uint32_t* aStart) |
659 | 0 | { |
660 | 0 | MOZ_ASSERT(mTableUpdate); |
661 | 0 | NS_ASSERTION(mChunkState.hashSize == COMPLETE_SIZE, |
662 | 0 | "ProcessHostAddComplete should only be called for complete hashes."); |
663 | 0 |
|
664 | 0 | if (aNumEntries == 0) { |
665 | 0 | // this is totally comprehensible. |
666 | 0 | // My sarcasm detector is going off! |
667 | 0 | NS_WARNING("Expected > 0 entries for a 32-byte hash add."); |
668 | 0 | return NS_OK; |
669 | 0 | } |
670 | 0 |
|
671 | 0 | if (*aStart + (COMPLETE_SIZE * aNumEntries) > aChunk.Length()) { |
672 | 0 | NS_WARNING("Chunk is not long enough to contain the expected entries."); |
673 | 0 | return NS_ERROR_FAILURE; |
674 | 0 | } |
675 | 0 |
|
676 | 0 | for (uint8_t i = 0; i < aNumEntries; i++) { |
677 | 0 | Completion hash; |
678 | 0 | hash.Assign(Substring(aChunk, *aStart, COMPLETE_SIZE)); |
679 | 0 | nsresult rv = mTableUpdate->NewAddComplete(mChunkState.num, hash); |
680 | 0 | if (NS_FAILED(rv)) { |
681 | 0 | return rv; |
682 | 0 | } |
683 | 0 | *aStart += COMPLETE_SIZE; |
684 | 0 | } |
685 | 0 |
|
686 | 0 | return NS_OK; |
687 | 0 | } |
688 | | |
689 | | nsresult |
690 | | ProtocolParserV2::ProcessHostSubComplete(uint8_t aNumEntries, |
691 | | const nsACString& aChunk, uint32_t* aStart) |
692 | 0 | { |
693 | 0 | MOZ_ASSERT(mTableUpdate); |
694 | 0 | NS_ASSERTION(mChunkState.hashSize == COMPLETE_SIZE, |
695 | 0 | "ProcessHostSubComplete should only be called for complete hashes."); |
696 | 0 |
|
697 | 0 | if (aNumEntries == 0) { |
698 | 0 | // this is totally comprehensible. |
699 | 0 | NS_WARNING("Expected > 0 entries for a 32-byte hash sub."); |
700 | 0 | return NS_OK; |
701 | 0 | } |
702 | 0 |
|
703 | 0 | if (*aStart + ((COMPLETE_SIZE + 4) * aNumEntries) > aChunk.Length()) { |
704 | 0 | NS_WARNING("Chunk is not long enough to contain the expected entries."); |
705 | 0 | return NS_ERROR_FAILURE; |
706 | 0 | } |
707 | 0 |
|
708 | 0 | for (uint8_t i = 0; i < aNumEntries; i++) { |
709 | 0 | Completion hash; |
710 | 0 | hash.Assign(Substring(aChunk, *aStart, COMPLETE_SIZE)); |
711 | 0 | *aStart += COMPLETE_SIZE; |
712 | 0 |
|
713 | 0 | const nsACString& addChunkStr = Substring(aChunk, *aStart, 4); |
714 | 0 | *aStart += 4; |
715 | 0 |
|
716 | 0 | uint32_t addChunk; |
717 | 0 | memcpy(&addChunk, addChunkStr.BeginReading(), 4); |
718 | 0 | addChunk = PR_ntohl(addChunk); |
719 | 0 |
|
720 | 0 | nsresult rv = mTableUpdate->NewSubComplete(addChunk, hash, mChunkState.num); |
721 | 0 | if (NS_FAILED(rv)) { |
722 | 0 | return rv; |
723 | 0 | } |
724 | 0 | } |
725 | 0 |
|
726 | 0 | return NS_OK; |
727 | 0 | } |
728 | | |
729 | | bool |
730 | | ProtocolParserV2::NextLine(nsACString& aLine) |
731 | 0 | { |
732 | 0 | int32_t newline = mPending.FindChar('\n'); |
733 | 0 | if (newline == kNotFound) { |
734 | 0 | return false; |
735 | 0 | } |
736 | 0 | aLine.Assign(Substring(mPending, 0, newline)); |
737 | 0 | mPending.Cut(0, newline + 1); |
738 | 0 | return true; |
739 | 0 | } |
740 | | |
741 | | RefPtr<TableUpdate> |
742 | | ProtocolParserV2::CreateTableUpdate(const nsACString& aTableName) const |
743 | 0 | { |
744 | 0 | return new TableUpdateV2(aTableName); |
745 | 0 | } |
746 | | |
747 | | /////////////////////////////////////////////////////////////////////// |
748 | | // ProtocolParserProtobuf |
749 | | |
750 | | ProtocolParserProtobuf::ProtocolParserProtobuf() |
751 | 0 | { |
752 | 0 | } |
753 | | |
754 | | ProtocolParserProtobuf::~ProtocolParserProtobuf() |
755 | | { |
756 | | } |
757 | | |
758 | | void |
759 | | ProtocolParserProtobuf::SetCurrentTable(const nsACString& aTable) |
760 | 0 | { |
761 | 0 | // Should never occur. |
762 | 0 | MOZ_ASSERT_UNREACHABLE("SetCurrentTable shouldn't be called"); |
763 | 0 | } |
764 | | |
765 | | |
766 | | RefPtr<TableUpdate> |
767 | | ProtocolParserProtobuf::CreateTableUpdate(const nsACString& aTableName) const |
768 | 0 | { |
769 | 0 | return new TableUpdateV4(aTableName); |
770 | 0 | } |
771 | | |
772 | | nsresult |
773 | | ProtocolParserProtobuf::AppendStream(const nsACString& aData) |
774 | 0 | { |
775 | 0 | // Protobuf data cannot be parsed progressively. Just save the incoming data. |
776 | 0 | mPending.Append(aData); |
777 | 0 | return NS_OK; |
778 | 0 | } |
779 | | |
780 | | void |
781 | | ProtocolParserProtobuf::End() |
782 | 0 | { |
783 | 0 | // mUpdateStatus will be updated to success as long as not all |
784 | 0 | // the responses are invalid. |
785 | 0 | mUpdateStatus = NS_ERROR_FAILURE; |
786 | 0 |
|
787 | 0 | FetchThreatListUpdatesResponse response; |
788 | 0 | if (!response.ParseFromArray(mPending.get(), mPending.Length())) { |
789 | 0 | NS_WARNING("ProtocolParserProtobuf failed parsing data."); |
790 | 0 | return; |
791 | 0 | } |
792 | 0 |
|
793 | 0 | auto minWaitDuration = response.minimum_wait_duration(); |
794 | 0 | mUpdateWaitSec = minWaitDuration.seconds() + |
795 | 0 | minWaitDuration.nanos() / 1000000000; |
796 | 0 |
|
797 | 0 | for (int i = 0; i < response.list_update_responses_size(); i++) { |
798 | 0 | auto r = response.list_update_responses(i); |
799 | 0 | nsAutoCString listName; |
800 | 0 | nsresult rv = ProcessOneResponse(r, listName); |
801 | 0 | if (NS_SUCCEEDED(rv)) { |
802 | 0 | mUpdateStatus = rv; |
803 | 0 | } else { |
804 | 0 | nsAutoCString errorName; |
805 | 0 | mozilla::GetErrorName(rv, errorName); |
806 | 0 | NS_WARNING(nsPrintfCString("Failed to process one response for '%s': %s", |
807 | 0 | listName.get(), errorName.get()).get()); |
808 | 0 | if (!listName.IsEmpty()) { |
809 | 0 | PARSER_LOG(("Table %s will be reset.", listName.get())); |
810 | 0 | mTablesToReset.AppendElement(listName); |
811 | 0 | } |
812 | 0 | } |
813 | 0 | } |
814 | 0 | } |
815 | | |
816 | | nsresult |
817 | | ProtocolParserProtobuf::ProcessOneResponse(const ListUpdateResponse& aResponse, |
818 | | nsACString& aListName) |
819 | 0 | { |
820 | 0 | MOZ_ASSERT(aListName.IsEmpty()); |
821 | 0 |
|
822 | 0 | // A response must have a threat type. |
823 | 0 | if (!aResponse.has_threat_type()) { |
824 | 0 | NS_WARNING("Threat type not initialized. This seems to be an invalid response."); |
825 | 0 | return NS_ERROR_UC_PARSER_MISSING_PARAM; |
826 | 0 | } |
827 | 0 |
|
828 | 0 | // Convert threat type to list name. |
829 | 0 | nsCOMPtr<nsIUrlClassifierUtils> urlUtil = |
830 | 0 | do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID); |
831 | 0 | nsCString possibleListNames; |
832 | 0 | nsresult rv = urlUtil->ConvertThreatTypeToListNames(aResponse.threat_type(), |
833 | 0 | possibleListNames); |
834 | 0 | if (NS_FAILED(rv)) { |
835 | 0 | PARSER_LOG(("Threat type to list name conversion error: %d", |
836 | 0 | aResponse.threat_type())); |
837 | 0 | return NS_ERROR_UC_PARSER_UNKNOWN_THREAT; |
838 | 0 | } |
839 | 0 |
|
840 | 0 | // Match the table name we received with one of the ones we requested. |
841 | 0 | // We ignore the case where a threat type matches more than one list |
842 | 0 | // per provider and return the first one. See bug 1287059." |
843 | 0 | nsTArray<nsCString> possibleListNameArray; |
844 | 0 | Classifier::SplitTables(possibleListNames, possibleListNameArray); |
845 | 0 | for (auto possibleName : possibleListNameArray) { |
846 | 0 | if (mRequestedTables.Contains(possibleName)) { |
847 | 0 | aListName = possibleName; |
848 | 0 | break; |
849 | 0 | } |
850 | 0 | } |
851 | 0 |
|
852 | 0 | if (aListName.IsEmpty()) { |
853 | 0 | PARSER_LOG(("We received an update for a list we didn't ask for. Ignoring it.")); |
854 | 0 | return NS_ERROR_FAILURE; |
855 | 0 | } |
856 | 0 |
|
857 | 0 | // Test if this is a full update. |
858 | 0 | bool isFullUpdate = false; |
859 | 0 | if (aResponse.has_response_type()) { |
860 | 0 | isFullUpdate = |
861 | 0 | aResponse.response_type() == ListUpdateResponse::FULL_UPDATE; |
862 | 0 | } else { |
863 | 0 | NS_WARNING("Response type not initialized."); |
864 | 0 | return NS_ERROR_UC_PARSER_MISSING_PARAM; |
865 | 0 | } |
866 | 0 |
|
867 | 0 | // Warn if there's no new state. |
868 | 0 | if (!aResponse.has_new_client_state()) { |
869 | 0 | NS_WARNING("New state not initialized."); |
870 | 0 | return NS_ERROR_UC_PARSER_MISSING_PARAM; |
871 | 0 | } |
872 | 0 |
|
873 | 0 | auto tu = GetTableUpdate(aListName); |
874 | 0 | auto tuV4 = TableUpdate::Cast<TableUpdateV4>(tu); |
875 | 0 | NS_ENSURE_TRUE(tuV4, NS_ERROR_FAILURE); |
876 | 0 |
|
877 | 0 | nsCString state(aResponse.new_client_state().c_str(), |
878 | 0 | aResponse.new_client_state().size()); |
879 | 0 | tuV4->SetNewClientState(state); |
880 | 0 |
|
881 | 0 | if (aResponse.has_checksum()) { |
882 | 0 | tuV4->NewChecksum(aResponse.checksum().sha256()); |
883 | 0 | } |
884 | 0 |
|
885 | 0 | PARSER_LOG(("==== Update for threat type '%d' ====", aResponse.threat_type())); |
886 | 0 | PARSER_LOG(("* aListName: %s\n", PromiseFlatCString(aListName).get())); |
887 | 0 | PARSER_LOG(("* newState: %s\n", aResponse.new_client_state().c_str())); |
888 | 0 | PARSER_LOG(("* isFullUpdate: %s\n", (isFullUpdate ? "yes" : "no"))); |
889 | 0 | PARSER_LOG(("* hasChecksum: %s\n", (aResponse.has_checksum() ? "yes" : "no"))); |
890 | 0 | PARSER_LOG(("* additions: %d\n", aResponse.additions().size())); |
891 | 0 | PARSER_LOG(("* removals: %d\n", aResponse.removals().size())); |
892 | 0 |
|
893 | 0 | tuV4->SetFullUpdate(isFullUpdate); |
894 | 0 |
|
895 | 0 | rv = ProcessAdditionOrRemoval(*tuV4, aResponse.additions(), true /*aIsAddition*/); |
896 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
897 | 0 | rv = ProcessAdditionOrRemoval(*tuV4, aResponse.removals(), false); |
898 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
899 | 0 |
|
900 | 0 | PARSER_LOG(("\n\n")); |
901 | 0 |
|
902 | 0 | return NS_OK; |
903 | 0 | } |
904 | | |
905 | | nsresult |
906 | | ProtocolParserProtobuf::ProcessAdditionOrRemoval(TableUpdateV4& aTableUpdate, |
907 | | const ThreatEntrySetList& aUpdate, |
908 | | bool aIsAddition) |
909 | 0 | { |
910 | 0 | nsresult ret = NS_OK; |
911 | 0 |
|
912 | 0 | for (int i = 0; i < aUpdate.size(); i++) { |
913 | 0 | auto update = aUpdate.Get(i); |
914 | 0 | if (!update.has_compression_type()) { |
915 | 0 | NS_WARNING(nsPrintfCString("%s with no compression type.", |
916 | 0 | aIsAddition ? "Addition" : "Removal").get()); |
917 | 0 | continue; |
918 | 0 | } |
919 | 0 |
|
920 | 0 | switch (update.compression_type()) { |
921 | 0 | case COMPRESSION_TYPE_UNSPECIFIED: |
922 | 0 | NS_WARNING("Unspecified compression type."); |
923 | 0 | break; |
924 | 0 |
|
925 | 0 | case RAW: |
926 | 0 | ret = (aIsAddition ? ProcessRawAddition(aTableUpdate, update) |
927 | 0 | : ProcessRawRemoval(aTableUpdate, update)); |
928 | 0 | break; |
929 | 0 |
|
930 | 0 | case RICE: |
931 | 0 | ret = (aIsAddition ? ProcessEncodedAddition(aTableUpdate, update) |
932 | 0 | : ProcessEncodedRemoval(aTableUpdate, update)); |
933 | 0 | break; |
934 | 0 | } |
935 | 0 | } |
936 | 0 |
|
937 | 0 | return ret; |
938 | 0 | } |
939 | | |
940 | | nsresult |
941 | | ProtocolParserProtobuf::ProcessRawAddition(TableUpdateV4& aTableUpdate, |
942 | | const ThreatEntrySet& aAddition) |
943 | 0 | { |
944 | 0 | if (!aAddition.has_raw_hashes()) { |
945 | 0 | PARSER_LOG(("* No raw addition.")); |
946 | 0 | return NS_OK; |
947 | 0 | } |
948 | 0 |
|
949 | 0 | auto rawHashes = aAddition.raw_hashes(); |
950 | 0 | if (!rawHashes.has_prefix_size()) { |
951 | 0 | NS_WARNING("Raw hash has no prefix size"); |
952 | 0 | return NS_OK; |
953 | 0 | } |
954 | 0 |
|
955 | 0 | uint32_t prefixSize = rawHashes.prefix_size(); |
956 | 0 | MOZ_ASSERT(prefixSize >= PREFIX_SIZE && prefixSize <= COMPLETE_SIZE); |
957 | 0 |
|
958 | 0 | nsCString prefixes; |
959 | 0 | if (!prefixes.Assign(rawHashes.raw_hashes().c_str(), |
960 | 0 | rawHashes.raw_hashes().size(), mozilla::fallible)) { |
961 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
962 | 0 | } |
963 | 0 | MOZ_ASSERT(prefixes.Length() % prefixSize == 0, |
964 | 0 | "PrefixString length must be a multiple of the prefix size."); |
965 | 0 |
|
966 | 0 | if (LOG_ENABLED()) { |
967 | 0 | PARSER_LOG((" Raw addition (%d-byte prefixes)", prefixSize)); |
968 | 0 | PARSER_LOG((" - # of prefixes: %u", prefixes.Length() / prefixSize)); |
969 | 0 | if (4 == prefixSize) { |
970 | 0 | uint32_t* fixedLengthPrefixes = (uint32_t*)prefixes.get(); |
971 | 0 | PARSER_LOG((" - Memory address: 0x%p", fixedLengthPrefixes)); |
972 | 0 | } |
973 | 0 | } |
974 | 0 |
|
975 | 0 | aTableUpdate.NewPrefixes(prefixSize, prefixes); |
976 | 0 | return NS_OK; |
977 | 0 | } |
978 | | |
979 | | nsresult |
980 | | ProtocolParserProtobuf::ProcessRawRemoval(TableUpdateV4& aTableUpdate, |
981 | | const ThreatEntrySet& aRemoval) |
982 | 0 | { |
983 | 0 | if (!aRemoval.has_raw_indices()) { |
984 | 0 | NS_WARNING("A removal has no indices."); |
985 | 0 | return NS_OK; |
986 | 0 | } |
987 | 0 |
|
988 | 0 | // indices is an array of int32. |
989 | 0 | auto indices = aRemoval.raw_indices().indices(); |
990 | 0 | PARSER_LOG(("* Raw removal")); |
991 | 0 | PARSER_LOG((" - # of removal: %d", indices.size())); |
992 | 0 |
|
993 | 0 | nsresult rv = aTableUpdate.NewRemovalIndices((const uint32_t*)indices.data(), |
994 | 0 | indices.size()); |
995 | 0 | if (NS_FAILED(rv)) { |
996 | 0 | PARSER_LOG(("Failed to create new removal indices.")); |
997 | 0 | return rv; |
998 | 0 | } |
999 | 0 |
|
1000 | 0 | return NS_OK; |
1001 | 0 | } |
1002 | | |
1003 | | static nsresult |
1004 | | DoRiceDeltaDecode(const RiceDeltaEncoding& aEncoding, |
1005 | | nsTArray<uint32_t>& aDecoded) |
1006 | 0 | { |
1007 | 0 | if (!aEncoding.has_first_value()) { |
1008 | 0 | PARSER_LOG(("The encoding info is incomplete.")); |
1009 | 0 | return NS_ERROR_UC_PARSER_MISSING_PARAM; |
1010 | 0 | } |
1011 | 0 | if (aEncoding.num_entries() > 0 && |
1012 | 0 | (!aEncoding.has_rice_parameter() || !aEncoding.has_encoded_data())) { |
1013 | 0 | PARSER_LOG(("Rice parameter or encoded data is missing.")); |
1014 | 0 | return NS_ERROR_UC_PARSER_MISSING_PARAM; |
1015 | 0 | } |
1016 | 0 |
|
1017 | 0 | PARSER_LOG(("* Encoding info:")); |
1018 | 0 | PARSER_LOG((" - First value: %" PRId64, aEncoding.first_value())); |
1019 | 0 | PARSER_LOG((" - Num of entries: %d", aEncoding.num_entries())); |
1020 | 0 | PARSER_LOG((" - Rice parameter: %d", aEncoding.rice_parameter())); |
1021 | 0 |
|
1022 | 0 | // Set up the input buffer. Note that the bits should be read |
1023 | 0 | // from LSB to MSB so that we in-place reverse the bits before |
1024 | 0 | // feeding to the decoder. |
1025 | 0 | auto encoded = const_cast<RiceDeltaEncoding&>(aEncoding).mutable_encoded_data(); |
1026 | 0 | RiceDeltaDecoder decoder((uint8_t*)encoded->c_str(), encoded->size()); |
1027 | 0 |
|
1028 | 0 | // Setup the output buffer. The "first value" is included in |
1029 | 0 | // the output buffer. |
1030 | 0 | if (!aDecoded.SetLength(aEncoding.num_entries() + 1, mozilla::fallible)) { |
1031 | 0 | NS_WARNING("Not enough memory to decode the RiceDelta input."); |
1032 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
1033 | 0 | } |
1034 | 0 |
|
1035 | 0 | // Decode! |
1036 | 0 | bool rv = decoder.Decode(aEncoding.rice_parameter(), |
1037 | 0 | aEncoding.first_value(), // first value. |
1038 | 0 | aEncoding.num_entries(), // # of entries (first value not included). |
1039 | 0 | &aDecoded[0]); |
1040 | 0 |
|
1041 | 0 | NS_ENSURE_TRUE(rv, NS_ERROR_UC_PARSER_DECODE_FAILURE); |
1042 | 0 |
|
1043 | 0 | return NS_OK; |
1044 | 0 | } |
1045 | | |
1046 | | nsresult |
1047 | | ProtocolParserProtobuf::ProcessEncodedAddition(TableUpdateV4& aTableUpdate, |
1048 | | const ThreatEntrySet& aAddition) |
1049 | 0 | { |
1050 | 0 | if (!aAddition.has_rice_hashes()) { |
1051 | 0 | PARSER_LOG(("* No rice encoded addition.")); |
1052 | 0 | return NS_OK; |
1053 | 0 | } |
1054 | 0 |
|
1055 | 0 | nsTArray<uint32_t> decoded; |
1056 | 0 | nsresult rv = DoRiceDeltaDecode(aAddition.rice_hashes(), decoded); |
1057 | 0 | if (NS_FAILED(rv)) { |
1058 | 0 | PARSER_LOG(("Failed to parse encoded prefixes.")); |
1059 | 0 | return rv; |
1060 | 0 | } |
1061 | 0 |
|
1062 | 0 | // Say we have the following raw prefixes |
1063 | 0 | // BE LE |
1064 | 0 | // 00 00 00 01 1 16777216 |
1065 | 0 | // 00 00 02 00 512 131072 |
1066 | 0 | // 00 03 00 00 196608 768 |
1067 | 0 | // 04 00 00 00 67108864 4 |
1068 | 0 | // |
1069 | 0 | // which can be treated as uint32 (big-endian) sorted in increasing order: |
1070 | 0 | // |
1071 | 0 | // [1, 512, 196608, 67108864] |
1072 | 0 | // |
1073 | 0 | // According to https://developers.google.com/safe-browsing/v4/compression, |
1074 | 0 | // the following should be done prior to compression: |
1075 | 0 | // |
1076 | 0 | // 1) re-interpret in little-endian ==> [16777216, 131072, 768, 4] |
1077 | 0 | // 2) sort in increasing order ==> [4, 768, 131072, 16777216] |
1078 | 0 | // |
1079 | 0 | // In order to get the original byte stream from |decoded| |
1080 | 0 | // ([4, 768, 131072, 16777216] in this case), we have to: |
1081 | 0 | // |
1082 | 0 | // 1) sort in big-endian order ==> [16777216, 131072, 768, 4] |
1083 | 0 | // 2) copy each uint32 in little-endian to the result string |
1084 | 0 | // |
1085 | 0 |
|
1086 | 0 | // The 4-byte prefixes have to be re-sorted in Big-endian increasing order. |
1087 | 0 | struct CompareBigEndian |
1088 | 0 | { |
1089 | 0 | bool Equals(const uint32_t& aA, const uint32_t& aB) const |
1090 | 0 | { |
1091 | 0 | return aA == aB; |
1092 | 0 | } |
1093 | 0 |
|
1094 | 0 | bool LessThan(const uint32_t& aA, const uint32_t& aB) const |
1095 | 0 | { |
1096 | 0 | return NativeEndian::swapToBigEndian(aA) < |
1097 | 0 | NativeEndian::swapToBigEndian(aB); |
1098 | 0 | } |
1099 | 0 | }; |
1100 | 0 | decoded.Sort(CompareBigEndian()); |
1101 | 0 |
|
1102 | 0 | // The encoded prefixes are always 4 bytes. |
1103 | 0 | nsCString prefixes; |
1104 | 0 | if (!prefixes.SetCapacity(decoded.Length() * 4, mozilla::fallible)) { |
1105 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
1106 | 0 | } |
1107 | 0 | for (size_t i = 0; i < decoded.Length(); i++) { |
1108 | 0 | // Note that the third argument is the number of elements we want |
1109 | 0 | // to copy (and swap) but not the number of bytes we want to copy. |
1110 | 0 | char p[4]; |
1111 | 0 | NativeEndian::copyAndSwapToLittleEndian(p, &decoded[i], 1); |
1112 | 0 | prefixes.Append(p, 4); |
1113 | 0 | } |
1114 | 0 |
|
1115 | 0 | aTableUpdate.NewPrefixes(4, prefixes); |
1116 | 0 | return NS_OK; |
1117 | 0 | } |
1118 | | |
1119 | | nsresult |
1120 | | ProtocolParserProtobuf::ProcessEncodedRemoval(TableUpdateV4& aTableUpdate, |
1121 | | const ThreatEntrySet& aRemoval) |
1122 | 0 | { |
1123 | 0 | if (!aRemoval.has_rice_indices()) { |
1124 | 0 | PARSER_LOG(("* No rice encoded removal.")); |
1125 | 0 | return NS_OK; |
1126 | 0 | } |
1127 | 0 |
|
1128 | 0 | nsTArray<uint32_t> decoded; |
1129 | 0 | nsresult rv = DoRiceDeltaDecode(aRemoval.rice_indices(), decoded); |
1130 | 0 | if (NS_FAILED(rv)) { |
1131 | 0 | PARSER_LOG(("Failed to decode encoded removal indices.")); |
1132 | 0 | return rv; |
1133 | 0 | } |
1134 | 0 |
|
1135 | 0 | // The encoded prefixes are always 4 bytes. |
1136 | 0 | rv = aTableUpdate.NewRemovalIndices(&decoded[0], decoded.Length()); |
1137 | 0 | if (NS_FAILED(rv)) { |
1138 | 0 | PARSER_LOG(("Failed to create new removal indices.")); |
1139 | 0 | return rv; |
1140 | 0 | } |
1141 | 0 |
|
1142 | 0 | return NS_OK; |
1143 | 0 | } |
1144 | | |
1145 | | } // namespace safebrowsing |
1146 | | } // namespace mozilla |