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