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