1 // Copyright 2014 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 "extensions/renderer/user_script_injector.h"
6 
7 #include <tuple>
8 #include <vector>
9 
10 #include "base/lazy_instance.h"
11 #include "content/public/common/url_constants.h"
12 #include "content/public/renderer/render_frame.h"
13 #include "content/public/renderer/render_thread.h"
14 #include "content/public/renderer/render_view.h"
15 #include "extensions/common/extension.h"
16 #include "extensions/common/guest_view/extensions_guest_view_messages.h"
17 #include "extensions/common/permissions/permissions_data.h"
18 #include "extensions/grit/extensions_renderer_resources.h"
19 #include "extensions/renderer/injection_host.h"
20 #include "extensions/renderer/script_context.h"
21 #include "extensions/renderer/scripts_run_info.h"
22 #include "third_party/blink/public/web/web_document.h"
23 #include "third_party/blink/public/web/web_local_frame.h"
24 #include "third_party/blink/public/web/web_script_source.h"
25 #include "ui/base/resource/resource_bundle.h"
26 #include "url/gurl.h"
27 
28 namespace extensions {
29 
30 namespace {
31 
32 struct RoutingInfoKey {
33   int routing_id;
34   int script_id;
35 
RoutingInfoKeyextensions::__anon8112fdc90111::RoutingInfoKey36   RoutingInfoKey(int routing_id, int script_id)
37       : routing_id(routing_id), script_id(script_id) {}
38 
operator <extensions::__anon8112fdc90111::RoutingInfoKey39   bool operator<(const RoutingInfoKey& other) const {
40     return std::tie(routing_id, script_id) <
41            std::tie(other.routing_id, other.script_id);
42   }
43 };
44 
45 using RoutingInfoMap = std::map<RoutingInfoKey, bool>;
46 
47 // A map records whether a given |script_id| from a webview-added user script
48 // is allowed to inject on the render of given |routing_id|.
49 // Once a script is added, the decision of whether or not allowed to inject
50 // won't be changed.
51 // After removed by the webview, the user scipt will also be removed
52 // from the render. Therefore, there won't be any query from the same
53 // |script_id| and |routing_id| pair.
54 base::LazyInstance<RoutingInfoMap>::DestructorAtExit g_routing_info_map =
55     LAZY_INSTANCE_INITIALIZER;
56 
57 // Greasemonkey API source that is injected with the scripts.
58 struct GreasemonkeyApiJsString {
59   GreasemonkeyApiJsString();
60   blink::WebScriptSource GetSource() const;
61 
62  private:
63   blink::WebString source_;
64 };
65 
66 // The below constructor, monstrous as it is, just makes a WebScriptSource from
67 // the GreasemonkeyApiJs resource.
GreasemonkeyApiJsString()68 GreasemonkeyApiJsString::GreasemonkeyApiJsString() {
69   base::StringPiece source_piece =
70       ui::ResourceBundle::GetSharedInstance().GetRawDataResource(
71           IDR_GREASEMONKEY_API_JS);
72   source_ =
73       blink::WebString::FromUTF8(source_piece.data(), source_piece.length());
74 }
75 
GetSource() const76 blink::WebScriptSource GreasemonkeyApiJsString::GetSource() const {
77   return blink::WebScriptSource(source_);
78 }
79 
80 base::LazyInstance<GreasemonkeyApiJsString>::Leaky g_greasemonkey_api =
81     LAZY_INSTANCE_INITIALIZER;
82 
ShouldInjectScripts(const UserScript::FileList & scripts,const std::set<std::string> & injected_files)83 bool ShouldInjectScripts(const UserScript::FileList& scripts,
84                          const std::set<std::string>& injected_files) {
85   for (const std::unique_ptr<UserScript::File>& file : scripts) {
86     // Check if the script is already injected.
87     if (injected_files.count(file->url().path()) == 0) {
88       return true;
89     }
90   }
91   return false;
92 }
93 
94 }  // namespace
95 
UserScriptInjector(const UserScript * script,UserScriptSet * script_list,bool is_declarative)96 UserScriptInjector::UserScriptInjector(const UserScript* script,
97                                        UserScriptSet* script_list,
98                                        bool is_declarative)
99     : script_(script),
100       user_script_set_(script_list),
101       script_id_(script_->id()),
102       host_id_(script_->host_id()),
103       is_declarative_(is_declarative),
104       user_script_set_observer_(this) {
105   user_script_set_observer_.Add(script_list);
106 }
107 
~UserScriptInjector()108 UserScriptInjector::~UserScriptInjector() {
109 }
110 
OnUserScriptsUpdated(const std::set<HostID> & changed_hosts,const UserScriptList & scripts)111 void UserScriptInjector::OnUserScriptsUpdated(
112     const std::set<HostID>& changed_hosts,
113     const UserScriptList& scripts) {
114   // When user scripts are updated, all the old script pointers are invalidated.
115   script_ = nullptr;
116   // If the host causing this injection changed, then this injection
117   // will be removed, and there's no guarantee the backing script still exists.
118   if (changed_hosts.count(host_id_) > 0)
119     return;
120 
121   for (const std::unique_ptr<UserScript>& script : scripts) {
122     if (script->id() == script_id_) {
123       script_ = script.get();
124       break;
125     }
126   }
127   // If |host_id_| wasn't in |changed_hosts|, then the script for this injection
128   // should be guaranteed to exist.
129   DCHECK(script_);
130 }
131 
script_type() const132 UserScript::InjectionType UserScriptInjector::script_type() const {
133   return UserScript::CONTENT_SCRIPT;
134 }
135 
IsUserGesture() const136 bool UserScriptInjector::IsUserGesture() const {
137   return false;
138 }
139 
ExpectsResults() const140 bool UserScriptInjector::ExpectsResults() const {
141   return false;
142 }
143 
GetCssOrigin() const144 base::Optional<CSSOrigin> UserScriptInjector::GetCssOrigin() const {
145   return base::nullopt;
146 }
147 
GetInjectionKey() const148 const base::Optional<std::string> UserScriptInjector::GetInjectionKey() const {
149   return base::nullopt;
150 }
151 
ShouldInjectJs(UserScript::RunLocation run_location,const std::set<std::string> & executing_scripts) const152 bool UserScriptInjector::ShouldInjectJs(
153     UserScript::RunLocation run_location,
154     const std::set<std::string>& executing_scripts) const {
155   return script_ && script_->run_location() == run_location &&
156          !script_->js_scripts().empty() &&
157          ShouldInjectScripts(script_->js_scripts(), executing_scripts);
158 }
159 
ShouldInjectCss(UserScript::RunLocation run_location,const std::set<std::string> & injected_stylesheets) const160 bool UserScriptInjector::ShouldInjectCss(
161     UserScript::RunLocation run_location,
162     const std::set<std::string>& injected_stylesheets) const {
163   return script_ && run_location == UserScript::DOCUMENT_START &&
164          !script_->css_scripts().empty() &&
165          ShouldInjectScripts(script_->css_scripts(), injected_stylesheets);
166 }
167 
CanExecuteOnFrame(const InjectionHost * injection_host,blink::WebLocalFrame * web_frame,int tab_id)168 PermissionsData::PageAccess UserScriptInjector::CanExecuteOnFrame(
169     const InjectionHost* injection_host,
170     blink::WebLocalFrame* web_frame,
171     int tab_id) {
172   // There is no harm in allowing the injection when the script is gone,
173   // because there is nothing to inject.
174   if (!script_)
175     return PermissionsData::PageAccess::kAllowed;
176 
177   if (script_->consumer_instance_type() ==
178           UserScript::ConsumerInstanceType::WEBVIEW) {
179     int routing_id = content::RenderView::FromWebView(web_frame->Top()->View())
180                          ->GetRoutingID();
181 
182     RoutingInfoKey key(routing_id, script_->id());
183 
184     RoutingInfoMap& map = g_routing_info_map.Get();
185     auto iter = map.find(key);
186 
187     bool allowed = false;
188     if (iter != map.end()) {
189       allowed = iter->second;
190     } else {
191       // Send a SYNC IPC message to the browser to check if this is allowed.
192       // This is not ideal, but is mitigated by the fact that this is only done
193       // for webviews, and then only once per host.
194       // TODO(hanxi): Find a more efficient way to do this.
195       content::RenderThread::Get()->Send(
196           new ExtensionsGuestViewHostMsg_CanExecuteContentScriptSync(
197               routing_id, script_->id(), &allowed));
198       map.insert(std::pair<RoutingInfoKey, bool>(key, allowed));
199     }
200 
201     return allowed ? PermissionsData::PageAccess::kAllowed
202                    : PermissionsData::PageAccess::kDenied;
203   }
204 
205   GURL effective_document_url = ScriptContext::GetEffectiveDocumentURL(
206       web_frame, web_frame->GetDocument().Url(), script_->match_about_blank());
207 
208   return injection_host->CanExecuteOnFrame(
209       effective_document_url,
210       content::RenderFrame::FromWebFrame(web_frame),
211       tab_id,
212       is_declarative_);
213 }
214 
GetJsSources(UserScript::RunLocation run_location,std::set<std::string> * executing_scripts,size_t * num_injected_js_scripts) const215 std::vector<blink::WebScriptSource> UserScriptInjector::GetJsSources(
216     UserScript::RunLocation run_location,
217     std::set<std::string>* executing_scripts,
218     size_t* num_injected_js_scripts) const {
219   DCHECK(script_);
220   std::vector<blink::WebScriptSource> sources;
221 
222   DCHECK_EQ(script_->run_location(), run_location);
223 
224   const UserScript::FileList& js_scripts = script_->js_scripts();
225   sources.reserve(js_scripts.size() +
226                   (script_->emulate_greasemonkey() ? 1 : 0));
227   // Emulate Greasemonkey API for scripts that were converted to extension
228   // user scripts.
229   if (script_->emulate_greasemonkey())
230     sources.push_back(g_greasemonkey_api.Get().GetSource());
231   for (const std::unique_ptr<UserScript::File>& file : js_scripts) {
232     const GURL& script_url = file->url();
233     // Check if the script is already injected.
234     if (executing_scripts->count(script_url.path()) != 0)
235       continue;
236 
237     sources.push_back(blink::WebScriptSource(
238         user_script_set_->GetJsSource(*file, script_->emulate_greasemonkey()),
239         script_url));
240 
241     (*num_injected_js_scripts) += 1;
242     executing_scripts->insert(script_url.path());
243   }
244 
245   return sources;
246 }
247 
GetCssSources(UserScript::RunLocation run_location,std::set<std::string> * injected_stylesheets,size_t * num_injected_stylesheets) const248 std::vector<blink::WebString> UserScriptInjector::GetCssSources(
249     UserScript::RunLocation run_location,
250     std::set<std::string>* injected_stylesheets,
251     size_t* num_injected_stylesheets) const {
252   DCHECK(script_);
253   DCHECK_EQ(UserScript::DOCUMENT_START, run_location);
254 
255   std::vector<blink::WebString> sources;
256 
257   const UserScript::FileList& css_scripts = script_->css_scripts();
258   sources.reserve(css_scripts.size());
259   for (const std::unique_ptr<UserScript::File>& file : script_->css_scripts()) {
260     const std::string& stylesheet_path = file->url().path();
261     // Check if the stylesheet is already injected.
262     if (injected_stylesheets->count(stylesheet_path) != 0)
263       continue;
264 
265     sources.push_back(user_script_set_->GetCssSource(*file));
266     (*num_injected_stylesheets) += 1;
267     injected_stylesheets->insert(stylesheet_path);
268   }
269   return sources;
270 }
271 
OnInjectionComplete(std::unique_ptr<base::Value> execution_result,UserScript::RunLocation run_location,content::RenderFrame * render_frame)272 void UserScriptInjector::OnInjectionComplete(
273     std::unique_ptr<base::Value> execution_result,
274     UserScript::RunLocation run_location,
275     content::RenderFrame* render_frame) {}
276 
OnWillNotInject(InjectFailureReason reason,content::RenderFrame * render_frame)277 void UserScriptInjector::OnWillNotInject(InjectFailureReason reason,
278                                          content::RenderFrame* render_frame) {
279 }
280 
281 }  // namespace extensions
282