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 "chrome/browser/extensions/user_script_loader.h" 6 7#include <set> 8#include <string> 9 10#include "base/bind.h" 11#include "base/bind_helpers.h" 12#include "base/files/file_path.h" 13#include "base/files/file_util.h" 14#include "base/memory/shared_memory.h" 15#include "base/version.h" 16#include "chrome/browser/chrome_notification_types.h" 17#include "chrome/browser/profiles/profile.h" 18#include "chrome/common/extensions/api/i18n/default_locale_handler.h" 19#include "content/public/browser/browser_thread.h" 20#include "content/public/browser/notification_service.h" 21#include "content/public/browser/render_process_host.h" 22#include "extensions/browser/component_extension_resource_manager.h" 23#include "extensions/browser/content_verifier.h" 24#include "extensions/browser/extension_registry.h" 25#include "extensions/browser/extension_system.h" 26#include "extensions/browser/extensions_browser_client.h" 27#include "extensions/common/extension_messages.h" 28#include "extensions/common/file_util.h" 29#include "extensions/common/message_bundle.h" 30#include "extensions/common/one_shot_event.h" 31#include "ui/base/resource/resource_bundle.h" 32 33using content::BrowserThread; 34using extensions::ExtensionsBrowserClient; 35 36namespace extensions { 37 38namespace { 39 40typedef base::Callback< 41 void(scoped_ptr<UserScriptList>, scoped_ptr<base::SharedMemory>)> 42 LoadScriptsCallback; 43 44void VerifyContent(scoped_refptr<ContentVerifier> verifier, 45 const ExtensionId& extension_id, 46 const base::FilePath& extension_root, 47 const base::FilePath& relative_path, 48 const std::string& content) { 49 DCHECK_CURRENTLY_ON(content::BrowserThread::IO); 50 scoped_refptr<ContentVerifyJob> job( 51 verifier->CreateJobFor(extension_id, extension_root, relative_path)); 52 if (job.get()) { 53 job->Start(); 54 job->BytesRead(content.size(), content.data()); 55 job->DoneReading(); 56 } 57} 58 59bool LoadScriptContent(const ExtensionId& extension_id, 60 UserScript::File* script_file, 61 const SubstitutionMap* localization_messages, 62 scoped_refptr<ContentVerifier> verifier) { 63 std::string content; 64 const base::FilePath& path = ExtensionResource::GetFilePath( 65 script_file->extension_root(), 66 script_file->relative_path(), 67 ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT); 68 if (path.empty()) { 69 int resource_id; 70 if (ExtensionsBrowserClient::Get()->GetComponentExtensionResourceManager()-> 71 IsComponentExtensionResource(script_file->extension_root(), 72 script_file->relative_path(), 73 &resource_id)) { 74 const ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 75 content = rb.GetRawDataResource(resource_id).as_string(); 76 } else { 77 LOG(WARNING) << "Failed to get file path to " 78 << script_file->relative_path().value() << " from " 79 << script_file->extension_root().value(); 80 return false; 81 } 82 } else { 83 if (!base::ReadFileToString(path, &content)) { 84 LOG(WARNING) << "Failed to load user script file: " << path.value(); 85 return false; 86 } 87 if (verifier.get()) { 88 content::BrowserThread::PostTask(content::BrowserThread::IO, 89 FROM_HERE, 90 base::Bind(&VerifyContent, 91 verifier, 92 extension_id, 93 script_file->extension_root(), 94 script_file->relative_path(), 95 content)); 96 } 97 } 98 99 // Localize the content. 100 if (localization_messages) { 101 std::string error; 102 MessageBundle::ReplaceMessagesWithExternalDictionary( 103 *localization_messages, &content, &error); 104 if (!error.empty()) { 105 LOG(WARNING) << "Failed to replace messages in script: " << error; 106 } 107 } 108 109 // Remove BOM from the content. 110 std::string::size_type index = content.find(base::kUtf8ByteOrderMark); 111 if (index == 0) { 112 script_file->set_content(content.substr(strlen(base::kUtf8ByteOrderMark))); 113 } else { 114 script_file->set_content(content); 115 } 116 117 return true; 118} 119 120SubstitutionMap* GetLocalizationMessages(const ExtensionsInfo& extensions_info, 121 const ExtensionId& extension_id) { 122 ExtensionsInfo::const_iterator iter = extensions_info.find(extension_id); 123 if (iter == extensions_info.end()) 124 return NULL; 125 return file_util::LoadMessageBundleSubstitutionMap( 126 iter->second.first, extension_id, iter->second.second); 127} 128 129void LoadUserScripts(UserScriptList* user_scripts, 130 const ExtensionsInfo& extensions_info, 131 const std::set<int>& added_script_ids, 132 ContentVerifier* verifier) { 133 for (UserScriptList::iterator script = user_scripts->begin(); 134 script != user_scripts->end(); 135 ++script) { 136 if (added_script_ids.count(script->id()) == 0) 137 continue; 138 scoped_ptr<SubstitutionMap> localization_messages( 139 GetLocalizationMessages(extensions_info, script->extension_id())); 140 for (size_t k = 0; k < script->js_scripts().size(); ++k) { 141 UserScript::File& script_file = script->js_scripts()[k]; 142 if (script_file.GetContent().empty()) 143 LoadScriptContent(script->extension_id(), &script_file, NULL, verifier); 144 } 145 for (size_t k = 0; k < script->css_scripts().size(); ++k) { 146 UserScript::File& script_file = script->css_scripts()[k]; 147 if (script_file.GetContent().empty()) 148 LoadScriptContent(script->extension_id(), 149 &script_file, 150 localization_messages.get(), 151 verifier); 152 } 153 } 154} 155 156// Pickle user scripts and return pointer to the shared memory. 157scoped_ptr<base::SharedMemory> Serialize(const UserScriptList& scripts) { 158 Pickle pickle; 159 pickle.WriteUInt64(scripts.size()); 160 for (UserScriptList::const_iterator script = scripts.begin(); 161 script != scripts.end(); 162 ++script) { 163 // TODO(aa): This can be replaced by sending content script metadata to 164 // renderers along with other extension data in ExtensionMsg_Loaded. 165 // See crbug.com/70516. 166 script->Pickle(&pickle); 167 // Write scripts as 'data' so that we can read it out in the slave without 168 // allocating a new string. 169 for (size_t j = 0; j < script->js_scripts().size(); j++) { 170 base::StringPiece contents = script->js_scripts()[j].GetContent(); 171 pickle.WriteData(contents.data(), contents.length()); 172 } 173 for (size_t j = 0; j < script->css_scripts().size(); j++) { 174 base::StringPiece contents = script->css_scripts()[j].GetContent(); 175 pickle.WriteData(contents.data(), contents.length()); 176 } 177 } 178 179 // Create the shared memory object. 180 base::SharedMemory shared_memory; 181 182 base::SharedMemoryCreateOptions options; 183 options.size = pickle.size(); 184 options.share_read_only = true; 185 if (!shared_memory.Create(options)) 186 return scoped_ptr<base::SharedMemory>(); 187 188 if (!shared_memory.Map(pickle.size())) 189 return scoped_ptr<base::SharedMemory>(); 190 191 // Copy the pickle to shared memory. 192 memcpy(shared_memory.memory(), pickle.data(), pickle.size()); 193 194 base::SharedMemoryHandle readonly_handle; 195 if (!shared_memory.ShareReadOnlyToProcess(base::GetCurrentProcessHandle(), 196 &readonly_handle)) 197 return scoped_ptr<base::SharedMemory>(); 198 199 return make_scoped_ptr(new base::SharedMemory(readonly_handle, 200 /*read_only=*/true)); 201} 202 203void LoadScriptsOnFileThread(scoped_ptr<UserScriptList> user_scripts, 204 const ExtensionsInfo& extensions_info, 205 const std::set<int>& added_script_ids, 206 scoped_refptr<ContentVerifier> verifier, 207 LoadScriptsCallback callback) { 208 DCHECK(user_scripts.get()); 209 LoadUserScripts( 210 user_scripts.get(), extensions_info, added_script_ids, verifier.get()); 211 scoped_ptr<base::SharedMemory> memory = Serialize(*user_scripts); 212 BrowserThread::PostTask( 213 BrowserThread::UI, 214 FROM_HERE, 215 base::Bind(callback, base::Passed(&user_scripts), base::Passed(&memory))); 216} 217 218// Helper function to parse greasesmonkey headers 219bool GetDeclarationValue(const base::StringPiece& line, 220 const base::StringPiece& prefix, 221 std::string* value) { 222 base::StringPiece::size_type index = line.find(prefix); 223 if (index == base::StringPiece::npos) 224 return false; 225 226 std::string temp(line.data() + index + prefix.length(), 227 line.length() - index - prefix.length()); 228 229 if (temp.empty() || !IsWhitespace(temp[0])) 230 return false; 231 232 base::TrimWhitespaceASCII(temp, base::TRIM_ALL, value); 233 return true; 234} 235 236} // namespace 237 238// static 239bool UserScriptLoader::ParseMetadataHeader(const base::StringPiece& script_text, 240 UserScript* script) { 241 // http://wiki.greasespot.net/Metadata_block 242 base::StringPiece line; 243 size_t line_start = 0; 244 size_t line_end = line_start; 245 bool in_metadata = false; 246 247 static const base::StringPiece kUserScriptBegin("// ==UserScript=="); 248 static const base::StringPiece kUserScriptEng("// ==/UserScript=="); 249 static const base::StringPiece kNamespaceDeclaration("// @namespace"); 250 static const base::StringPiece kNameDeclaration("// @name"); 251 static const base::StringPiece kVersionDeclaration("// @version"); 252 static const base::StringPiece kDescriptionDeclaration("// @description"); 253 static const base::StringPiece kIncludeDeclaration("// @include"); 254 static const base::StringPiece kExcludeDeclaration("// @exclude"); 255 static const base::StringPiece kMatchDeclaration("// @match"); 256 static const base::StringPiece kExcludeMatchDeclaration("// @exclude_match"); 257 static const base::StringPiece kRunAtDeclaration("// @run-at"); 258 static const base::StringPiece kRunAtDocumentStartValue("document-start"); 259 static const base::StringPiece kRunAtDocumentEndValue("document-end"); 260 static const base::StringPiece kRunAtDocumentIdleValue("document-idle"); 261 262 while (line_start < script_text.length()) { 263 line_end = script_text.find('\n', line_start); 264 265 // Handle the case where there is no trailing newline in the file. 266 if (line_end == std::string::npos) 267 line_end = script_text.length() - 1; 268 269 line.set(script_text.data() + line_start, line_end - line_start); 270 271 if (!in_metadata) { 272 if (line.starts_with(kUserScriptBegin)) 273 in_metadata = true; 274 } else { 275 if (line.starts_with(kUserScriptEng)) 276 break; 277 278 std::string value; 279 if (GetDeclarationValue(line, kIncludeDeclaration, &value)) { 280 // We escape some characters that MatchPattern() considers special. 281 ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\"); 282 ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?"); 283 script->add_glob(value); 284 } else if (GetDeclarationValue(line, kExcludeDeclaration, &value)) { 285 ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\"); 286 ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?"); 287 script->add_exclude_glob(value); 288 } else if (GetDeclarationValue(line, kNamespaceDeclaration, &value)) { 289 script->set_name_space(value); 290 } else if (GetDeclarationValue(line, kNameDeclaration, &value)) { 291 script->set_name(value); 292 } else if (GetDeclarationValue(line, kVersionDeclaration, &value)) { 293 Version version(value); 294 if (version.IsValid()) 295 script->set_version(version.GetString()); 296 } else if (GetDeclarationValue(line, kDescriptionDeclaration, &value)) { 297 script->set_description(value); 298 } else if (GetDeclarationValue(line, kMatchDeclaration, &value)) { 299 URLPattern pattern(UserScript::ValidUserScriptSchemes()); 300 if (URLPattern::PARSE_SUCCESS != pattern.Parse(value)) 301 return false; 302 script->add_url_pattern(pattern); 303 } else if (GetDeclarationValue(line, kExcludeMatchDeclaration, &value)) { 304 URLPattern exclude(UserScript::ValidUserScriptSchemes()); 305 if (URLPattern::PARSE_SUCCESS != exclude.Parse(value)) 306 return false; 307 script->add_exclude_url_pattern(exclude); 308 } else if (GetDeclarationValue(line, kRunAtDeclaration, &value)) { 309 if (value == kRunAtDocumentStartValue) 310 script->set_run_location(UserScript::DOCUMENT_START); 311 else if (value == kRunAtDocumentEndValue) 312 script->set_run_location(UserScript::DOCUMENT_END); 313 else if (value == kRunAtDocumentIdleValue) 314 script->set_run_location(UserScript::DOCUMENT_IDLE); 315 else 316 return false; 317 } 318 319 // TODO(aa): Handle more types of metadata. 320 } 321 322 line_start = line_end + 1; 323 } 324 325 // If no patterns were specified, default to @include *. This is what 326 // Greasemonkey does. 327 if (script->globs().empty() && script->url_patterns().is_empty()) 328 script->add_glob("*"); 329 330 return true; 331} 332 333// static 334void UserScriptLoader::LoadScriptsForTest(UserScriptList* user_scripts) { 335 ExtensionsInfo info; 336 std::set<int> added_script_ids; 337 for (UserScriptList::iterator it = user_scripts->begin(); 338 it != user_scripts->end(); 339 ++it) { 340 added_script_ids.insert(it->id()); 341 } 342 LoadUserScripts( 343 user_scripts, info, added_script_ids, NULL /* no verifier for testing */); 344} 345 346UserScriptLoader::UserScriptLoader(Profile* profile, 347 const ExtensionId& owner_extension_id, 348 bool listen_for_extension_system_loaded) 349 : user_scripts_(new UserScriptList()), 350 clear_scripts_(false), 351 extension_system_ready_(false), 352 pending_load_(false), 353 profile_(profile), 354 owner_extension_id_(owner_extension_id), 355 extension_registry_observer_(this), 356 weak_factory_(this) { 357 extension_registry_observer_.Add(ExtensionRegistry::Get(profile)); 358 if (listen_for_extension_system_loaded) { 359 ExtensionSystem::Get(profile_)->ready().Post( 360 FROM_HERE, 361 base::Bind(&UserScriptLoader::OnExtensionSystemReady, 362 weak_factory_.GetWeakPtr())); 363 } else { 364 extension_system_ready_ = true; 365 } 366 registrar_.Add(this, 367 content::NOTIFICATION_RENDERER_PROCESS_CREATED, 368 content::NotificationService::AllBrowserContextsAndSources()); 369} 370 371UserScriptLoader::~UserScriptLoader() { 372} 373 374void UserScriptLoader::AddScripts(const std::set<UserScript>& scripts) { 375 for (std::set<UserScript>::const_iterator it = scripts.begin(); 376 it != scripts.end(); 377 ++it) { 378 removed_scripts_.erase(*it); 379 added_scripts_.insert(*it); 380 } 381 AttemptLoad(); 382} 383 384void UserScriptLoader::RemoveScripts(const std::set<UserScript>& scripts) { 385 for (std::set<UserScript>::const_iterator it = scripts.begin(); 386 it != scripts.end(); 387 ++it) { 388 added_scripts_.erase(*it); 389 removed_scripts_.insert(*it); 390 } 391 AttemptLoad(); 392} 393 394void UserScriptLoader::ClearScripts() { 395 clear_scripts_ = true; 396 added_scripts_.clear(); 397 removed_scripts_.clear(); 398 AttemptLoad(); 399} 400 401void UserScriptLoader::Observe(int type, 402 const content::NotificationSource& source, 403 const content::NotificationDetails& details) { 404 DCHECK_EQ(type, content::NOTIFICATION_RENDERER_PROCESS_CREATED); 405 content::RenderProcessHost* process = 406 content::Source<content::RenderProcessHost>(source).ptr(); 407 Profile* profile = Profile::FromBrowserContext(process->GetBrowserContext()); 408 if (!profile_->IsSameProfile(profile)) 409 return; 410 if (scripts_ready()) { 411 SendUpdate(process, 412 shared_memory_.get(), 413 std::set<ExtensionId>()); // Include all extensions. 414 } 415} 416 417void UserScriptLoader::OnExtensionUnloaded( 418 content::BrowserContext* browser_context, 419 const Extension* extension, 420 UnloadedExtensionInfo::Reason reason) { 421 extensions_info_.erase(extension->id()); 422} 423 424void UserScriptLoader::OnExtensionSystemReady() { 425 extension_system_ready_ = true; 426 AttemptLoad(); 427} 428 429bool UserScriptLoader::ScriptsMayHaveChanged() const { 430 // Scripts may have changed if there are scripts added, scripts removed, or 431 // if scripts were cleared and either: 432 // (1) A load is in progress (which may result in a non-zero number of 433 // scripts that need to be cleared), or 434 // (2) The current set of scripts is non-empty (so they need to be cleared). 435 return (added_scripts_.size() || 436 removed_scripts_.size() || 437 (clear_scripts_ && 438 (is_loading() || user_scripts_->size()))); 439} 440 441void UserScriptLoader::AttemptLoad() { 442 if (extension_system_ready_ && ScriptsMayHaveChanged()) { 443 if (is_loading()) 444 pending_load_ = true; 445 else 446 StartLoad(); 447 } 448} 449 450void UserScriptLoader::StartLoad() { 451 DCHECK_CURRENTLY_ON(BrowserThread::UI); 452 DCHECK(!is_loading()); 453 454 // If scripts were marked for clearing before adding and removing, then clear 455 // them. 456 if (clear_scripts_) { 457 user_scripts_->clear(); 458 } else { 459 for (UserScriptList::iterator it = user_scripts_->begin(); 460 it != user_scripts_->end();) { 461 if (removed_scripts_.count(*it)) 462 it = user_scripts_->erase(it); 463 else 464 ++it; 465 } 466 } 467 468 user_scripts_->insert( 469 user_scripts_->end(), added_scripts_.begin(), added_scripts_.end()); 470 471 std::set<int> added_script_ids; 472 for (std::set<UserScript>::const_iterator it = added_scripts_.begin(); 473 it != added_scripts_.end(); 474 ++it) { 475 added_script_ids.insert(it->id()); 476 } 477 478 // Expand |changed_extensions_| for OnScriptsLoaded, which will use it in 479 // its IPC message. This must be done before we clear |added_scripts_| and 480 // |removed_scripts_| below. 481 std::set<UserScript> changed_scripts(added_scripts_); 482 changed_scripts.insert(removed_scripts_.begin(), removed_scripts_.end()); 483 ExpandChangedExtensions(changed_scripts); 484 485 // Update |extensions_info_| to contain info from every extension in 486 // |changed_extensions_| before passing it to LoadScriptsOnFileThread. 487 UpdateExtensionsInfo(); 488 489 BrowserThread::PostTask( 490 BrowserThread::FILE, 491 FROM_HERE, 492 base::Bind(&LoadScriptsOnFileThread, 493 base::Passed(&user_scripts_), 494 extensions_info_, 495 added_script_ids, 496 make_scoped_refptr( 497 ExtensionSystem::Get(profile_)->content_verifier()), 498 base::Bind(&UserScriptLoader::OnScriptsLoaded, 499 weak_factory_.GetWeakPtr()))); 500 501 clear_scripts_ = false; 502 added_scripts_.clear(); 503 removed_scripts_.clear(); 504 user_scripts_.reset(NULL); 505} 506 507void UserScriptLoader::OnScriptsLoaded( 508 scoped_ptr<UserScriptList> user_scripts, 509 scoped_ptr<base::SharedMemory> shared_memory) { 510 user_scripts_.reset(user_scripts.release()); 511 if (pending_load_) { 512 // While we were loading, there were further changes. Don't bother 513 // notifying about these scripts and instead just immediately reload. 514 pending_load_ = false; 515 StartLoad(); 516 return; 517 } 518 519 if (shared_memory.get() == NULL) { 520 // This can happen if we run out of file descriptors. In that case, we 521 // have a choice between silently omitting all user scripts for new tabs, 522 // by nulling out shared_memory_, or only silently omitting new ones by 523 // leaving the existing object in place. The second seems less bad, even 524 // though it removes the possibility that freeing the shared memory block 525 // would open up enough FDs for long enough for a retry to succeed. 526 527 // Pretend the extension change didn't happen. 528 return; 529 } 530 531 // We've got scripts ready to go. 532 shared_memory_.reset(shared_memory.release()); 533 534 for (content::RenderProcessHost::iterator i( 535 content::RenderProcessHost::AllHostsIterator()); 536 !i.IsAtEnd(); 537 i.Advance()) { 538 SendUpdate(i.GetCurrentValue(), shared_memory_.get(), changed_extensions_); 539 } 540 changed_extensions_.clear(); 541 542 content::NotificationService::current()->Notify( 543 extensions::NOTIFICATION_USER_SCRIPTS_UPDATED, 544 content::Source<Profile>(profile_), 545 content::Details<base::SharedMemory>(shared_memory_.get())); 546} 547 548void UserScriptLoader::SendUpdate( 549 content::RenderProcessHost* process, 550 base::SharedMemory* shared_memory, 551 const std::set<ExtensionId>& changed_extensions) { 552 // Don't allow injection of content scripts into <webview>. 553 if (process->IsIsolatedGuest()) 554 return; 555 556 Profile* profile = Profile::FromBrowserContext(process->GetBrowserContext()); 557 // Make sure we only send user scripts to processes in our profile. 558 if (!profile_->IsSameProfile(profile)) 559 return; 560 561 // If the process is being started asynchronously, early return. We'll end up 562 // calling InitUserScripts when it's created which will call this again. 563 base::ProcessHandle handle = process->GetHandle(); 564 if (!handle) 565 return; 566 567 base::SharedMemoryHandle handle_for_process; 568 if (!shared_memory->ShareToProcess(handle, &handle_for_process)) 569 return; // This can legitimately fail if the renderer asserts at startup. 570 571 if (base::SharedMemory::IsHandleValid(handle_for_process)) { 572 process->Send(new ExtensionMsg_UpdateUserScripts( 573 handle_for_process, owner_extension_id_, changed_extensions)); 574 } 575} 576 577void UserScriptLoader::ExpandChangedExtensions( 578 const std::set<UserScript>& scripts) { 579 for (std::set<UserScript>::const_iterator it = scripts.begin(); 580 it != scripts.end(); 581 ++it) { 582 changed_extensions_.insert(it->extension_id()); 583 } 584} 585 586void UserScriptLoader::UpdateExtensionsInfo() { 587 ExtensionRegistry* registry = ExtensionRegistry::Get(profile_); 588 for (std::set<ExtensionId>::const_iterator it = changed_extensions_.begin(); 589 it != changed_extensions_.end(); 590 ++it) { 591 if (extensions_info_.find(*it) == extensions_info_.end()) { 592 const Extension* extension = 593 registry->GetExtensionById(*it, ExtensionRegistry::EVERYTHING); 594 // |changed_extensions_| may include extensions that have been removed, 595 // which leads to the above lookup failing. In this case, just continue. 596 if (!extension) 597 continue; 598 extensions_info_[*it] = ExtensionSet::ExtensionPathAndDefaultLocale( 599 extension->path(), LocaleInfo::GetDefaultLocale(extension)); 600 } 601 } 602} 603 604} // namespace extensions 605