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