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/files/file_path.h" 11#include "base/files/file_util.h" 12#include "base/files/scoped_temp_dir.h" 13#include "base/message_loop/message_loop.h" 14#include "base/path_service.h" 15#include "base/strings/string_util.h" 16#include "chrome/browser/chrome_notification_types.h" 17#include "chrome/test/base/testing_profile.h" 18#include "content/public/browser/notification_observer.h" 19#include "content/public/browser/notification_registrar.h" 20#include "content/public/browser/notification_service.h" 21#include "content/public/test/test_browser_thread.h" 22#include "testing/gtest/include/gtest/gtest.h" 23 24using content::BrowserThread; 25using extensions::URLPatternSet; 26 27namespace { 28 29static void AddPattern(URLPatternSet* extent, const std::string& pattern) { 30 int schemes = URLPattern::SCHEME_ALL; 31 extent->AddPattern(URLPattern(schemes, pattern)); 32} 33} 34 35namespace extensions { 36 37// Test bringing up a script loader on a specific directory, putting a script 38// in there, etc. 39 40class UserScriptLoaderTest : public testing::Test, 41 public content::NotificationObserver { 42 public: 43 UserScriptLoaderTest() : shared_memory_(NULL) {} 44 45 virtual void SetUp() { 46 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 47 48 // Register for all user script notifications. 49 registrar_.Add(this, 50 extensions::NOTIFICATION_USER_SCRIPTS_UPDATED, 51 content::NotificationService::AllSources()); 52 53 // UserScriptLoader posts tasks to the file thread so make the current 54 // thread look like one. 55 file_thread_.reset(new content::TestBrowserThread( 56 BrowserThread::FILE, base::MessageLoop::current())); 57 ui_thread_.reset(new content::TestBrowserThread( 58 BrowserThread::UI, base::MessageLoop::current())); 59 } 60 61 virtual void TearDown() { 62 file_thread_.reset(); 63 ui_thread_.reset(); 64 } 65 66 virtual void Observe(int type, 67 const content::NotificationSource& source, 68 const content::NotificationDetails& details) OVERRIDE { 69 DCHECK(type == extensions::NOTIFICATION_USER_SCRIPTS_UPDATED); 70 71 shared_memory_ = content::Details<base::SharedMemory>(details).ptr(); 72 if (base::MessageLoop::current() == &message_loop_) 73 base::MessageLoop::current()->Quit(); 74 } 75 76 // Directory containing user scripts. 77 base::ScopedTempDir temp_dir_; 78 79 content::NotificationRegistrar registrar_; 80 81 // MessageLoop used in tests. 82 base::MessageLoopForUI message_loop_; 83 84 scoped_ptr<content::TestBrowserThread> file_thread_; 85 scoped_ptr<content::TestBrowserThread> ui_thread_; 86 87 // Updated to the script shared memory when we get notified. 88 base::SharedMemory* shared_memory_; 89}; 90 91// Test that we get notified even when there are no scripts. 92TEST_F(UserScriptLoaderTest, NoScripts) { 93 TestingProfile profile; 94 UserScriptLoader loader(&profile, 95 std::string() /* owner_extension_id */, 96 true /* listen_for_extension_system_loaded */); 97 loader.StartLoad(); 98 message_loop_.PostTask(FROM_HERE, base::MessageLoop::QuitClosure()); 99 message_loop_.Run(); 100 101 ASSERT_TRUE(shared_memory_ != NULL); 102} 103 104TEST_F(UserScriptLoaderTest, Parse1) { 105 const std::string text( 106 "// This is my awesome script\n" 107 "// It does stuff.\n" 108 "// ==UserScript== trailing garbage\n" 109 "// @name foobar script\n" 110 "// @namespace http://www.google.com/\n" 111 "// @include *mail.google.com*\n" 112 "// \n" 113 "// @othergarbage\n" 114 "// @include *mail.yahoo.com*\r\n" 115 "// @include \t *mail.msn.com*\n" // extra spaces after "@include" OK 116 "//@include not-recognized\n" // must have one space after "//" 117 "// ==/UserScript== trailing garbage\n" 118 "\n" 119 "\n" 120 "alert('hoo!');\n"); 121 122 UserScript script; 123 EXPECT_TRUE(UserScriptLoader::ParseMetadataHeader(text, &script)); 124 ASSERT_EQ(3U, script.globs().size()); 125 EXPECT_EQ("*mail.google.com*", script.globs()[0]); 126 EXPECT_EQ("*mail.yahoo.com*", script.globs()[1]); 127 EXPECT_EQ("*mail.msn.com*", script.globs()[2]); 128} 129 130TEST_F(UserScriptLoaderTest, Parse2) { 131 const std::string text("default to @include *"); 132 133 UserScript script; 134 EXPECT_TRUE(UserScriptLoader::ParseMetadataHeader(text, &script)); 135 ASSERT_EQ(1U, script.globs().size()); 136 EXPECT_EQ("*", script.globs()[0]); 137} 138 139TEST_F(UserScriptLoaderTest, Parse3) { 140 const std::string text( 141 "// ==UserScript==\n" 142 "// @include *foo*\n" 143 "// ==/UserScript=="); // no trailing newline 144 145 UserScript script; 146 UserScriptLoader::ParseMetadataHeader(text, &script); 147 ASSERT_EQ(1U, script.globs().size()); 148 EXPECT_EQ("*foo*", script.globs()[0]); 149} 150 151TEST_F(UserScriptLoaderTest, Parse4) { 152 const std::string text( 153 "// ==UserScript==\n" 154 "// @match http://*.mail.google.com/*\n" 155 "// @match \t http://mail.yahoo.com/*\n" 156 "// ==/UserScript==\n"); 157 158 URLPatternSet expected_patterns; 159 AddPattern(&expected_patterns, "http://*.mail.google.com/*"); 160 AddPattern(&expected_patterns, "http://mail.yahoo.com/*"); 161 162 UserScript script; 163 EXPECT_TRUE(UserScriptLoader::ParseMetadataHeader(text, &script)); 164 EXPECT_EQ(0U, script.globs().size()); 165 EXPECT_EQ(expected_patterns, script.url_patterns()); 166} 167 168TEST_F(UserScriptLoaderTest, Parse5) { 169 const std::string text( 170 "// ==UserScript==\n" 171 "// @match http://*mail.google.com/*\n" 172 "// ==/UserScript==\n"); 173 174 // Invalid @match value. 175 UserScript script; 176 EXPECT_FALSE(UserScriptLoader::ParseMetadataHeader(text, &script)); 177} 178 179TEST_F(UserScriptLoaderTest, Parse6) { 180 const std::string text( 181 "// ==UserScript==\n" 182 "// @include http://*.mail.google.com/*\n" 183 "// @match \t http://mail.yahoo.com/*\n" 184 "// ==/UserScript==\n"); 185 186 // Allowed to match @include and @match. 187 UserScript script; 188 EXPECT_TRUE(UserScriptLoader::ParseMetadataHeader(text, &script)); 189} 190 191TEST_F(UserScriptLoaderTest, Parse7) { 192 // Greasemonkey allows there to be any leading text before the comment marker. 193 const std::string text( 194 "// ==UserScript==\n" 195 "adsasdfasf// @name hello\n" 196 " // @description\twiggity woo\n" 197 "\t// @match \t http://mail.yahoo.com/*\n" 198 "// ==/UserScript==\n"); 199 200 UserScript script; 201 EXPECT_TRUE(UserScriptLoader::ParseMetadataHeader(text, &script)); 202 ASSERT_EQ("hello", script.name()); 203 ASSERT_EQ("wiggity woo", script.description()); 204 ASSERT_EQ(1U, script.url_patterns().patterns().size()); 205 EXPECT_EQ("http://mail.yahoo.com/*", 206 script.url_patterns().begin()->GetAsString()); 207} 208 209TEST_F(UserScriptLoaderTest, Parse8) { 210 const std::string text( 211 "// ==UserScript==\n" 212 "// @name myscript\n" 213 "// @match http://www.google.com/*\n" 214 "// @exclude_match http://www.google.com/foo*\n" 215 "// ==/UserScript==\n"); 216 217 UserScript script; 218 EXPECT_TRUE(UserScriptLoader::ParseMetadataHeader(text, &script)); 219 ASSERT_EQ("myscript", script.name()); 220 ASSERT_EQ(1U, script.url_patterns().patterns().size()); 221 EXPECT_EQ("http://www.google.com/*", 222 script.url_patterns().begin()->GetAsString()); 223 ASSERT_EQ(1U, script.exclude_url_patterns().patterns().size()); 224 EXPECT_EQ("http://www.google.com/foo*", 225 script.exclude_url_patterns().begin()->GetAsString()); 226} 227 228TEST_F(UserScriptLoaderTest, SkipBOMAtTheBeginning) { 229 base::FilePath path = temp_dir_.path().AppendASCII("script.user.js"); 230 const std::string content("\xEF\xBB\xBF alert('hello');"); 231 size_t written = base::WriteFile(path, content.c_str(), content.size()); 232 ASSERT_EQ(written, content.size()); 233 234 UserScript user_script; 235 user_script.js_scripts().push_back( 236 UserScript::File(temp_dir_.path(), path.BaseName(), GURL())); 237 238 UserScriptList user_scripts; 239 user_scripts.push_back(user_script); 240 241 UserScriptLoader::LoadScriptsForTest(&user_scripts); 242 243 EXPECT_EQ(content.substr(3), 244 user_scripts[0].js_scripts()[0].GetContent().as_string()); 245} 246 247TEST_F(UserScriptLoaderTest, LeaveBOMNotAtTheBeginning) { 248 base::FilePath path = temp_dir_.path().AppendASCII("script.user.js"); 249 const std::string content("alert('here's a BOOM: \xEF\xBB\xBF');"); 250 size_t written = base::WriteFile(path, content.c_str(), content.size()); 251 ASSERT_EQ(written, content.size()); 252 253 UserScript user_script; 254 user_script.js_scripts().push_back(UserScript::File( 255 temp_dir_.path(), path.BaseName(), GURL())); 256 257 UserScriptList user_scripts; 258 user_scripts.push_back(user_script); 259 260 UserScriptLoader::LoadScriptsForTest(&user_scripts); 261 262 EXPECT_EQ(content, user_scripts[0].js_scripts()[0].GetContent().as_string()); 263} 264 265} // namespace extensions 266