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
3 // found in the LICENSE file.
5 #include "chrome/browser/extensions/activity_log/activity_actions.h"
7 #include <algorithm> // for std::find.
10 #include "base/command_line.h"
11 #include "base/format_macros.h"
12 #include "base/json/json_string_value_serializer.h"
13 #include "base/logging.h"
14 #include "base/macros.h"
15 #include "base/memory/singleton.h"
16 #include "base/metrics/histogram.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/values.h"
21 #include "chrome/browser/extensions/activity_log/activity_action_constants.h"
22 #include "chrome/browser/extensions/activity_log/ad_network_database.h"
23 #include "chrome/browser/extensions/activity_log/fullstream_ui_policy.h"
24 #include "chrome/browser/ui/browser.h"
25 #include "chrome/common/chrome_switches.h"
26 #include "components/rappor/rappor_service.h"
27 #include "content/public/browser/web_contents.h"
28 #include "extensions/common/constants.h"
29 #include "extensions/common/dom_action_types.h"
32 namespace constants
= activity_log_constants
;
33 namespace activity_log
= extensions::api::activity_log_private
;
35 namespace extensions
{
39 // The "Extensions.PossibleAdInjection2" metric uses different Rappor
40 // parameters than the original metric.
41 const char kExtensionAdInjectionRapporMetricName
[] =
42 "Extensions.PossibleAdInjection2";
44 const char kBlinkSetAttributeEvent
[] = "blinkSetAttribute";
45 const char kBlinkAddElementEvent
[] = "blinkAddElement";
47 const char kIframe
[] = "iframe";
48 const char kAnchor
[] = "a";
49 const char kScript
[] = "script";
51 const char kSrc
[] = "src";
52 const char kHref
[] = "href";
54 std::string
Serialize(const base::Value
* value
) {
55 std::string value_as_text
;
57 value_as_text
= "null";
59 JSONStringValueSerializer
serializer(&value_as_text
);
60 serializer
.SerializeAndOmitBinaryValues(*value
);
67 using api::activity_log_private::ExtensionActivity
;
69 Action::Action(const std::string
& extension_id
,
70 const base::Time
& time
,
71 const ActionType action_type
,
72 const std::string
& api_name
,
74 : extension_id_(extension_id
),
76 action_type_(action_type
),
78 page_incognito_(false),
79 arg_incognito_(false),
81 action_id_(action_id
) {}
85 // TODO(mvrable): As an optimization, we might return this directly if the
86 // refcount is one. However, there are likely to be other stray references in
87 // many cases that will prevent this optimization.
88 scoped_refptr
<Action
> Action::Clone() const {
89 scoped_refptr
<Action
> clone(
91 extension_id(), time(), action_type(), api_name(), action_id()));
93 clone
->set_args(make_scoped_ptr(args()->DeepCopy()));
94 clone
->set_page_url(page_url());
95 clone
->set_page_title(page_title());
96 clone
->set_page_incognito(page_incognito());
97 clone
->set_arg_url(arg_url());
98 clone
->set_arg_incognito(arg_incognito());
100 clone
->set_other(make_scoped_ptr(other()->DeepCopy()));
104 Action::InjectionType
Action::DidInjectAd(
105 rappor::RapporService
* rappor_service
) const {
106 MaybeUploadUrl(rappor_service
);
108 // We should always have an AdNetworkDatabase, but, on the offchance we don't,
109 // don't crash in a release build.
110 if (!AdNetworkDatabase::Get()) {
112 return NO_AD_INJECTION
;
115 AdType ad_type
= AD_TYPE_NONE
;
116 InjectionType injection_type
= NO_AD_INJECTION
;
118 if (api_name_
== kBlinkSetAttributeEvent
) {
119 std::string element_name
;
120 std::string attr_name
;
122 args_
->GetString(0u, &element_name
);
123 args_
->GetString(1u, &attr_name
);
125 if (attr_name
== kSrc
) {
126 if (element_name
== kIframe
)
127 ad_type
= AD_TYPE_IFRAME
;
128 else if (element_name
== kScript
)
129 ad_type
= AD_TYPE_SCRIPT
;
130 } else if (element_name
== kAnchor
&& attr_name
== kHref
) {
131 ad_type
= AD_TYPE_ANCHOR
;
134 if (ad_type
!= AD_TYPE_NONE
)
135 injection_type
= CheckAttrModification();
136 } else if (api_name_
== kBlinkAddElementEvent
) {
137 std::string element_name
;
139 args_
->GetString(0u, &element_name
);
140 if (element_name
== kIframe
)
141 ad_type
= AD_TYPE_IFRAME
;
142 else if (element_name
== kAnchor
)
143 ad_type
= AD_TYPE_ANCHOR
;
144 else if (element_name
== kScript
)
145 ad_type
= AD_TYPE_SCRIPT
;
147 if (ad_type
!= AD_TYPE_NONE
)
148 injection_type
= CheckElementAddition();
151 if (injection_type
!= NO_AD_INJECTION
) {
152 UMA_HISTOGRAM_ENUMERATION(
153 "Extensions.AdInjection.AdType", ad_type
, Action::NUM_AD_TYPES
);
156 return injection_type
;
159 void Action::set_args(scoped_ptr
<base::ListValue
> args
) {
160 args_
.reset(args
.release());
163 base::ListValue
* Action::mutable_args() {
165 args_
.reset(new base::ListValue());
170 void Action::set_page_url(const GURL
& page_url
) {
171 page_url_
= page_url
;
174 void Action::set_arg_url(const GURL
& arg_url
) {
178 void Action::set_other(scoped_ptr
<base::DictionaryValue
> other
) {
179 other_
.reset(other
.release());
182 base::DictionaryValue
* Action::mutable_other() {
184 other_
.reset(new base::DictionaryValue());
189 std::string
Action::SerializePageUrl() const {
190 return (page_incognito() ? constants::kIncognitoUrl
: "") + page_url().spec();
193 void Action::ParsePageUrl(const std::string
& url
) {
194 set_page_incognito(base::StartsWith(url
, constants::kIncognitoUrl
,
195 base::CompareCase::SENSITIVE
));
196 if (page_incognito())
197 set_page_url(GURL(url
.substr(strlen(constants::kIncognitoUrl
))));
199 set_page_url(GURL(url
));
202 std::string
Action::SerializeArgUrl() const {
203 return (arg_incognito() ? constants::kIncognitoUrl
: "") + arg_url().spec();
206 void Action::ParseArgUrl(const std::string
& url
) {
207 set_arg_incognito(base::StartsWith(url
, constants::kIncognitoUrl
,
208 base::CompareCase::SENSITIVE
));
210 set_arg_url(GURL(url
.substr(strlen(constants::kIncognitoUrl
))));
212 set_arg_url(GURL(url
));
215 scoped_ptr
<ExtensionActivity
> Action::ConvertToExtensionActivity() {
216 scoped_ptr
<ExtensionActivity
> result(new ExtensionActivity
);
218 // We do this translation instead of using the same enum because the database
219 // values need to be stable; this allows us to change the extension API
220 // without affecting the database.
221 switch (action_type()) {
222 case ACTION_API_CALL
:
223 result
->activity_type
= activity_log::EXTENSION_ACTIVITY_TYPE_API_CALL
;
225 case ACTION_API_EVENT
:
226 result
->activity_type
= activity_log::EXTENSION_ACTIVITY_TYPE_API_EVENT
;
228 case ACTION_CONTENT_SCRIPT
:
229 result
->activity_type
=
230 activity_log::EXTENSION_ACTIVITY_TYPE_CONTENT_SCRIPT
;
232 case ACTION_DOM_ACCESS
:
233 result
->activity_type
= activity_log::EXTENSION_ACTIVITY_TYPE_DOM_ACCESS
;
235 case ACTION_DOM_EVENT
:
236 result
->activity_type
= activity_log::EXTENSION_ACTIVITY_TYPE_DOM_EVENT
;
238 case ACTION_WEB_REQUEST
:
239 result
->activity_type
= activity_log::EXTENSION_ACTIVITY_TYPE_WEB_REQUEST
;
241 case UNUSED_ACTION_API_BLOCKED
:
244 // This shouldn't be reached, but some people might have old or otherwise
245 // weird db entries. Treat it like an API call if that happens.
246 result
->activity_type
= activity_log::EXTENSION_ACTIVITY_TYPE_API_CALL
;
250 result
->extension_id
.reset(new std::string(extension_id()));
251 result
->time
.reset(new double(time().ToJsTime()));
252 result
->count
.reset(new double(count()));
253 result
->api_call
.reset(new std::string(api_name()));
254 result
->args
.reset(new std::string(Serialize(args())));
255 if (action_id() != -1)
256 result
->activity_id
.reset(
257 new std::string(base::StringPrintf("%" PRId64
, action_id())));
258 if (page_url().is_valid()) {
259 if (!page_title().empty())
260 result
->page_title
.reset(new std::string(page_title()));
261 result
->page_url
.reset(new std::string(SerializePageUrl()));
263 if (arg_url().is_valid())
264 result
->arg_url
.reset(new std::string(SerializeArgUrl()));
267 scoped_ptr
<ExtensionActivity::Other
> other_field(
268 new ExtensionActivity::Other
);
270 if (other()->GetBooleanWithoutPathExpansion(constants::kActionPrerender
,
272 other_field
->prerender
.reset(new bool(prerender
));
274 const base::DictionaryValue
* web_request
;
275 if (other()->GetDictionaryWithoutPathExpansion(constants::kActionWebRequest
,
277 other_field
->web_request
.reset(new std::string(
278 ActivityLogPolicy::Util::Serialize(web_request
)));
281 if (other()->GetStringWithoutPathExpansion(constants::kActionExtra
, &extra
))
282 other_field
->extra
.reset(new std::string(extra
));
284 if (other()->GetIntegerWithoutPathExpansion(constants::kActionDomVerb
,
286 switch (static_cast<DomActionType::Type
>(dom_verb
)) {
287 case DomActionType::GETTER
:
288 other_field
->dom_verb
=
289 activity_log::EXTENSION_ACTIVITY_DOM_VERB_GETTER
;
291 case DomActionType::SETTER
:
292 other_field
->dom_verb
=
293 activity_log::EXTENSION_ACTIVITY_DOM_VERB_SETTER
;
295 case DomActionType::METHOD
:
296 other_field
->dom_verb
=
297 activity_log::EXTENSION_ACTIVITY_DOM_VERB_METHOD
;
299 case DomActionType::INSERTED
:
300 other_field
->dom_verb
=
301 activity_log::EXTENSION_ACTIVITY_DOM_VERB_INSERTED
;
303 case DomActionType::XHR
:
304 other_field
->dom_verb
= activity_log::EXTENSION_ACTIVITY_DOM_VERB_XHR
;
306 case DomActionType::WEBREQUEST
:
307 other_field
->dom_verb
=
308 activity_log::EXTENSION_ACTIVITY_DOM_VERB_WEBREQUEST
;
310 case DomActionType::MODIFIED
:
311 other_field
->dom_verb
=
312 activity_log::EXTENSION_ACTIVITY_DOM_VERB_MODIFIED
;
315 other_field
->dom_verb
=
316 activity_log::EXTENSION_ACTIVITY_DOM_VERB_NONE
;
319 other_field
->dom_verb
= activity_log::EXTENSION_ACTIVITY_DOM_VERB_NONE
;
321 result
->other
.reset(other_field
.release());
324 return result
.Pass();
327 std::string
Action::PrintForDebug() const {
328 std::string result
= base::StringPrintf("ACTION ID=%" PRId64
, action_id());
329 result
+= " EXTENSION ID=" + extension_id() + " CATEGORY=";
330 switch (action_type_
) {
331 case ACTION_API_CALL
:
332 result
+= "api_call";
334 case ACTION_API_EVENT
:
335 result
+= "api_event_callback";
337 case ACTION_WEB_REQUEST
:
338 result
+= "webrequest";
340 case ACTION_CONTENT_SCRIPT
:
341 result
+= "content_script";
343 case UNUSED_ACTION_API_BLOCKED
:
344 // This is deprecated.
345 result
+= "api_blocked";
347 case ACTION_DOM_EVENT
:
348 result
+= "dom_event";
350 case ACTION_DOM_ACCESS
:
351 result
+= "dom_access";
354 result
+= base::StringPrintf("type%d", static_cast<int>(action_type_
));
357 result
+= " API=" + api_name_
;
359 result
+= " ARGS=" + Serialize(args_
.get());
361 if (page_url_
.is_valid()) {
363 result
+= " PAGE_URL=(incognito)" + page_url_
.spec();
365 result
+= " PAGE_URL=" + page_url_
.spec();
367 if (!page_title_
.empty()) {
368 base::StringValue
title(page_title_
);
369 result
+= " PAGE_TITLE=" + Serialize(&title
);
371 if (arg_url_
.is_valid()) {
373 result
+= " ARG_URL=(incognito)" + arg_url_
.spec();
375 result
+= " ARG_URL=" + arg_url_
.spec();
378 result
+= " OTHER=" + Serialize(other_
.get());
381 result
+= base::StringPrintf(" COUNT=%d", count_
);
385 bool Action::UrlCouldBeAd(const GURL
& url
) const {
386 // Ads can only be valid urls that don't match the page's host (linking to the
387 // current page should be considered valid use), and aren't local to the
389 return url
.is_valid() &&
391 url
.host() != page_url_
.host() &&
392 !url
.SchemeIs(kExtensionScheme
);
395 void Action::MaybeUploadUrl(rappor::RapporService
* rappor_service
) const {
396 // Don't bother recording if the url is innocuous (or no |rappor_service|).
402 if (api_name_
== kBlinkSetAttributeEvent
) {
403 std::string element_name
;
404 std::string attr_name
;
405 std::string url_string
;
407 args_
->GetString(0u, &element_name
);
408 args_
->GetString(1u, &attr_name
);
410 if (element_name
== kIframe
&& attr_name
== kSrc
) {
411 args_
->GetString(3u, &url_string
);
412 url
= GURL(url_string
);
413 } else if (element_name
== kAnchor
&& attr_name
== kHref
) {
414 args_
->GetString(3u, &url_string
);
415 url
= GURL(url_string
);
417 } else if (api_name_
== kBlinkAddElementEvent
) {
418 std::string element_name
;
419 std::string url_string
;
421 args_
->GetString(0u, &element_name
);
422 if (element_name
== kIframe
) {
423 args_
->GetString(1u, &url_string
);
424 url
= GURL(url_string
);
425 } else if (element_name
== kAnchor
) {
426 args_
->GetString(1u, &url_string
);
427 url
= GURL(url_string
);
431 if (!UrlCouldBeAd(url
))
434 // Record the URL - an ad *may* have been injected.
435 rappor_service
->RecordSample(kExtensionAdInjectionRapporMetricName
,
436 rappor::ETLD_PLUS_ONE_RAPPOR_TYPE
,
440 Action::InjectionType
Action::CheckAttrModification() const {
441 if (api_name_
!= kBlinkSetAttributeEvent
)
442 return NO_AD_INJECTION
;
444 const AdNetworkDatabase
* database
= AdNetworkDatabase::Get();
447 std::string prev_url_string
;
448 if (args_
.get() && args_
->GetString(2u, &prev_url_string
))
449 prev_url
= GURL(prev_url_string
);
452 std::string new_url_string
;
453 if (args_
.get() && args_
->GetString(3u, &new_url_string
))
454 new_url
= GURL(new_url_string
);
456 bool new_url_could_be_ad
= UrlCouldBeAd(new_url
);
457 bool prev_url_valid
= prev_url
.is_valid() && !prev_url
.is_empty();
459 bool injected_ad
= new_url_could_be_ad
&& database
->IsAdNetwork(new_url
);
460 bool replaced_ad
= prev_url_valid
&& database
->IsAdNetwork(prev_url
);
462 if (injected_ad
&& replaced_ad
)
463 return INJECTION_REPLACED_AD
;
465 return INJECTION_NEW_AD
;
467 return INJECTION_REMOVED_AD
;
469 // If the extension modified the URL with an external, valid URL then there's
470 // a good chance it's ad injection. Log it as a likely one, which also helps
471 // us determine the effectiveness of our IsAdNetwork() recognition.
472 if (new_url_could_be_ad
) {
474 return INJECTION_LIKELY_REPLACED_AD
;
475 return INJECTION_LIKELY_NEW_AD
;
478 return NO_AD_INJECTION
;
481 Action::InjectionType
Action::CheckElementAddition() const {
482 DCHECK_EQ(kBlinkAddElementEvent
, api_name_
);
485 std::string url_string
;
486 if (args_
.get() && args_
->GetString(1u, &url_string
))
487 url
= GURL(url_string
);
489 if (UrlCouldBeAd(url
)) {
490 if (AdNetworkDatabase::Get()->IsAdNetwork(url
))
491 return INJECTION_NEW_AD
;
492 // If the extension injected an URL which is not local to itself or the
493 // page, there is a good chance it could be a new ad, and our database
495 return INJECTION_LIKELY_NEW_AD
;
497 return NO_AD_INJECTION
;
500 bool ActionComparator::operator()(
501 const scoped_refptr
<Action
>& lhs
,
502 const scoped_refptr
<Action
>& rhs
) const {
503 if (lhs
->time() != rhs
->time())
504 return lhs
->time() < rhs
->time();
505 else if (lhs
->action_id() != rhs
->action_id())
506 return lhs
->action_id() < rhs
->action_id();
508 return ActionComparatorExcludingTimeAndActionId()(lhs
, rhs
);
511 bool ActionComparatorExcludingTimeAndActionId::operator()(
512 const scoped_refptr
<Action
>& lhs
,
513 const scoped_refptr
<Action
>& rhs
) const {
514 if (lhs
->extension_id() != rhs
->extension_id())
515 return lhs
->extension_id() < rhs
->extension_id();
516 if (lhs
->action_type() != rhs
->action_type())
517 return lhs
->action_type() < rhs
->action_type();
518 if (lhs
->api_name() != rhs
->api_name())
519 return lhs
->api_name() < rhs
->api_name();
521 // args might be null; treat a null value as less than all non-null values,
522 // including the empty string.
523 if (!lhs
->args() && rhs
->args())
525 if (lhs
->args() && !rhs
->args())
527 if (lhs
->args() && rhs
->args()) {
528 std::string lhs_args
= ActivityLogPolicy::Util::Serialize(lhs
->args());
529 std::string rhs_args
= ActivityLogPolicy::Util::Serialize(rhs
->args());
530 if (lhs_args
!= rhs_args
)
531 return lhs_args
< rhs_args
;
534 // Compare URLs as strings, and treat the incognito flag as a separate field.
535 if (lhs
->page_url().spec() != rhs
->page_url().spec())
536 return lhs
->page_url().spec() < rhs
->page_url().spec();
537 if (lhs
->page_incognito() != rhs
->page_incognito())
538 return lhs
->page_incognito() < rhs
->page_incognito();
540 if (lhs
->page_title() != rhs
->page_title())
541 return lhs
->page_title() < rhs
->page_title();
543 if (lhs
->arg_url().spec() != rhs
->arg_url().spec())
544 return lhs
->arg_url().spec() < rhs
->arg_url().spec();
545 if (lhs
->arg_incognito() != rhs
->arg_incognito())
546 return lhs
->arg_incognito() < rhs
->arg_incognito();
548 // other is treated much like the args field.
549 if (!lhs
->other() && rhs
->other())
551 if (lhs
->other() && !rhs
->other())
553 if (lhs
->other() && rhs
->other()) {
554 std::string lhs_other
= ActivityLogPolicy::Util::Serialize(lhs
->other());
555 std::string rhs_other
= ActivityLogPolicy::Util::Serialize(rhs
->other());
556 if (lhs_other
!= rhs_other
)
557 return lhs_other
< rhs_other
;
560 // All fields compare as equal if this point is reached.
564 } // namespace extensions