toolkit/components/url-classifier/ProtocolParser.cpp

branch
TOR_BUG_9701
changeset 14
925c144e1f1f
equal deleted inserted replaced
-1:000000000000 0:496ffe800f1b
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 "prlog.h"
10 #include "prnetdb.h"
11 #include "prprf.h"
12
13 #include "nsUrlClassifierUtils.h"
14
15 // NSPR_LOG_MODULES=UrlClassifierDbService:5
16 extern PRLogModuleInfo *gUrlClassifierDbServiceLog;
17 #if defined(PR_LOGGING)
18 #define LOG(args) PR_LOG(gUrlClassifierDbServiceLog, PR_LOG_DEBUG, args)
19 #define LOG_ENABLED() PR_LOG_TEST(gUrlClassifierDbServiceLog, 4)
20 #else
21 #define LOG(args)
22 #define LOG_ENABLED() (false)
23 #endif
24
25 namespace mozilla {
26 namespace safebrowsing {
27
28 // Updates will fail if fed chunks larger than this
29 const uint32_t MAX_CHUNK_SIZE = (1024 * 1024);
30
31 const uint32_t DOMAIN_SIZE = 4;
32
33 // Parse one stringified range of chunks of the form "n" or "n-m" from a
34 // comma-separated list of chunks. Upon return, 'begin' will point to the
35 // next range of chunks in the list of chunks.
36 static bool
37 ParseChunkRange(nsACString::const_iterator& aBegin,
38 const nsACString::const_iterator& aEnd,
39 uint32_t* aFirst, uint32_t* aLast)
40 {
41 nsACString::const_iterator iter = aBegin;
42 FindCharInReadable(',', iter, aEnd);
43
44 nsAutoCString element(Substring(aBegin, iter));
45 aBegin = iter;
46 if (aBegin != aEnd)
47 aBegin++;
48
49 uint32_t numRead = PR_sscanf(element.get(), "%u-%u", aFirst, aLast);
50 if (numRead == 2) {
51 if (*aFirst > *aLast) {
52 uint32_t tmp = *aFirst;
53 *aFirst = *aLast;
54 *aLast = tmp;
55 }
56 return true;
57 }
58
59 if (numRead == 1) {
60 *aLast = *aFirst;
61 return true;
62 }
63
64 return false;
65 }
66
67 ProtocolParser::ProtocolParser()
68 : mState(PROTOCOL_STATE_CONTROL)
69 , mUpdateStatus(NS_OK)
70 , mUpdateWait(0)
71 , mResetRequested(false)
72 {
73 }
74
75 ProtocolParser::~ProtocolParser()
76 {
77 CleanupUpdates();
78 }
79
80 nsresult
81 ProtocolParser::Init(nsICryptoHash* aHasher)
82 {
83 mCryptoHash = aHasher;
84 return NS_OK;
85 }
86
87 void
88 ProtocolParser::SetCurrentTable(const nsACString& aTable)
89 {
90 mTableUpdate = GetTableUpdate(aTable);
91 }
92
93 nsresult
94 ProtocolParser::AppendStream(const nsACString& aData)
95 {
96 if (NS_FAILED(mUpdateStatus))
97 return mUpdateStatus;
98
99 nsresult rv;
100 mPending.Append(aData);
101
102 bool done = false;
103 while (!done) {
104 if (mState == PROTOCOL_STATE_CONTROL) {
105 rv = ProcessControl(&done);
106 } else if (mState == PROTOCOL_STATE_CHUNK) {
107 rv = ProcessChunk(&done);
108 } else {
109 NS_ERROR("Unexpected protocol state");
110 rv = NS_ERROR_FAILURE;
111 }
112 if (NS_FAILED(rv)) {
113 mUpdateStatus = rv;
114 return rv;
115 }
116 }
117 return NS_OK;
118 }
119
120 nsresult
121 ProtocolParser::ProcessControl(bool* aDone)
122 {
123 nsresult rv;
124
125 nsAutoCString line;
126 *aDone = true;
127 while (NextLine(line)) {
128 //LOG(("Processing %s\n", line.get()));
129
130 if (StringBeginsWith(line, NS_LITERAL_CSTRING("i:"))) {
131 // Set the table name from the table header line.
132 SetCurrentTable(Substring(line, 2));
133 } else if (StringBeginsWith(line, NS_LITERAL_CSTRING("n:"))) {
134 if (PR_sscanf(line.get(), "n:%d", &mUpdateWait) != 1) {
135 LOG(("Error parsing n: '%s' (%d)", line.get(), mUpdateWait));
136 mUpdateWait = 0;
137 }
138 } else if (line.EqualsLiteral("r:pleasereset")) {
139 mResetRequested = true;
140 } else if (StringBeginsWith(line, NS_LITERAL_CSTRING("u:"))) {
141 rv = ProcessForward(line);
142 NS_ENSURE_SUCCESS(rv, rv);
143 } else if (StringBeginsWith(line, NS_LITERAL_CSTRING("a:")) ||
144 StringBeginsWith(line, NS_LITERAL_CSTRING("s:"))) {
145 rv = ProcessChunkControl(line);
146 NS_ENSURE_SUCCESS(rv, rv);
147 *aDone = false;
148 return NS_OK;
149 } else if (StringBeginsWith(line, NS_LITERAL_CSTRING("ad:")) ||
150 StringBeginsWith(line, NS_LITERAL_CSTRING("sd:"))) {
151 rv = ProcessExpirations(line);
152 NS_ENSURE_SUCCESS(rv, rv);
153 }
154 }
155
156 *aDone = true;
157 return NS_OK;
158 }
159
160 nsresult
161 ProtocolParser::ProcessExpirations(const nsCString& aLine)
162 {
163 if (!mTableUpdate) {
164 NS_WARNING("Got an expiration without a table.");
165 return NS_ERROR_FAILURE;
166 }
167 const nsCSubstring &list = Substring(aLine, 3);
168 nsACString::const_iterator begin, end;
169 list.BeginReading(begin);
170 list.EndReading(end);
171 while (begin != end) {
172 uint32_t first, last;
173 if (ParseChunkRange(begin, end, &first, &last)) {
174 for (uint32_t num = first; num <= last; num++) {
175 if (aLine[0] == 'a')
176 mTableUpdate->NewAddExpiration(num);
177 else
178 mTableUpdate->NewSubExpiration(num);
179 }
180 } else {
181 return NS_ERROR_FAILURE;
182 }
183 }
184 return NS_OK;
185 }
186
187 nsresult
188 ProtocolParser::ProcessChunkControl(const nsCString& aLine)
189 {
190 if (!mTableUpdate) {
191 NS_WARNING("Got a chunk before getting a table.");
192 return NS_ERROR_FAILURE;
193 }
194
195 mState = PROTOCOL_STATE_CHUNK;
196 char command;
197
198 mChunkState.Clear();
199
200 if (PR_sscanf(aLine.get(),
201 "%c:%d:%d:%d",
202 &command,
203 &mChunkState.num, &mChunkState.hashSize, &mChunkState.length)
204 != 4)
205 {
206 return NS_ERROR_FAILURE;
207 }
208
209 if (mChunkState.length > MAX_CHUNK_SIZE) {
210 return NS_ERROR_FAILURE;
211 }
212
213 if (!(mChunkState.hashSize == PREFIX_SIZE || mChunkState.hashSize == COMPLETE_SIZE)) {
214 NS_WARNING("Invalid hash size specified in update.");
215 return NS_ERROR_FAILURE;
216 }
217
218 if (StringEndsWith(mTableUpdate->TableName(),
219 NS_LITERAL_CSTRING("-shavar")) ||
220 StringEndsWith(mTableUpdate->TableName(),
221 NS_LITERAL_CSTRING("-simple"))) {
222 // Accommodate test tables ending in -simple for now.
223 mChunkState.type = (command == 'a') ? CHUNK_ADD : CHUNK_SUB;
224 } else if (StringEndsWith(mTableUpdate->TableName(),
225 NS_LITERAL_CSTRING("-digest256"))) {
226 LOG(("Processing digest256 data"));
227 mChunkState.type = (command == 'a') ? CHUNK_ADD_DIGEST : CHUNK_SUB_DIGEST;
228 }
229 switch (mChunkState.type) {
230 case CHUNK_ADD:
231 mTableUpdate->NewAddChunk(mChunkState.num);
232 break;
233 case CHUNK_SUB:
234 mTableUpdate->NewSubChunk(mChunkState.num);
235 break;
236 case CHUNK_ADD_DIGEST:
237 mTableUpdate->NewAddChunk(mChunkState.num);
238 break;
239 case CHUNK_SUB_DIGEST:
240 mTableUpdate->NewSubChunk(mChunkState.num);
241 break;
242 }
243
244 return NS_OK;
245 }
246
247 nsresult
248 ProtocolParser::ProcessForward(const nsCString& aLine)
249 {
250 const nsCSubstring &forward = Substring(aLine, 2);
251 return AddForward(forward);
252 }
253
254 nsresult
255 ProtocolParser::AddForward(const nsACString& aUrl)
256 {
257 if (!mTableUpdate) {
258 NS_WARNING("Forward without a table name.");
259 return NS_ERROR_FAILURE;
260 }
261
262 ForwardedUpdate *forward = mForwards.AppendElement();
263 forward->table = mTableUpdate->TableName();
264 forward->url.Assign(aUrl);
265
266 return NS_OK;
267 }
268
269 nsresult
270 ProtocolParser::ProcessChunk(bool* aDone)
271 {
272 if (!mTableUpdate) {
273 NS_WARNING("Processing chunk without an active table.");
274 return NS_ERROR_FAILURE;
275 }
276
277 NS_ASSERTION(mChunkState.num != 0, "Must have a chunk number.");
278
279 if (mPending.Length() < mChunkState.length) {
280 *aDone = true;
281 return NS_OK;
282 }
283
284 // Pull the chunk out of the pending stream data.
285 nsAutoCString chunk;
286 chunk.Assign(Substring(mPending, 0, mChunkState.length));
287 mPending = Substring(mPending, mChunkState.length);
288
289 *aDone = false;
290 mState = PROTOCOL_STATE_CONTROL;
291
292 //LOG(("Handling a %d-byte chunk", chunk.Length()));
293 if (StringEndsWith(mTableUpdate->TableName(),
294 NS_LITERAL_CSTRING("-shavar"))) {
295 return ProcessShaChunk(chunk);
296 }
297 if (StringEndsWith(mTableUpdate->TableName(),
298 NS_LITERAL_CSTRING("-digest256"))) {
299 return ProcessDigestChunk(chunk);
300 }
301 return ProcessPlaintextChunk(chunk);
302 }
303
304 /**
305 * Process a plaintext chunk (currently only used in unit tests).
306 */
307 nsresult
308 ProtocolParser::ProcessPlaintextChunk(const nsACString& aChunk)
309 {
310 if (!mTableUpdate) {
311 NS_WARNING("Chunk received with no table.");
312 return NS_ERROR_FAILURE;
313 }
314
315 nsTArray<nsCString> lines;
316 ParseString(PromiseFlatCString(aChunk), '\n', lines);
317
318 // non-hashed tables need to be hashed
319 for (uint32_t i = 0; i < lines.Length(); i++) {
320 nsCString& line = lines[i];
321
322 if (mChunkState.type == CHUNK_ADD) {
323 if (mChunkState.hashSize == COMPLETE_SIZE) {
324 Completion hash;
325 hash.FromPlaintext(line, mCryptoHash);
326 mTableUpdate->NewAddComplete(mChunkState.num, hash);
327 } else {
328 NS_ASSERTION(mChunkState.hashSize == 4, "Only 32- or 4-byte hashes can be used for add chunks.");
329 Prefix hash;
330 hash.FromPlaintext(line, mCryptoHash);
331 mTableUpdate->NewAddPrefix(mChunkState.num, hash);
332 }
333 } else {
334 nsCString::const_iterator begin, iter, end;
335 line.BeginReading(begin);
336 line.EndReading(end);
337 iter = begin;
338 uint32_t addChunk;
339 if (!FindCharInReadable(':', iter, end) ||
340 PR_sscanf(lines[i].get(), "%d:", &addChunk) != 1) {
341 NS_WARNING("Received sub chunk without associated add chunk.");
342 return NS_ERROR_FAILURE;
343 }
344 iter++;
345
346 if (mChunkState.hashSize == COMPLETE_SIZE) {
347 Completion hash;
348 hash.FromPlaintext(Substring(iter, end), mCryptoHash);
349 mTableUpdate->NewSubComplete(addChunk, hash, mChunkState.num);
350 } else {
351 NS_ASSERTION(mChunkState.hashSize == 4, "Only 32- or 4-byte hashes can be used for add chunks.");
352 Prefix hash;
353 hash.FromPlaintext(Substring(iter, end), mCryptoHash);
354 mTableUpdate->NewSubPrefix(addChunk, hash, mChunkState.num);
355 }
356 }
357 }
358
359 return NS_OK;
360 }
361
362 nsresult
363 ProtocolParser::ProcessShaChunk(const nsACString& aChunk)
364 {
365 uint32_t start = 0;
366 while (start < aChunk.Length()) {
367 // First four bytes are the domain key.
368 Prefix domain;
369 domain.Assign(Substring(aChunk, start, DOMAIN_SIZE));
370 start += DOMAIN_SIZE;
371
372 // Then a count of entries.
373 uint8_t numEntries = static_cast<uint8_t>(aChunk[start]);
374 start++;
375
376 nsresult rv;
377 if (mChunkState.type == CHUNK_ADD && mChunkState.hashSize == PREFIX_SIZE) {
378 rv = ProcessHostAdd(domain, numEntries, aChunk, &start);
379 } else if (mChunkState.type == CHUNK_ADD && mChunkState.hashSize == COMPLETE_SIZE) {
380 rv = ProcessHostAddComplete(numEntries, aChunk, &start);
381 } else if (mChunkState.type == CHUNK_SUB && mChunkState.hashSize == PREFIX_SIZE) {
382 rv = ProcessHostSub(domain, numEntries, aChunk, &start);
383 } else if (mChunkState.type == CHUNK_SUB && mChunkState.hashSize == COMPLETE_SIZE) {
384 rv = ProcessHostSubComplete(numEntries, aChunk, &start);
385 } else {
386 NS_WARNING("Unexpected chunk type/hash size!");
387 LOG(("Got an unexpected chunk type/hash size: %s:%d",
388 mChunkState.type == CHUNK_ADD ? "add" : "sub",
389 mChunkState.hashSize));
390 return NS_ERROR_FAILURE;
391 }
392 NS_ENSURE_SUCCESS(rv, rv);
393 }
394
395 return NS_OK;
396 }
397
398 nsresult
399 ProtocolParser::ProcessDigestChunk(const nsACString& aChunk)
400 {
401 if (mChunkState.type == CHUNK_ADD_DIGEST) {
402 return ProcessDigestAdd(aChunk);
403 }
404 if (mChunkState.type == CHUNK_SUB_DIGEST) {
405 return ProcessDigestSub(aChunk);
406 }
407 return NS_ERROR_UNEXPECTED;
408 }
409
410 nsresult
411 ProtocolParser::ProcessDigestAdd(const nsACString& aChunk)
412 {
413 // The ABNF format for add chunks is (HASH)+, where HASH is 32 bytes.
414 MOZ_ASSERT(aChunk.Length() % 32 == 0,
415 "Chunk length in bytes must be divisible by 4");
416 uint32_t start = 0;
417 while (start < aChunk.Length()) {
418 Completion hash;
419 hash.Assign(Substring(aChunk, start, COMPLETE_SIZE));
420 start += COMPLETE_SIZE;
421 mTableUpdate->NewAddComplete(mChunkState.num, hash);
422 }
423 return NS_OK;
424 }
425
426 nsresult
427 ProtocolParser::ProcessDigestSub(const nsACString& aChunk)
428 {
429 // The ABNF format for sub chunks is (ADDCHUNKNUM HASH)+, where ADDCHUNKNUM
430 // is a 4 byte chunk number, and HASH is 32 bytes.
431 MOZ_ASSERT(aChunk.Length() % 36 == 0,
432 "Chunk length in bytes must be divisible by 36");
433 uint32_t start = 0;
434 while (start < aChunk.Length()) {
435 // Read ADDCHUNKNUM
436 const nsCSubstring& addChunkStr = Substring(aChunk, start, 4);
437 start += 4;
438
439 uint32_t addChunk;
440 memcpy(&addChunk, addChunkStr.BeginReading(), 4);
441 addChunk = PR_ntohl(addChunk);
442
443 // Read the hash
444 Completion hash;
445 hash.Assign(Substring(aChunk, start, COMPLETE_SIZE));
446 start += COMPLETE_SIZE;
447
448 mTableUpdate->NewSubComplete(addChunk, hash, mChunkState.num);
449 }
450 return NS_OK;
451 }
452
453 nsresult
454 ProtocolParser::ProcessHostAdd(const Prefix& aDomain, uint8_t aNumEntries,
455 const nsACString& aChunk, uint32_t* aStart)
456 {
457 NS_ASSERTION(mChunkState.hashSize == PREFIX_SIZE,
458 "ProcessHostAdd should only be called for prefix hashes.");
459
460 if (aNumEntries == 0) {
461 mTableUpdate->NewAddPrefix(mChunkState.num, aDomain);
462 return NS_OK;
463 }
464
465 if (*aStart + (PREFIX_SIZE * aNumEntries) > aChunk.Length()) {
466 NS_WARNING("Chunk is not long enough to contain the expected entries.");
467 return NS_ERROR_FAILURE;
468 }
469
470 for (uint8_t i = 0; i < aNumEntries; i++) {
471 Prefix hash;
472 hash.Assign(Substring(aChunk, *aStart, PREFIX_SIZE));
473 mTableUpdate->NewAddPrefix(mChunkState.num, hash);
474 *aStart += PREFIX_SIZE;
475 }
476
477 return NS_OK;
478 }
479
480 nsresult
481 ProtocolParser::ProcessHostSub(const Prefix& aDomain, uint8_t aNumEntries,
482 const nsACString& aChunk, uint32_t *aStart)
483 {
484 NS_ASSERTION(mChunkState.hashSize == PREFIX_SIZE,
485 "ProcessHostSub should only be called for prefix hashes.");
486
487 if (aNumEntries == 0) {
488 if ((*aStart) + 4 > aChunk.Length()) {
489 NS_WARNING("Received a zero-entry sub chunk without an associated add.");
490 return NS_ERROR_FAILURE;
491 }
492
493 const nsCSubstring& addChunkStr = Substring(aChunk, *aStart, 4);
494 *aStart += 4;
495
496 uint32_t addChunk;
497 memcpy(&addChunk, addChunkStr.BeginReading(), 4);
498 addChunk = PR_ntohl(addChunk);
499
500 mTableUpdate->NewSubPrefix(addChunk, aDomain, mChunkState.num);
501 return NS_OK;
502 }
503
504 if (*aStart + ((PREFIX_SIZE + 4) * aNumEntries) > aChunk.Length()) {
505 NS_WARNING("Chunk is not long enough to contain the expected entries.");
506 return NS_ERROR_FAILURE;
507 }
508
509 for (uint8_t i = 0; i < aNumEntries; i++) {
510 const nsCSubstring& addChunkStr = Substring(aChunk, *aStart, 4);
511 *aStart += 4;
512
513 uint32_t addChunk;
514 memcpy(&addChunk, addChunkStr.BeginReading(), 4);
515 addChunk = PR_ntohl(addChunk);
516
517 Prefix prefix;
518 prefix.Assign(Substring(aChunk, *aStart, PREFIX_SIZE));
519 *aStart += PREFIX_SIZE;
520
521 mTableUpdate->NewSubPrefix(addChunk, prefix, mChunkState.num);
522 }
523
524 return NS_OK;
525 }
526
527 nsresult
528 ProtocolParser::ProcessHostAddComplete(uint8_t aNumEntries,
529 const nsACString& aChunk, uint32_t* aStart)
530 {
531 NS_ASSERTION(mChunkState.hashSize == COMPLETE_SIZE,
532 "ProcessHostAddComplete should only be called for complete hashes.");
533
534 if (aNumEntries == 0) {
535 // this is totally comprehensible.
536 // My sarcasm detector is going off!
537 NS_WARNING("Expected > 0 entries for a 32-byte hash add.");
538 return NS_OK;
539 }
540
541 if (*aStart + (COMPLETE_SIZE * aNumEntries) > aChunk.Length()) {
542 NS_WARNING("Chunk is not long enough to contain the expected entries.");
543 return NS_ERROR_FAILURE;
544 }
545
546 for (uint8_t i = 0; i < aNumEntries; i++) {
547 Completion hash;
548 hash.Assign(Substring(aChunk, *aStart, COMPLETE_SIZE));
549 mTableUpdate->NewAddComplete(mChunkState.num, hash);
550 *aStart += COMPLETE_SIZE;
551 }
552
553 return NS_OK;
554 }
555
556 nsresult
557 ProtocolParser::ProcessHostSubComplete(uint8_t aNumEntries,
558 const nsACString& aChunk, uint32_t* aStart)
559 {
560 NS_ASSERTION(mChunkState.hashSize == COMPLETE_SIZE,
561 "ProcessHostSubComplete should only be called for complete hashes.");
562
563 if (aNumEntries == 0) {
564 // this is totally comprehensible.
565 NS_WARNING("Expected > 0 entries for a 32-byte hash sub.");
566 return NS_OK;
567 }
568
569 if (*aStart + ((COMPLETE_SIZE + 4) * aNumEntries) > aChunk.Length()) {
570 NS_WARNING("Chunk is not long enough to contain the expected entries.");
571 return NS_ERROR_FAILURE;
572 }
573
574 for (uint8_t i = 0; i < aNumEntries; i++) {
575 Completion hash;
576 hash.Assign(Substring(aChunk, *aStart, COMPLETE_SIZE));
577 *aStart += COMPLETE_SIZE;
578
579 const nsCSubstring& addChunkStr = Substring(aChunk, *aStart, 4);
580 *aStart += 4;
581
582 uint32_t addChunk;
583 memcpy(&addChunk, addChunkStr.BeginReading(), 4);
584 addChunk = PR_ntohl(addChunk);
585
586 mTableUpdate->NewSubComplete(addChunk, hash, mChunkState.num);
587 }
588
589 return NS_OK;
590 }
591
592 bool
593 ProtocolParser::NextLine(nsACString& line)
594 {
595 int32_t newline = mPending.FindChar('\n');
596 if (newline == kNotFound) {
597 return false;
598 }
599 line.Assign(Substring(mPending, 0, newline));
600 mPending = Substring(mPending, newline + 1);
601 return true;
602 }
603
604 void
605 ProtocolParser::CleanupUpdates()
606 {
607 for (uint32_t i = 0; i < mTableUpdates.Length(); i++) {
608 delete mTableUpdates[i];
609 }
610 mTableUpdates.Clear();
611 }
612
613 TableUpdate *
614 ProtocolParser::GetTableUpdate(const nsACString& aTable)
615 {
616 for (uint32_t i = 0; i < mTableUpdates.Length(); i++) {
617 if (aTable.Equals(mTableUpdates[i]->TableName())) {
618 return mTableUpdates[i];
619 }
620 }
621
622 // We free automatically on destruction, ownership of these
623 // updates can be transferred to DBServiceWorker, which passes
624 // them back to Classifier when doing the updates, and that
625 // will free them.
626 TableUpdate *update = new TableUpdate(aTable);
627 mTableUpdates.AppendElement(update);
628 return update;
629 }
630
631 } // namespace safebrowsing
632 } // namespace mozilla

mercurial