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/strings/string_number_conversions.h" | 17 #include "base/strings/string_number_conversions.h" |
17 #include "base/strings/string_util.h" | 18 #include "base/strings/string_util.h" |
18 #include "base/strings/stringprintf.h" | 19 #include "base/strings/stringprintf.h" |
19 #include "base/values.h" | 20 #include "base/values.h" |
20 #include "chrome/browser/extensions/activity_log/activity_action_constants.h" | 21 #include "chrome/browser/extensions/activity_log/activity_action_constants.h" |
21 #include "chrome/browser/extensions/activity_log/ad_network_database.h" | 22 #include "chrome/browser/extensions/activity_log/ad_network_database.h" |
22 #include "chrome/browser/extensions/activity_log/fullstream_ui_policy.h" | 23 #include "chrome/browser/extensions/activity_log/fullstream_ui_policy.h" |
23 #include "chrome/browser/ui/browser.h" | 24 #include "chrome/browser/ui/browser.h" |
24 #include "chrome/common/chrome_switches.h" | 25 #include "chrome/common/chrome_switches.h" |
25 #include "components/rappor/rappor_service.h" | 26 #include "components/rappor/rappor_service.h" |
26 #include "content/public/browser/web_contents.h" | 27 #include "content/public/browser/web_contents.h" |
27 #include "extensions/common/ad_injection_constants.h" | 28 #include "extensions/common/ad_injection_constants.h" |
28 #include "extensions/common/constants.h" | 29 #include "extensions/common/constants.h" |
29 #include "extensions/common/dom_action_types.h" | 30 #include "extensions/common/dom_action_types.h" |
30 #include "sql/statement.h" | 31 #include "sql/statement.h" |
31 #include "url/gurl.h" | 32 #include "url/gurl.h" |
32 | 33 |
33 namespace constants = activity_log_constants; | 34 namespace constants = activity_log_constants; |
34 | 35 |
35 namespace extensions { | 36 namespace extensions { |
36 | 37 |
37 namespace { | 38 namespace { |
38 | 39 |
39 namespace keys = ad_injection_constants::keys; | 40 namespace keys = ad_injection_constants::keys; |
40 | 41 |
41 // The list of APIs for which we upload the URL to RAPPOR. | 42 // The list of APIs for which we upload the URL to RAPPOR. |
42 const char* kApisForRapporMetric[] = { | 43 const char* kApisForRapporMetric[] = { |
43 "HTMLIFrameElement.src", | 44 ad_injection_constants::kHtmlIframeSrcApiName, |
44 "HTMLEmbedElement.src", | 45 ad_injection_constants::kHtmlEmbedSrcApiName, |
45 "HTMLAnchorElement.href", | 46 ad_injection_constants::kHtmlAnchorHrefApiName |
46 }; | 47 }; |
47 | 48 |
48 const char* kExtensionAdInjectionRapporMetricName = | 49 const char* kExtensionAdInjectionRapporMetricName = |
49 "Extensions.PossibleAdInjection"; | 50 "Extensions.PossibleAdInjection"; |
50 | 51 |
51 // The elements for which we check the 'src' attribute to look for ads. | 52 // The names of different types of HTML elements we check for ad injection. |
52 const char* kSrcElements[] = { | 53 const char* kIframeElementType = "HTMLIFrameElement"; |
53 "HTMLIFrameElement", | 54 const char* kEmbedElementType = "HTMLEmbedElement"; |
54 "HTMLEmbedElement" | 55 const char* kAnchorElementType = "HTMLAnchorElement"; |
55 }; | |
56 | |
57 // The elements for which we check the 'href' attribute to look for ads. | |
58 const char* kHrefElements[] = { | |
59 "HTMLAnchorElement", | |
60 }; | |
61 | |
62 bool IsSrcElement(const std::string& str) { | |
63 static const char** end = kSrcElements + arraysize(kSrcElements); | |
64 return std::find(kSrcElements, end, str) != end; | |
65 } | |
66 | |
67 bool IsHrefElement(const std::string& str) { | |
68 static const char** end = kHrefElements + arraysize(kHrefElements); | |
69 return std::find(kHrefElements, end, str) != end; | |
70 } | |
71 | 56 |
72 std::string Serialize(const base::Value* value) { | 57 std::string Serialize(const base::Value* value) { |
73 std::string value_as_text; | 58 std::string value_as_text; |
74 if (!value) { | 59 if (!value) { |
75 value_as_text = "null"; | 60 value_as_text = "null"; |
76 } else { | 61 } else { |
77 JSONStringValueSerializer serializer(&value_as_text); | 62 JSONStringValueSerializer serializer(&value_as_text); |
78 serializer.SerializeAndOmitBinaryValues(*value); | 63 serializer.SerializeAndOmitBinaryValues(*value); |
79 } | 64 } |
80 return value_as_text; | 65 return value_as_text; |
81 } | 66 } |
82 | 67 |
83 Action::InjectionType CheckDomObject(const base::DictionaryValue* object) { | |
84 std::string type; | |
85 object->GetString(keys::kType, &type); | |
86 | |
87 std::string url_key; | |
88 if (IsSrcElement(type)) | |
89 url_key = keys::kSrc; | |
90 else if (IsHrefElement(type)) | |
91 url_key = keys::kHref; | |
92 | |
93 if (!url_key.empty()) { | |
94 std::string url; | |
95 if (object->GetString(url_key, &url)) { | |
96 GURL gurl(url); | |
97 if (AdNetworkDatabase::Get()->IsAdNetwork(gurl)) | |
98 return Action::INJECTION_NEW_AD; | |
99 // If the extension injected an URL which is not local to itself, there is | |
100 // a good chance it could be a new ad, and our database missed it. | |
101 // This could be noisier than other metrics, because there are perfectly | |
102 // acceptable uses for this, like "Show my mail". | |
103 if (gurl.is_valid() && | |
104 !gurl.is_empty() && | |
105 !gurl.SchemeIs(kExtensionScheme)) { | |
106 return Action::INJECTION_LIKELY_NEW_AD; | |
107 } | |
108 } | |
109 } | |
110 | |
111 const base::ListValue* children = NULL; | |
112 if (object->GetList(keys::kChildren, &children)) { | |
113 const base::DictionaryValue* child = NULL; | |
114 for (size_t i = 0; | |
115 i < children->GetSize() && | |
116 i < ad_injection_constants::kMaximumChildrenToCheck; | |
117 ++i) { | |
118 if (children->GetDictionary(i, &child)) { | |
119 Action::InjectionType type = CheckDomObject(child); | |
120 if (type != Action::NO_AD_INJECTION) | |
121 return type; | |
122 } | |
123 } | |
124 } | |
125 | |
126 return Action::NO_AD_INJECTION; | |
127 } | |
128 | |
129 } // namespace | 68 } // namespace |
130 | 69 |
131 using api::activity_log_private::ExtensionActivity; | 70 using api::activity_log_private::ExtensionActivity; |
132 | 71 |
133 Action::Action(const std::string& extension_id, | 72 Action::Action(const std::string& extension_id, |
134 const base::Time& time, | 73 const base::Time& time, |
135 const ActionType action_type, | 74 const ActionType action_type, |
136 const std::string& api_name, | 75 const std::string& api_name, |
137 int64 action_id) | 76 int64 action_id) |
138 : extension_id_(extension_id), | 77 : extension_id_(extension_id), |
(...skipping 23 matching lines...) Expand all Loading... |
162 clone->set_arg_incognito(arg_incognito()); | 101 clone->set_arg_incognito(arg_incognito()); |
163 if (other()) | 102 if (other()) |
164 clone->set_other(make_scoped_ptr(other()->DeepCopy())); | 103 clone->set_other(make_scoped_ptr(other()->DeepCopy())); |
165 return clone; | 104 return clone; |
166 } | 105 } |
167 | 106 |
168 Action::InjectionType Action::DidInjectAd( | 107 Action::InjectionType Action::DidInjectAd( |
169 rappor::RapporService* rappor_service) const { | 108 rappor::RapporService* rappor_service) const { |
170 MaybeUploadUrl(rappor_service); | 109 MaybeUploadUrl(rappor_service); |
171 | 110 |
172 // Currently, we do not have the list of ad networks, so we exit immediately | 111 // We should always have an AdNetworkDatabase, but, on the offchance we don't, |
173 // with NO_AD_INJECTION (unless the database has been set by a test). | 112 // don't crash in a release build. |
174 if (!AdNetworkDatabase::Get()) | 113 if (!AdNetworkDatabase::Get()) { |
| 114 NOTREACHED(); |
175 return NO_AD_INJECTION; | 115 return NO_AD_INJECTION; |
176 | |
177 if (api_name_ == ad_injection_constants::kHtmlIframeSrcApiName || | |
178 api_name_ == ad_injection_constants::kHtmlEmbedSrcApiName || | |
179 api_name_ == ad_injection_constants::kHtmlAnchorHrefApiName) { | |
180 return CheckSrcModification(); | |
181 } else if (EndsWith(api_name_, | |
182 ad_injection_constants::kAppendChildApiSuffix, | |
183 true /* case senstive */)) { | |
184 return CheckAppendChild(); | |
185 } | 116 } |
186 | 117 |
187 return NO_AD_INJECTION; | 118 AdType ad_type = AD_TYPE_NONE; |
| 119 InjectionType injection_type = NO_AD_INJECTION; |
| 120 |
| 121 if (EndsWith(api_name_, |
| 122 ad_injection_constants::kAppendChildApiSuffix, |
| 123 true /* case senstive */)) { |
| 124 injection_type = CheckAppendChild(&ad_type); |
| 125 } else { |
| 126 // Check if the action modified an element's src/href. |
| 127 if (api_name_ == ad_injection_constants::kHtmlIframeSrcApiName) |
| 128 ad_type = AD_TYPE_IFRAME; |
| 129 else if (api_name_ == ad_injection_constants::kHtmlEmbedSrcApiName) |
| 130 ad_type = AD_TYPE_EMBED; |
| 131 else if (api_name_ == ad_injection_constants::kHtmlAnchorHrefApiName) |
| 132 ad_type = AD_TYPE_ANCHOR; |
| 133 |
| 134 if (ad_type != AD_TYPE_NONE) |
| 135 injection_type = CheckSrcModification(); |
| 136 } |
| 137 |
| 138 if (injection_type != NO_AD_INJECTION) { |
| 139 UMA_HISTOGRAM_ENUMERATION( |
| 140 "Extensions.AdInjection.Type", ad_type, Action::NUM_AD_TYPES); |
| 141 } |
| 142 |
| 143 return injection_type; |
188 } | 144 } |
189 | 145 |
190 void Action::set_args(scoped_ptr<base::ListValue> args) { | 146 void Action::set_args(scoped_ptr<base::ListValue> args) { |
191 args_.reset(args.release()); | 147 args_.reset(args.release()); |
192 } | 148 } |
193 | 149 |
194 base::ListValue* Action::mutable_args() { | 150 base::ListValue* Action::mutable_args() { |
195 if (!args_.get()) { | 151 if (!args_.get()) { |
196 args_.reset(new base::ListValue()); | 152 args_.reset(new base::ListValue()); |
197 } | 153 } |
(...skipping 198 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
396 result += " ARG_URL=" + arg_url_.spec(); | 352 result += " ARG_URL=" + arg_url_.spec(); |
397 } | 353 } |
398 if (other_.get()) { | 354 if (other_.get()) { |
399 result += " OTHER=" + Serialize(other_.get()); | 355 result += " OTHER=" + Serialize(other_.get()); |
400 } | 356 } |
401 | 357 |
402 result += base::StringPrintf(" COUNT=%d", count_); | 358 result += base::StringPrintf(" COUNT=%d", count_); |
403 return result; | 359 return result; |
404 } | 360 } |
405 | 361 |
| 362 bool Action::UrlCouldBeAd(const GURL& url) const { |
| 363 // Ads can only be valid urls that don't match the page's host (linking to the |
| 364 // current page should be considered valid use), and aren't local to the |
| 365 // extension. |
| 366 return url.is_valid() && |
| 367 !url.is_empty() && |
| 368 url.host() != page_url_.host() && |
| 369 !url.SchemeIs(kExtensionScheme); |
| 370 } |
| 371 |
406 void Action::MaybeUploadUrl(rappor::RapporService* rappor_service) const { | 372 void Action::MaybeUploadUrl(rappor::RapporService* rappor_service) const { |
407 // If there's no given |rappor_service|, abort immediately. | 373 // Don't bother recording if the url is innocuous (or no |rappor_service|). |
408 if (!rappor_service) | 374 if (!rappor_service || !UrlCouldBeAd(arg_url_)) |
409 return; | |
410 | |
411 // If the action has no url, or the url is empty, then return. | |
412 if (!arg_url_.is_valid() || arg_url_.is_empty()) | |
413 return; | |
414 std::string host = arg_url_.host(); | |
415 if (host.empty()) | |
416 return; | 375 return; |
417 | 376 |
418 bool can_inject_ads = false; | 377 bool can_inject_ads = false; |
419 for (size_t i = 0; i < arraysize(kApisForRapporMetric); ++i) { | 378 for (size_t i = 0; i < arraysize(kApisForRapporMetric); ++i) { |
420 if (api_name_ == kApisForRapporMetric[i]) { | 379 if (api_name_ == kApisForRapporMetric[i]) { |
421 can_inject_ads = true; | 380 can_inject_ads = true; |
422 break; | 381 break; |
423 } | 382 } |
424 } | 383 } |
425 | 384 |
426 if (!can_inject_ads) | 385 if (!can_inject_ads) |
427 return; | 386 return; |
428 | 387 |
429 // Record the URL - an ad *may* have been injected. | 388 // Record the URL - an ad *may* have been injected. |
430 rappor_service->RecordSample(kExtensionAdInjectionRapporMetricName, | 389 rappor_service->RecordSample(kExtensionAdInjectionRapporMetricName, |
431 rappor::ETLD_PLUS_ONE_RAPPOR_TYPE, | 390 rappor::ETLD_PLUS_ONE_RAPPOR_TYPE, |
432 host); | 391 arg_url_.host()); |
433 } | 392 } |
434 | 393 |
435 Action::InjectionType Action::CheckSrcModification() const { | 394 Action::InjectionType Action::CheckSrcModification() const { |
436 const AdNetworkDatabase* database = AdNetworkDatabase::Get(); | 395 const AdNetworkDatabase* database = AdNetworkDatabase::Get(); |
437 | 396 |
438 bool arg_url_valid = arg_url_.is_valid() && !arg_url_.is_empty(); | 397 bool arg_url_could_be_ad = UrlCouldBeAd(arg_url_); |
439 | 398 |
440 GURL prev_url; | 399 GURL prev_url; |
441 std::string prev_url_string; | 400 std::string prev_url_string; |
442 if (args_.get() && args_->GetString(1u, &prev_url_string)) | 401 if (args_.get() && args_->GetString(1u, &prev_url_string)) |
443 prev_url = GURL(prev_url_string); | 402 prev_url = GURL(prev_url_string); |
444 | 403 |
445 bool prev_url_valid = prev_url.is_valid() && !prev_url.is_empty(); | 404 bool prev_url_valid = prev_url.is_valid() && !prev_url.is_empty(); |
446 | 405 |
447 bool injected_ad = arg_url_valid && database->IsAdNetwork(arg_url_); | 406 bool injected_ad = arg_url_could_be_ad && database->IsAdNetwork(arg_url_); |
448 bool replaced_ad = prev_url_valid && database->IsAdNetwork(prev_url); | 407 bool replaced_ad = prev_url_valid && database->IsAdNetwork(prev_url); |
449 | 408 |
450 if (injected_ad && replaced_ad) | 409 if (injected_ad && replaced_ad) |
451 return INJECTION_REPLACED_AD; | 410 return INJECTION_REPLACED_AD; |
452 if (injected_ad) | 411 if (injected_ad) |
453 return INJECTION_NEW_AD; | 412 return INJECTION_NEW_AD; |
454 if (replaced_ad) | 413 if (replaced_ad) |
455 return INJECTION_REMOVED_AD; | 414 return INJECTION_REMOVED_AD; |
456 | 415 |
457 // If the extension modified the URL with an external, valid URL then there's | 416 // If the extension modified the URL with an external, valid URL then there's |
458 // a good chance it's ad injection. Log it as a likely one, which also helps | 417 // a good chance it's ad injection. Log it as a likely one, which also helps |
459 // us determine the effectiveness of our IsAdNetwork() recognition. | 418 // us determine the effectiveness of our IsAdNetwork() recognition. |
460 if (arg_url_valid && !arg_url_.SchemeIs(kExtensionScheme)) { | 419 if (arg_url_could_be_ad) { |
461 if (prev_url_valid) | 420 if (prev_url_valid) |
462 return INJECTION_LIKELY_REPLACED_AD; | 421 return INJECTION_LIKELY_REPLACED_AD; |
463 return INJECTION_LIKELY_NEW_AD; | 422 return INJECTION_LIKELY_NEW_AD; |
464 } | 423 } |
465 | 424 |
466 return NO_AD_INJECTION; | 425 return NO_AD_INJECTION; |
467 } | 426 } |
468 | 427 |
469 Action::InjectionType Action::CheckAppendChild() const { | 428 Action::InjectionType Action::CheckAppendChild(AdType* ad_type_out) const { |
470 const base::DictionaryValue* child = NULL; | 429 const base::DictionaryValue* child = NULL; |
471 if (!args_->GetDictionary(0u, &child)) | 430 if (!args_->GetDictionary(0u, &child)) |
472 return NO_AD_INJECTION; | 431 return NO_AD_INJECTION; |
473 | 432 |
474 return CheckDomObject(child); | 433 return CheckDomObject(child, ad_type_out); |
| 434 } |
| 435 |
| 436 Action::InjectionType Action::CheckDomObject( |
| 437 const base::DictionaryValue* object, |
| 438 AdType* ad_type_out) const { |
| 439 DCHECK(ad_type_out); |
| 440 std::string type; |
| 441 object->GetString(keys::kType, &type); |
| 442 |
| 443 AdType ad_type = AD_TYPE_NONE; |
| 444 std::string url_key; |
| 445 if (type == kIframeElementType) { |
| 446 ad_type = AD_TYPE_IFRAME; |
| 447 url_key = keys::kSrc; |
| 448 } else if (type == kEmbedElementType) { |
| 449 ad_type = AD_TYPE_EMBED; |
| 450 url_key = keys::kSrc; |
| 451 } else if (type == kAnchorElementType) { |
| 452 ad_type = AD_TYPE_ANCHOR; |
| 453 url_key = keys::kHref; |
| 454 } |
| 455 |
| 456 if (!url_key.empty()) { |
| 457 std::string url; |
| 458 if (object->GetString(url_key, &url)) { |
| 459 GURL gurl(url); |
| 460 if (UrlCouldBeAd(gurl)) { |
| 461 *ad_type_out = ad_type; |
| 462 if (AdNetworkDatabase::Get()->IsAdNetwork(gurl)) |
| 463 return INJECTION_NEW_AD; |
| 464 // If the extension injected an URL which is not local to itself or the |
| 465 // page, there is a good chance it could be a new ad, and our database |
| 466 // missed it. |
| 467 return INJECTION_LIKELY_NEW_AD; |
| 468 } |
| 469 } |
| 470 } |
| 471 |
| 472 const base::ListValue* children = NULL; |
| 473 if (object->GetList(keys::kChildren, &children)) { |
| 474 const base::DictionaryValue* child = NULL; |
| 475 for (size_t i = 0; |
| 476 i < children->GetSize() && |
| 477 i < ad_injection_constants::kMaximumChildrenToCheck; |
| 478 ++i) { |
| 479 if (children->GetDictionary(i, &child)) { |
| 480 InjectionType type = CheckDomObject(child, ad_type_out); |
| 481 if (type != NO_AD_INJECTION) |
| 482 return type; |
| 483 } |
| 484 } |
| 485 } |
| 486 |
| 487 return NO_AD_INJECTION; |
475 } | 488 } |
476 | 489 |
477 bool ActionComparator::operator()( | 490 bool ActionComparator::operator()( |
478 const scoped_refptr<Action>& lhs, | 491 const scoped_refptr<Action>& lhs, |
479 const scoped_refptr<Action>& rhs) const { | 492 const scoped_refptr<Action>& rhs) const { |
480 if (lhs->time() != rhs->time()) | 493 if (lhs->time() != rhs->time()) |
481 return lhs->time() < rhs->time(); | 494 return lhs->time() < rhs->time(); |
482 else if (lhs->action_id() != rhs->action_id()) | 495 else if (lhs->action_id() != rhs->action_id()) |
483 return lhs->action_id() < rhs->action_id(); | 496 return lhs->action_id() < rhs->action_id(); |
484 else | 497 else |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
532 std::string rhs_other = ActivityLogPolicy::Util::Serialize(rhs->other()); | 545 std::string rhs_other = ActivityLogPolicy::Util::Serialize(rhs->other()); |
533 if (lhs_other != rhs_other) | 546 if (lhs_other != rhs_other) |
534 return lhs_other < rhs_other; | 547 return lhs_other < rhs_other; |
535 } | 548 } |
536 | 549 |
537 // All fields compare as equal if this point is reached. | 550 // All fields compare as equal if this point is reached. |
538 return false; | 551 return false; |
539 } | 552 } |
540 | 553 |
541 } // namespace extensions | 554 } // namespace extensions |
OLD | NEW |