Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / activity_log / activity_actions.cc
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.
4
5 #include "chrome/browser/extensions/activity_log/activity_actions.h"
6
7 #include <algorithm>  // for std::find.
8 #include <string>
9
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"
30 #include "sql/statement.h"
31 #include "url/gurl.h"
32
33 namespace constants = activity_log_constants;
34
35 namespace extensions {
36
37 namespace {
38
39 // The "Extensions.PossibleAdInjection2" metric uses different Rappor
40 // parameters than the original metric.
41 const char kExtensionAdInjectionRapporMetricName[] =
42     "Extensions.PossibleAdInjection2";
43
44 const char kBlinkSetAttributeEvent[] = "blinkSetAttribute";
45 const char kBlinkAddElementEvent[] = "blinkAddElement";
46
47 const char kIframe[] = "iframe";
48 const char kAnchor[] = "a";
49 const char kScript[] = "script";
50
51 const char kSrc[] = "src";
52 const char kHref[] = "href";
53
54 std::string Serialize(const base::Value* value) {
55   std::string value_as_text;
56   if (!value) {
57     value_as_text = "null";
58   } else {
59     JSONStringValueSerializer serializer(&value_as_text);
60     serializer.SerializeAndOmitBinaryValues(*value);
61   }
62   return value_as_text;
63 }
64
65 }  // namespace
66
67 using api::activity_log_private::ExtensionActivity;
68
69 Action::Action(const std::string& extension_id,
70                const base::Time& time,
71                const ActionType action_type,
72                const std::string& api_name,
73                int64 action_id)
74     : extension_id_(extension_id),
75       time_(time),
76       action_type_(action_type),
77       api_name_(api_name),
78       page_incognito_(false),
79       arg_incognito_(false),
80       count_(0),
81       action_id_(action_id) {}
82
83 Action::~Action() {}
84
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(
90       new Action(
91           extension_id(), time(), action_type(), api_name(), action_id()));
92   if (args())
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());
99   if (other())
100     clone->set_other(make_scoped_ptr(other()->DeepCopy()));
101   return clone;
102 }
103
104 Action::InjectionType Action::DidInjectAd(
105     rappor::RapporService* rappor_service) const {
106   MaybeUploadUrl(rappor_service);
107
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()) {
111     NOTREACHED();
112     return NO_AD_INJECTION;
113   }
114
115   AdType ad_type = AD_TYPE_NONE;
116   InjectionType injection_type = NO_AD_INJECTION;
117
118   if (api_name_ == kBlinkSetAttributeEvent) {
119     std::string element_name;
120     std::string attr_name;
121     if (args_.get()) {
122       args_->GetString(0u, &element_name);
123       args_->GetString(1u, &attr_name);
124     }
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;
132     }
133
134     if (ad_type != AD_TYPE_NONE)
135       injection_type = CheckAttrModification();
136   } else if (api_name_ == kBlinkAddElementEvent) {
137     std::string element_name;
138     if (args_.get())
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;
146
147     if (ad_type != AD_TYPE_NONE)
148       injection_type = CheckElementAddition();
149   }
150
151   if (injection_type != NO_AD_INJECTION) {
152     UMA_HISTOGRAM_ENUMERATION(
153         "Extensions.AdInjection.AdType", ad_type, Action::NUM_AD_TYPES);
154   }
155
156   return injection_type;
157 }
158
159 void Action::set_args(scoped_ptr<base::ListValue> args) {
160   args_.reset(args.release());
161 }
162
163 base::ListValue* Action::mutable_args() {
164   if (!args_.get()) {
165     args_.reset(new base::ListValue());
166   }
167   return args_.get();
168 }
169
170 void Action::set_page_url(const GURL& page_url) {
171   page_url_ = page_url;
172 }
173
174 void Action::set_arg_url(const GURL& arg_url) {
175   arg_url_ = arg_url;
176 }
177
178 void Action::set_other(scoped_ptr<base::DictionaryValue> other) {
179   other_.reset(other.release());
180 }
181
182 base::DictionaryValue* Action::mutable_other() {
183   if (!other_.get()) {
184     other_.reset(new base::DictionaryValue());
185   }
186   return other_.get();
187 }
188
189 std::string Action::SerializePageUrl() const {
190   return (page_incognito() ? constants::kIncognitoUrl : "") + page_url().spec();
191 }
192
193 void Action::ParsePageUrl(const std::string& url) {
194   set_page_incognito(StartsWithASCII(url, constants::kIncognitoUrl, true));
195   if (page_incognito())
196     set_page_url(GURL(url.substr(strlen(constants::kIncognitoUrl))));
197   else
198     set_page_url(GURL(url));
199 }
200
201 std::string Action::SerializeArgUrl() const {
202   return (arg_incognito() ? constants::kIncognitoUrl : "") + arg_url().spec();
203 }
204
205 void Action::ParseArgUrl(const std::string& url) {
206   set_arg_incognito(StartsWithASCII(url, constants::kIncognitoUrl, true));
207   if (arg_incognito())
208     set_arg_url(GURL(url.substr(strlen(constants::kIncognitoUrl))));
209   else
210     set_arg_url(GURL(url));
211 }
212
213 scoped_ptr<ExtensionActivity> Action::ConvertToExtensionActivity() {
214   scoped_ptr<ExtensionActivity> result(new ExtensionActivity);
215
216   // We do this translation instead of using the same enum because the database
217   // values need to be stable; this allows us to change the extension API
218   // without affecting the database.
219   switch (action_type()) {
220     case ACTION_API_CALL:
221       result->activity_type = ExtensionActivity::ACTIVITY_TYPE_API_CALL;
222       break;
223     case ACTION_API_EVENT:
224       result->activity_type = ExtensionActivity::ACTIVITY_TYPE_API_EVENT;
225       break;
226     case ACTION_CONTENT_SCRIPT:
227       result->activity_type = ExtensionActivity::ACTIVITY_TYPE_CONTENT_SCRIPT;
228       break;
229     case ACTION_DOM_ACCESS:
230       result->activity_type = ExtensionActivity::ACTIVITY_TYPE_DOM_ACCESS;
231       break;
232     case ACTION_DOM_EVENT:
233       result->activity_type = ExtensionActivity::ACTIVITY_TYPE_DOM_EVENT;
234       break;
235     case ACTION_WEB_REQUEST:
236       result->activity_type = ExtensionActivity::ACTIVITY_TYPE_WEB_REQUEST;
237       break;
238     case UNUSED_ACTION_API_BLOCKED:
239     case ACTION_ANY:
240     default:
241       // This shouldn't be reached, but some people might have old or otherwise
242       // weird db entries. Treat it like an API call if that happens.
243       result->activity_type = ExtensionActivity::ACTIVITY_TYPE_API_CALL;
244       break;
245   }
246
247   result->extension_id.reset(new std::string(extension_id()));
248   result->time.reset(new double(time().ToJsTime()));
249   result->count.reset(new double(count()));
250   result->api_call.reset(new std::string(api_name()));
251   result->args.reset(new std::string(Serialize(args())));
252   if (action_id() != -1)
253     result->activity_id.reset(
254         new std::string(base::StringPrintf("%" PRId64, action_id())));
255   if (page_url().is_valid()) {
256     if (!page_title().empty())
257       result->page_title.reset(new std::string(page_title()));
258     result->page_url.reset(new std::string(SerializePageUrl()));
259   }
260   if (arg_url().is_valid())
261     result->arg_url.reset(new std::string(SerializeArgUrl()));
262
263   if (other()) {
264     scoped_ptr<ExtensionActivity::Other> other_field(
265         new ExtensionActivity::Other);
266     bool prerender;
267     if (other()->GetBooleanWithoutPathExpansion(constants::kActionPrerender,
268                                                 &prerender)) {
269       other_field->prerender.reset(new bool(prerender));
270     }
271     const base::DictionaryValue* web_request;
272     if (other()->GetDictionaryWithoutPathExpansion(constants::kActionWebRequest,
273                                                    &web_request)) {
274       other_field->web_request.reset(new std::string(
275           ActivityLogPolicy::Util::Serialize(web_request)));
276     }
277     std::string extra;
278     if (other()->GetStringWithoutPathExpansion(constants::kActionExtra, &extra))
279       other_field->extra.reset(new std::string(extra));
280     int dom_verb;
281     if (other()->GetIntegerWithoutPathExpansion(constants::kActionDomVerb,
282                                                 &dom_verb)) {
283       switch (static_cast<DomActionType::Type>(dom_verb)) {
284         case DomActionType::GETTER:
285           other_field->dom_verb = ExtensionActivity::Other::DOM_VERB_GETTER;
286           break;
287         case DomActionType::SETTER:
288           other_field->dom_verb = ExtensionActivity::Other::DOM_VERB_SETTER;
289           break;
290         case DomActionType::METHOD:
291           other_field->dom_verb = ExtensionActivity::Other::DOM_VERB_METHOD;
292           break;
293         case DomActionType::INSERTED:
294           other_field->dom_verb = ExtensionActivity::Other::DOM_VERB_INSERTED;
295           break;
296         case DomActionType::XHR:
297           other_field->dom_verb = ExtensionActivity::Other::DOM_VERB_XHR;
298           break;
299         case DomActionType::WEBREQUEST:
300           other_field->dom_verb = ExtensionActivity::Other::DOM_VERB_WEBREQUEST;
301           break;
302         case DomActionType::MODIFIED:
303           other_field->dom_verb = ExtensionActivity::Other::DOM_VERB_MODIFIED;
304           break;
305         default:
306           other_field->dom_verb = ExtensionActivity::Other::DOM_VERB_NONE;
307       }
308     } else {
309       other_field->dom_verb = ExtensionActivity::Other::DOM_VERB_NONE;
310     }
311     result->other.reset(other_field.release());
312   }
313
314   return result.Pass();
315 }
316
317 std::string Action::PrintForDebug() const {
318   std::string result = base::StringPrintf("ACTION ID=%" PRId64, action_id());
319   result += " EXTENSION ID=" + extension_id() + " CATEGORY=";
320   switch (action_type_) {
321     case ACTION_API_CALL:
322       result += "api_call";
323       break;
324     case ACTION_API_EVENT:
325       result += "api_event_callback";
326       break;
327     case ACTION_WEB_REQUEST:
328       result += "webrequest";
329       break;
330     case ACTION_CONTENT_SCRIPT:
331       result += "content_script";
332       break;
333     case UNUSED_ACTION_API_BLOCKED:
334       // This is deprecated.
335       result += "api_blocked";
336       break;
337     case ACTION_DOM_EVENT:
338       result += "dom_event";
339       break;
340     case ACTION_DOM_ACCESS:
341       result += "dom_access";
342       break;
343     default:
344       result += base::StringPrintf("type%d", static_cast<int>(action_type_));
345   }
346
347   result += " API=" + api_name_;
348   if (args_.get()) {
349     result += " ARGS=" + Serialize(args_.get());
350   }
351   if (page_url_.is_valid()) {
352     if (page_incognito_)
353       result += " PAGE_URL=(incognito)" + page_url_.spec();
354     else
355       result += " PAGE_URL=" + page_url_.spec();
356   }
357   if (!page_title_.empty()) {
358     base::StringValue title(page_title_);
359     result += " PAGE_TITLE=" + Serialize(&title);
360   }
361   if (arg_url_.is_valid()) {
362     if (arg_incognito_)
363       result += " ARG_URL=(incognito)" + arg_url_.spec();
364     else
365       result += " ARG_URL=" + arg_url_.spec();
366   }
367   if (other_.get()) {
368     result += " OTHER=" + Serialize(other_.get());
369   }
370
371   result += base::StringPrintf(" COUNT=%d", count_);
372   return result;
373 }
374
375 bool Action::UrlCouldBeAd(const GURL& url) const {
376   // Ads can only be valid urls that don't match the page's host (linking to the
377   // current page should be considered valid use), and aren't local to the
378   // extension.
379   return url.is_valid() &&
380          !url.is_empty() &&
381          url.host() != page_url_.host() &&
382          !url.SchemeIs(kExtensionScheme);
383 }
384
385 void Action::MaybeUploadUrl(rappor::RapporService* rappor_service) const {
386   // Don't bother recording if the url is innocuous (or no |rappor_service|).
387   if (!rappor_service)
388     return;
389
390   GURL url;
391
392   if (api_name_ == kBlinkSetAttributeEvent) {
393     std::string element_name;
394     std::string attr_name;
395     std::string url_string;
396     if (args_.get()) {
397       args_->GetString(0u, &element_name);
398       args_->GetString(1u, &attr_name);
399     }
400     if (element_name == kIframe && attr_name == kSrc) {
401       args_->GetString(3u, &url_string);
402       url = GURL(url_string);
403     } else if (element_name == kAnchor && attr_name == kHref) {
404       args_->GetString(3u, &url_string);
405       url = GURL(url_string);
406     }
407   } else if (api_name_ == kBlinkAddElementEvent) {
408     std::string element_name;
409     std::string url_string;
410     if (args_.get())
411       args_->GetString(0u, &element_name);
412     if (element_name == kIframe) {
413       args_->GetString(1u, &url_string);
414       url = GURL(url_string);
415     } else if (element_name == kAnchor) {
416       args_->GetString(1u, &url_string);
417       url = GURL(url_string);
418     }
419   }
420
421   if (!UrlCouldBeAd(url))
422     return;
423
424   // Record the URL - an ad *may* have been injected.
425   rappor_service->RecordSample(kExtensionAdInjectionRapporMetricName,
426                                rappor::ETLD_PLUS_ONE_RAPPOR_TYPE,
427                                url.host());
428 }
429
430 Action::InjectionType Action::CheckAttrModification() const {
431   if (api_name_ != kBlinkSetAttributeEvent)
432     return NO_AD_INJECTION;
433
434   const AdNetworkDatabase* database = AdNetworkDatabase::Get();
435
436   GURL prev_url;
437   std::string prev_url_string;
438   if (args_.get() && args_->GetString(2u, &prev_url_string))
439     prev_url = GURL(prev_url_string);
440
441   GURL new_url;
442   std::string new_url_string;
443   if (args_.get() && args_->GetString(3u, &new_url_string))
444     new_url = GURL(new_url_string);
445
446   bool new_url_could_be_ad = UrlCouldBeAd(new_url);
447   bool prev_url_valid = prev_url.is_valid() && !prev_url.is_empty();
448
449   bool injected_ad = new_url_could_be_ad && database->IsAdNetwork(new_url);
450   bool replaced_ad = prev_url_valid && database->IsAdNetwork(prev_url);
451
452   if (injected_ad && replaced_ad)
453     return INJECTION_REPLACED_AD;
454   if (injected_ad)
455     return INJECTION_NEW_AD;
456   if (replaced_ad)
457     return INJECTION_REMOVED_AD;
458
459   // If the extension modified the URL with an external, valid URL then there's
460   // a good chance it's ad injection. Log it as a likely one, which also helps
461   // us determine the effectiveness of our IsAdNetwork() recognition.
462   if (new_url_could_be_ad) {
463     if (prev_url_valid)
464       return INJECTION_LIKELY_REPLACED_AD;
465     return INJECTION_LIKELY_NEW_AD;
466   }
467
468   return NO_AD_INJECTION;
469 }
470
471 Action::InjectionType Action::CheckElementAddition() const {
472   DCHECK_EQ(kBlinkAddElementEvent, api_name_);
473
474   GURL url;
475   std::string url_string;
476   if (args_.get() && args_->GetString(1u, &url_string))
477     url = GURL(url_string);
478
479   if (UrlCouldBeAd(url)) {
480     if (AdNetworkDatabase::Get()->IsAdNetwork(url))
481       return INJECTION_NEW_AD;
482     // If the extension injected an URL which is not local to itself or the
483     // page, there is a good chance it could be a new ad, and our database
484     // missed it.
485     return INJECTION_LIKELY_NEW_AD;
486   }
487   return NO_AD_INJECTION;
488 }
489
490 bool ActionComparator::operator()(
491     const scoped_refptr<Action>& lhs,
492     const scoped_refptr<Action>& rhs) const {
493   if (lhs->time() != rhs->time())
494     return lhs->time() < rhs->time();
495   else if (lhs->action_id() != rhs->action_id())
496     return lhs->action_id() < rhs->action_id();
497   else
498     return ActionComparatorExcludingTimeAndActionId()(lhs, rhs);
499 }
500
501 bool ActionComparatorExcludingTimeAndActionId::operator()(
502     const scoped_refptr<Action>& lhs,
503     const scoped_refptr<Action>& rhs) const {
504   if (lhs->extension_id() != rhs->extension_id())
505     return lhs->extension_id() < rhs->extension_id();
506   if (lhs->action_type() != rhs->action_type())
507     return lhs->action_type() < rhs->action_type();
508   if (lhs->api_name() != rhs->api_name())
509     return lhs->api_name() < rhs->api_name();
510
511   // args might be null; treat a null value as less than all non-null values,
512   // including the empty string.
513   if (!lhs->args() && rhs->args())
514     return true;
515   if (lhs->args() && !rhs->args())
516     return false;
517   if (lhs->args() && rhs->args()) {
518     std::string lhs_args = ActivityLogPolicy::Util::Serialize(lhs->args());
519     std::string rhs_args = ActivityLogPolicy::Util::Serialize(rhs->args());
520     if (lhs_args != rhs_args)
521       return lhs_args < rhs_args;
522   }
523
524   // Compare URLs as strings, and treat the incognito flag as a separate field.
525   if (lhs->page_url().spec() != rhs->page_url().spec())
526     return lhs->page_url().spec() < rhs->page_url().spec();
527   if (lhs->page_incognito() != rhs->page_incognito())
528     return lhs->page_incognito() < rhs->page_incognito();
529
530   if (lhs->page_title() != rhs->page_title())
531     return lhs->page_title() < rhs->page_title();
532
533   if (lhs->arg_url().spec() != rhs->arg_url().spec())
534     return lhs->arg_url().spec() < rhs->arg_url().spec();
535   if (lhs->arg_incognito() != rhs->arg_incognito())
536     return lhs->arg_incognito() < rhs->arg_incognito();
537
538   // other is treated much like the args field.
539   if (!lhs->other() && rhs->other())
540     return true;
541   if (lhs->other() && !rhs->other())
542     return false;
543   if (lhs->other() && rhs->other()) {
544     std::string lhs_other = ActivityLogPolicy::Util::Serialize(lhs->other());
545     std::string rhs_other = ActivityLogPolicy::Util::Serialize(rhs->other());
546     if (lhs_other != rhs_other)
547       return lhs_other < rhs_other;
548   }
549
550   // All fields compare as equal if this point is reached.
551   return false;
552 }
553
554 }  // namespace extensions