OLD | NEW |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/extensions/activity_log/activity_actions.h" | 5 #include "chrome/browser/extensions/activity_log/activity_actions.h" |
6 | 6 |
7 #include <algorithm> // for std::find. | 7 #include <algorithm> // for std::find. |
8 #include <string> | 8 #include <string> |
9 | 9 |
10 #include "base/command_line.h" | 10 #include "base/command_line.h" |
11 #include "base/format_macros.h" | 11 #include "base/format_macros.h" |
12 #include "base/json/json_string_value_serializer.h" | 12 #include "base/json/json_string_value_serializer.h" |
13 #include "base/logging.h" | 13 #include "base/logging.h" |
14 #include "base/macros.h" | 14 #include "base/macros.h" |
15 #include "base/memory/singleton.h" | 15 #include "base/memory/singleton.h" |
16 #include "base/metrics/histogram.h" | 16 #include "base/metrics/histogram.h" |
17 #include "base/strings/string_number_conversions.h" | 17 #include "base/strings/string_number_conversions.h" |
18 #include "base/strings/string_util.h" | 18 #include "base/strings/string_util.h" |
19 #include "base/strings/stringprintf.h" | 19 #include "base/strings/stringprintf.h" |
20 #include "base/values.h" | 20 #include "base/values.h" |
21 #include "chrome/browser/extensions/activity_log/activity_action_constants.h" | 21 #include "chrome/browser/extensions/activity_log/activity_action_constants.h" |
22 #include "chrome/browser/extensions/activity_log/ad_network_database.h" | 22 #include "chrome/browser/extensions/activity_log/ad_network_database.h" |
23 #include "chrome/browser/extensions/activity_log/fullstream_ui_policy.h" | 23 #include "chrome/browser/extensions/activity_log/fullstream_ui_policy.h" |
24 #include "chrome/browser/ui/browser.h" | 24 #include "chrome/browser/ui/browser.h" |
25 #include "chrome/common/chrome_switches.h" | 25 #include "chrome/common/chrome_switches.h" |
26 #include "components/rappor/rappor_service.h" | 26 #include "components/rappor/rappor_service.h" |
27 #include "content/public/browser/web_contents.h" | 27 #include "content/public/browser/web_contents.h" |
28 #include "extensions/common/ad_injection_constants.h" | |
29 #include "extensions/common/constants.h" | 28 #include "extensions/common/constants.h" |
30 #include "extensions/common/dom_action_types.h" | 29 #include "extensions/common/dom_action_types.h" |
31 #include "sql/statement.h" | 30 #include "sql/statement.h" |
32 #include "url/gurl.h" | 31 #include "url/gurl.h" |
33 | 32 |
34 namespace constants = activity_log_constants; | 33 namespace constants = activity_log_constants; |
35 | 34 |
36 namespace extensions { | 35 namespace extensions { |
37 | 36 |
38 namespace { | 37 namespace { |
39 | 38 |
40 namespace keys = ad_injection_constants::keys; | |
41 | |
42 // The list of APIs for which we upload the URL to RAPPOR. | |
43 const char* kApisForRapporMetric[] = { | |
44 ad_injection_constants::kHtmlIframeSrcApiName, | |
45 ad_injection_constants::kHtmlEmbedSrcApiName, | |
46 ad_injection_constants::kHtmlAnchorHrefApiName | |
47 }; | |
48 | |
49 // The "Extensions.PossibleAdInjection2" metric uses different Rappor | 39 // The "Extensions.PossibleAdInjection2" metric uses different Rappor |
50 // parameters than the original metric. | 40 // parameters than the original metric. |
51 const char* kExtensionAdInjectionRapporMetricName = | 41 const char* kExtensionAdInjectionRapporMetricName = |
52 "Extensions.PossibleAdInjection2"; | 42 "Extensions.PossibleAdInjection2"; |
53 | 43 |
54 // The names of different types of HTML elements we check for ad injection. | 44 const char kBlinkSetAttributeEvent[] = "blinkSetAttribute"; |
55 const char* kIframeElementType = "HTMLIFrameElement"; | 45 const char kBlinkAddElementEvent[] = "blinkAddElement"; |
56 const char* kEmbedElementType = "HTMLEmbedElement"; | 46 |
57 const char* kAnchorElementType = "HTMLAnchorElement"; | 47 const char kIframe[] = "iframe"; |
| 48 const char kAnchor[] = "a"; |
| 49 |
| 50 const char kSrc[] = "src"; |
| 51 const char kHref[] = "href"; |
58 | 52 |
59 std::string Serialize(const base::Value* value) { | 53 std::string Serialize(const base::Value* value) { |
60 std::string value_as_text; | 54 std::string value_as_text; |
61 if (!value) { | 55 if (!value) { |
62 value_as_text = "null"; | 56 value_as_text = "null"; |
63 } else { | 57 } else { |
64 JSONStringValueSerializer serializer(&value_as_text); | 58 JSONStringValueSerializer serializer(&value_as_text); |
65 serializer.SerializeAndOmitBinaryValues(*value); | 59 serializer.SerializeAndOmitBinaryValues(*value); |
66 } | 60 } |
67 return value_as_text; | 61 return value_as_text; |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
113 // We should always have an AdNetworkDatabase, but, on the offchance we don't, | 107 // We should always have an AdNetworkDatabase, but, on the offchance we don't, |
114 // don't crash in a release build. | 108 // don't crash in a release build. |
115 if (!AdNetworkDatabase::Get()) { | 109 if (!AdNetworkDatabase::Get()) { |
116 NOTREACHED(); | 110 NOTREACHED(); |
117 return NO_AD_INJECTION; | 111 return NO_AD_INJECTION; |
118 } | 112 } |
119 | 113 |
120 AdType ad_type = AD_TYPE_NONE; | 114 AdType ad_type = AD_TYPE_NONE; |
121 InjectionType injection_type = NO_AD_INJECTION; | 115 InjectionType injection_type = NO_AD_INJECTION; |
122 | 116 |
123 if (EndsWith(api_name_, | 117 if (api_name_ == kBlinkSetAttributeEvent) { |
124 ad_injection_constants::kAppendChildApiSuffix, | 118 std::string element_name; |
125 true /* case senstive */)) { | 119 std::string attr_name; |
126 injection_type = CheckAppendChild(&ad_type); | 120 if (args_.get()) { |
127 } else { | 121 args_->GetString(0u, &element_name); |
128 // Check if the action modified an element's src/href. | 122 args_->GetString(1u, &attr_name); |
129 if (api_name_ == ad_injection_constants::kHtmlIframeSrcApiName) | 123 } |
| 124 if (element_name == kIframe && attr_name == kSrc) |
130 ad_type = AD_TYPE_IFRAME; | 125 ad_type = AD_TYPE_IFRAME; |
131 else if (api_name_ == ad_injection_constants::kHtmlEmbedSrcApiName) | 126 else if (element_name == kAnchor && attr_name == kHref) |
132 ad_type = AD_TYPE_EMBED; | |
133 else if (api_name_ == ad_injection_constants::kHtmlAnchorHrefApiName) | |
134 ad_type = AD_TYPE_ANCHOR; | 127 ad_type = AD_TYPE_ANCHOR; |
135 | 128 |
136 if (ad_type != AD_TYPE_NONE) | 129 if (ad_type != AD_TYPE_NONE) |
137 injection_type = CheckSrcModification(); | 130 injection_type = CheckAttrModification(); |
| 131 } else if (api_name_ == kBlinkAddElementEvent) { |
| 132 std::string element_name; |
| 133 if (args_.get()) |
| 134 args_->GetString(0u, &element_name); |
| 135 if (element_name == kIframe) |
| 136 ad_type = AD_TYPE_IFRAME; |
| 137 else if (element_name == kAnchor) |
| 138 ad_type = AD_TYPE_ANCHOR; |
| 139 |
| 140 if (ad_type != AD_TYPE_NONE) |
| 141 injection_type = CheckElementAddition(); |
138 } | 142 } |
139 | 143 |
140 if (injection_type != NO_AD_INJECTION) { | 144 if (injection_type != NO_AD_INJECTION) { |
141 UMA_HISTOGRAM_ENUMERATION( | 145 UMA_HISTOGRAM_ENUMERATION( |
142 "Extensions.AdInjection.AdType", ad_type, Action::NUM_AD_TYPES); | 146 "Extensions.AdInjection.AdType", ad_type, Action::NUM_AD_TYPES); |
143 } | 147 } |
144 | 148 |
145 return injection_type; | 149 return injection_type; |
146 } | 150 } |
147 | 151 |
(...skipping 218 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
366 // current page should be considered valid use), and aren't local to the | 370 // current page should be considered valid use), and aren't local to the |
367 // extension. | 371 // extension. |
368 return url.is_valid() && | 372 return url.is_valid() && |
369 !url.is_empty() && | 373 !url.is_empty() && |
370 url.host() != page_url_.host() && | 374 url.host() != page_url_.host() && |
371 !url.SchemeIs(kExtensionScheme); | 375 !url.SchemeIs(kExtensionScheme); |
372 } | 376 } |
373 | 377 |
374 void Action::MaybeUploadUrl(rappor::RapporService* rappor_service) const { | 378 void Action::MaybeUploadUrl(rappor::RapporService* rappor_service) const { |
375 // Don't bother recording if the url is innocuous (or no |rappor_service|). | 379 // Don't bother recording if the url is innocuous (or no |rappor_service|). |
376 if (!rappor_service || !UrlCouldBeAd(arg_url_)) | 380 if (!rappor_service) |
377 return; | 381 return; |
378 | 382 |
379 bool can_inject_ads = false; | 383 GURL url; |
380 for (size_t i = 0; i < arraysize(kApisForRapporMetric); ++i) { | 384 |
381 if (api_name_ == kApisForRapporMetric[i]) { | 385 if (api_name_ == kBlinkSetAttributeEvent) { |
382 can_inject_ads = true; | 386 std::string element_name; |
383 break; | 387 std::string attr_name; |
| 388 std::string url_string; |
| 389 if (args_.get()) { |
| 390 args_->GetString(0u, &element_name); |
| 391 args_->GetString(1u, &attr_name); |
| 392 } |
| 393 if (element_name == kIframe && attr_name == kSrc) { |
| 394 args_->GetString(3u, &url_string); |
| 395 url = GURL(url_string); |
| 396 } else if (element_name == kAnchor && attr_name == kHref) { |
| 397 args_->GetString(3u, &url_string); |
| 398 url = GURL(url_string); |
| 399 } |
| 400 } else if (api_name_ == kBlinkAddElementEvent) { |
| 401 std::string element_name; |
| 402 std::string url_string; |
| 403 if (args_.get()) |
| 404 args_->GetString(0u, &element_name); |
| 405 if (element_name == kIframe) { |
| 406 args_->GetString(1u, &url_string); |
| 407 url = GURL(url_string); |
| 408 } else if (element_name == kAnchor) { |
| 409 args_->GetString(1u, &url_string); |
| 410 url = GURL(url_string); |
384 } | 411 } |
385 } | 412 } |
386 | 413 |
387 if (!can_inject_ads) | 414 if (!UrlCouldBeAd(url)) |
388 return; | 415 return; |
389 | 416 |
390 // Record the URL - an ad *may* have been injected. | 417 // Record the URL - an ad *may* have been injected. |
391 rappor_service->RecordSample(kExtensionAdInjectionRapporMetricName, | 418 rappor_service->RecordSample(kExtensionAdInjectionRapporMetricName, |
392 rappor::ETLD_PLUS_ONE_RAPPOR_TYPE, | 419 rappor::ETLD_PLUS_ONE_RAPPOR_TYPE, |
393 arg_url_.host()); | 420 url.host()); |
394 } | 421 } |
395 | 422 |
396 Action::InjectionType Action::CheckSrcModification() const { | 423 Action::InjectionType Action::CheckAttrModification() const { |
| 424 if (api_name_ != kBlinkSetAttributeEvent) |
| 425 return NO_AD_INJECTION; |
| 426 |
397 const AdNetworkDatabase* database = AdNetworkDatabase::Get(); | 427 const AdNetworkDatabase* database = AdNetworkDatabase::Get(); |
398 | 428 |
399 bool arg_url_could_be_ad = UrlCouldBeAd(arg_url_); | |
400 | |
401 GURL prev_url; | 429 GURL prev_url; |
402 std::string prev_url_string; | 430 std::string prev_url_string; |
403 if (args_.get() && args_->GetString(1u, &prev_url_string)) | 431 if (args_.get() && args_->GetString(2u, &prev_url_string)) |
404 prev_url = GURL(prev_url_string); | 432 prev_url = GURL(prev_url_string); |
405 | 433 |
| 434 GURL new_url; |
| 435 std::string new_url_string; |
| 436 if (args_.get() && args_->GetString(3u, &new_url_string)) |
| 437 new_url = GURL(new_url_string); |
| 438 |
| 439 bool new_url_could_be_ad = UrlCouldBeAd(new_url); |
406 bool prev_url_valid = prev_url.is_valid() && !prev_url.is_empty(); | 440 bool prev_url_valid = prev_url.is_valid() && !prev_url.is_empty(); |
407 | 441 |
408 bool injected_ad = arg_url_could_be_ad && database->IsAdNetwork(arg_url_); | 442 bool injected_ad = new_url_could_be_ad && database->IsAdNetwork(new_url); |
409 bool replaced_ad = prev_url_valid && database->IsAdNetwork(prev_url); | 443 bool replaced_ad = prev_url_valid && database->IsAdNetwork(prev_url); |
410 | 444 |
411 if (injected_ad && replaced_ad) | 445 if (injected_ad && replaced_ad) |
412 return INJECTION_REPLACED_AD; | 446 return INJECTION_REPLACED_AD; |
413 if (injected_ad) | 447 if (injected_ad) |
414 return INJECTION_NEW_AD; | 448 return INJECTION_NEW_AD; |
415 if (replaced_ad) | 449 if (replaced_ad) |
416 return INJECTION_REMOVED_AD; | 450 return INJECTION_REMOVED_AD; |
417 | 451 |
418 // If the extension modified the URL with an external, valid URL then there's | 452 // If the extension modified the URL with an external, valid URL then there's |
419 // a good chance it's ad injection. Log it as a likely one, which also helps | 453 // a good chance it's ad injection. Log it as a likely one, which also helps |
420 // us determine the effectiveness of our IsAdNetwork() recognition. | 454 // us determine the effectiveness of our IsAdNetwork() recognition. |
421 if (arg_url_could_be_ad) { | 455 if (new_url_could_be_ad) { |
422 if (prev_url_valid) | 456 if (prev_url_valid) |
423 return INJECTION_LIKELY_REPLACED_AD; | 457 return INJECTION_LIKELY_REPLACED_AD; |
424 return INJECTION_LIKELY_NEW_AD; | 458 return INJECTION_LIKELY_NEW_AD; |
425 } | 459 } |
426 | 460 |
427 return NO_AD_INJECTION; | 461 return NO_AD_INJECTION; |
428 } | 462 } |
429 | 463 |
430 Action::InjectionType Action::CheckAppendChild(AdType* ad_type_out) const { | 464 Action::InjectionType Action::CheckElementAddition() const { |
431 const base::DictionaryValue* child = NULL; | 465 if (api_name_ != kBlinkAddElementEvent) |
432 if (!args_->GetDictionary(0u, &child)) | |
433 return NO_AD_INJECTION; | 466 return NO_AD_INJECTION; |
434 | 467 |
435 return CheckDomObject(child, ad_type_out); | 468 GURL url; |
436 } | 469 std::string url_string; |
| 470 if (args_.get() && args_->GetString(1u, &url_string)) |
| 471 url = GURL(url_string); |
437 | 472 |
438 Action::InjectionType Action::CheckDomObject( | 473 if (UrlCouldBeAd(url)) { |
439 const base::DictionaryValue* object, | 474 if (AdNetworkDatabase::Get()->IsAdNetwork(url)) |
440 AdType* ad_type_out) const { | 475 return INJECTION_NEW_AD; |
441 DCHECK(ad_type_out); | 476 // If the extension injected an URL which is not local to itself or the |
442 std::string type; | 477 // page, there is a good chance it could be a new ad, and our database |
443 object->GetString(keys::kType, &type); | 478 // missed it. |
444 | 479 return INJECTION_LIKELY_NEW_AD; |
445 AdType ad_type = AD_TYPE_NONE; | |
446 std::string url_key; | |
447 if (type == kIframeElementType) { | |
448 ad_type = AD_TYPE_IFRAME; | |
449 url_key = keys::kSrc; | |
450 } else if (type == kEmbedElementType) { | |
451 ad_type = AD_TYPE_EMBED; | |
452 url_key = keys::kSrc; | |
453 } else if (type == kAnchorElementType) { | |
454 ad_type = AD_TYPE_ANCHOR; | |
455 url_key = keys::kHref; | |
456 } | 480 } |
457 | |
458 if (!url_key.empty()) { | |
459 std::string url; | |
460 if (object->GetString(url_key, &url)) { | |
461 GURL gurl(url); | |
462 if (UrlCouldBeAd(gurl)) { | |
463 *ad_type_out = ad_type; | |
464 if (AdNetworkDatabase::Get()->IsAdNetwork(gurl)) | |
465 return INJECTION_NEW_AD; | |
466 // If the extension injected an URL which is not local to itself or the | |
467 // page, there is a good chance it could be a new ad, and our database | |
468 // missed it. | |
469 return INJECTION_LIKELY_NEW_AD; | |
470 } | |
471 } | |
472 } | |
473 | |
474 const base::ListValue* children = NULL; | |
475 if (object->GetList(keys::kChildren, &children)) { | |
476 const base::DictionaryValue* child = NULL; | |
477 for (size_t i = 0; | |
478 i < children->GetSize() && | |
479 i < ad_injection_constants::kMaximumChildrenToCheck; | |
480 ++i) { | |
481 if (children->GetDictionary(i, &child)) { | |
482 InjectionType type = CheckDomObject(child, ad_type_out); | |
483 if (type != NO_AD_INJECTION) | |
484 return type; | |
485 } | |
486 } | |
487 } | |
488 | |
489 return NO_AD_INJECTION; | 481 return NO_AD_INJECTION; |
490 } | 482 } |
491 | 483 |
492 bool ActionComparator::operator()( | 484 bool ActionComparator::operator()( |
493 const scoped_refptr<Action>& lhs, | 485 const scoped_refptr<Action>& lhs, |
494 const scoped_refptr<Action>& rhs) const { | 486 const scoped_refptr<Action>& rhs) const { |
495 if (lhs->time() != rhs->time()) | 487 if (lhs->time() != rhs->time()) |
496 return lhs->time() < rhs->time(); | 488 return lhs->time() < rhs->time(); |
497 else if (lhs->action_id() != rhs->action_id()) | 489 else if (lhs->action_id() != rhs->action_id()) |
498 return lhs->action_id() < rhs->action_id(); | 490 return lhs->action_id() < rhs->action_id(); |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
547 std::string rhs_other = ActivityLogPolicy::Util::Serialize(rhs->other()); | 539 std::string rhs_other = ActivityLogPolicy::Util::Serialize(rhs->other()); |
548 if (lhs_other != rhs_other) | 540 if (lhs_other != rhs_other) |
549 return lhs_other < rhs_other; | 541 return lhs_other < rhs_other; |
550 } | 542 } |
551 | 543 |
552 // All fields compare as equal if this point is reached. | 544 // All fields compare as equal if this point is reached. |
553 return false; | 545 return false; |
554 } | 546 } |
555 | 547 |
556 } // namespace extensions | 548 } // namespace extensions |
OLD | NEW |