1// Copyright (c) 2011 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/shell_integration.h"
6
7#include <map>
8
9#include "base/file_path.h"
10#include "base/file_util.h"
11#include "base/memory/scoped_temp_dir.h"
12#include "base/message_loop.h"
13#include "base/stl_util-inl.h"
14#include "base/string_util.h"
15#include "base/utf_string_conversions.h"
16#include "chrome/browser/web_applications/web_app.h"
17#include "chrome/common/chrome_constants.h"
18#include "chrome/common/chrome_paths_internal.h"
19#include "content/browser/browser_thread.h"
20#include "googleurl/src/gurl.h"
21#include "testing/gtest/include/gtest/gtest.h"
22
23#if defined(OS_WIN)
24#include "chrome/installer/util/browser_distribution.h"
25#elif defined(OS_LINUX)
26#include "base/environment.h"
27#endif  // defined(OS_LINUX)
28
29#define FPL FILE_PATH_LITERAL
30
31#if defined(OS_LINUX)
32namespace {
33
34// Provides mock environment variables values based on a stored map.
35class MockEnvironment : public base::Environment {
36 public:
37  MockEnvironment() {}
38
39  void Set(const std::string& name, const std::string& value) {
40    variables_[name] = value;
41  }
42
43  virtual bool GetVar(const char* variable_name, std::string* result) {
44    if (ContainsKey(variables_, variable_name)) {
45      *result = variables_[variable_name];
46      return true;
47    }
48
49    return false;
50  }
51
52  virtual bool SetVar(const char* variable_name, const std::string& new_value) {
53    ADD_FAILURE();
54    return false;
55  }
56
57  virtual bool UnSetVar(const char* variable_name) {
58    ADD_FAILURE();
59    return false;
60  }
61
62 private:
63  std::map<std::string, std::string> variables_;
64
65  DISALLOW_COPY_AND_ASSIGN(MockEnvironment);
66};
67
68}  // namespace
69
70TEST(ShellIntegrationTest, GetDesktopShortcutTemplate) {
71#if defined(GOOGLE_CHROME_BUILD)
72  const char kTemplateFilename[] = "google-chrome.desktop";
73#else  // CHROMIUM_BUILD
74  const char kTemplateFilename[] = "chromium-browser.desktop";
75#endif
76
77  const char kTestData1[] = "a magical testing string";
78  const char kTestData2[] = "a different testing string";
79
80  MessageLoop message_loop;
81  BrowserThread file_thread(BrowserThread::FILE, &message_loop);
82
83  {
84    ScopedTempDir temp_dir;
85    ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
86
87    MockEnvironment env;
88    env.Set("XDG_DATA_HOME", temp_dir.path().value());
89    ASSERT_TRUE(file_util::WriteFile(
90        temp_dir.path().AppendASCII(kTemplateFilename),
91        kTestData1, strlen(kTestData1)));
92    std::string contents;
93    ASSERT_TRUE(ShellIntegration::GetDesktopShortcutTemplate(&env,
94                                                             &contents));
95    EXPECT_EQ(kTestData1, contents);
96  }
97
98  {
99    ScopedTempDir temp_dir;
100    ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
101
102    MockEnvironment env;
103    env.Set("XDG_DATA_DIRS", temp_dir.path().value());
104    ASSERT_TRUE(file_util::CreateDirectory(
105        temp_dir.path().AppendASCII("applications")));
106    ASSERT_TRUE(file_util::WriteFile(
107        temp_dir.path().AppendASCII("applications")
108            .AppendASCII(kTemplateFilename),
109        kTestData2, strlen(kTestData2)));
110    std::string contents;
111    ASSERT_TRUE(ShellIntegration::GetDesktopShortcutTemplate(&env,
112                                                             &contents));
113    EXPECT_EQ(kTestData2, contents);
114  }
115
116  {
117    ScopedTempDir temp_dir;
118    ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
119
120    MockEnvironment env;
121    env.Set("XDG_DATA_DIRS", temp_dir.path().value() + ":" +
122                   temp_dir.path().AppendASCII("applications").value());
123    ASSERT_TRUE(file_util::CreateDirectory(
124        temp_dir.path().AppendASCII("applications")));
125    ASSERT_TRUE(file_util::WriteFile(
126        temp_dir.path().AppendASCII(kTemplateFilename),
127        kTestData1, strlen(kTestData1)));
128    ASSERT_TRUE(file_util::WriteFile(
129        temp_dir.path().AppendASCII("applications")
130            .AppendASCII(kTemplateFilename),
131        kTestData2, strlen(kTestData2)));
132    std::string contents;
133    ASSERT_TRUE(ShellIntegration::GetDesktopShortcutTemplate(&env,
134                                                             &contents));
135    EXPECT_EQ(kTestData1, contents);
136  }
137}
138
139TEST(ShellIntegrationTest, GetDesktopShortcutFilename) {
140  const struct {
141    const FilePath::CharType* path;
142    const char* url;
143  } test_cases[] = {
144    { FPL("http___foo_.desktop"), "http://foo" },
145    { FPL("http___foo_bar_.desktop"), "http://foo/bar/" },
146    { FPL("http___foo_bar_a=b&c=d.desktop"), "http://foo/bar?a=b&c=d" },
147
148    // Now we're starting to be more evil...
149    { FPL("http___foo_.desktop"), "http://foo/bar/baz/../../../../../" },
150    { FPL("http___foo_.desktop"), "http://foo/bar/././../baz/././../" },
151    { FPL("http___.._.desktop"), "http://../../../../" },
152  };
153  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) {
154    EXPECT_EQ(std::string(chrome::kBrowserProcessExecutableName) + "-" +
155              test_cases[i].path,
156              ShellIntegration::GetDesktopShortcutFilename(
157                  GURL(test_cases[i].url)).value()) <<
158        " while testing " << test_cases[i].url;
159  }
160}
161
162TEST(ShellIntegrationTest, GetDesktopFileContents) {
163  const struct {
164    const char* url;
165    const char* title;
166    const char* icon_name;
167    const char* template_contents;
168    const char* expected_output;
169  } test_cases[] = {
170    // Dumb case.
171    { "ignored", "ignored", "ignored", "", "#!/usr/bin/env xdg-open\n" },
172
173    // Real-world case.
174    { "http://gmail.com",
175      "GMail",
176      "chrome-http__gmail.com",
177
178      "[Desktop Entry]\n"
179      "Version=1.0\n"
180      "Encoding=UTF-8\n"
181      "Name=Google Chrome\n"
182      "Comment=The web browser from Google\n"
183      "Exec=/opt/google/chrome/google-chrome %U\n"
184      "Terminal=false\n"
185      "Icon=/opt/google/chrome/product_logo_48.png\n"
186      "Type=Application\n"
187      "Categories=Application;Network;WebBrowser;\n"
188      "MimeType=text/html;text/xml;application/xhtml_xml;\n",
189
190      "#!/usr/bin/env xdg-open\n"
191      "[Desktop Entry]\n"
192      "Version=1.0\n"
193      "Encoding=UTF-8\n"
194      "Name=GMail\n"
195      "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
196      "Terminal=false\n"
197      "Icon=chrome-http__gmail.com\n"
198      "Type=Application\n"
199      "Categories=Application;Network;WebBrowser;\n"
200      "StartupWMClass=gmail.com\n"
201    },
202
203    // Make sure we don't insert duplicate shebangs.
204    { "http://gmail.com",
205      "GMail",
206      "chrome-http__gmail.com",
207
208      "#!/some/shebang\n"
209      "Name=Google Chrome\n"
210      "Exec=/opt/google/chrome/google-chrome %U\n",
211
212      "#!/usr/bin/env xdg-open\n"
213      "Name=GMail\n"
214      "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
215      "StartupWMClass=gmail.com\n"
216    },
217
218    // Make sure i18n-ed comments are removed.
219    { "http://gmail.com",
220      "GMail",
221      "chrome-http__gmail.com",
222
223      "Name=Google Chrome\n"
224      "Exec=/opt/google/chrome/google-chrome %U\n"
225      "Comment[pl]=Jakis komentarz.\n",
226
227      "#!/usr/bin/env xdg-open\n"
228      "Name=GMail\n"
229      "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
230      "StartupWMClass=gmail.com\n"
231    },
232
233    // Make sure that empty icons are replaced by the chrome icon.
234    { "http://gmail.com",
235      "GMail",
236      "",
237
238      "Name=Google Chrome\n"
239      "Exec=/opt/google/chrome/google-chrome %U\n"
240      "Comment[pl]=Jakis komentarz.\n"
241      "Icon=/opt/google/chrome/product_logo_48.png\n",
242
243      "#!/usr/bin/env xdg-open\n"
244      "Name=GMail\n"
245      "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
246      "Icon=/opt/google/chrome/product_logo_48.png\n"
247      "StartupWMClass=gmail.com\n"
248    },
249
250    // Now we're starting to be more evil...
251    { "http://evil.com/evil --join-the-b0tnet",
252      "Ownz0red\nExec=rm -rf /",
253      "chrome-http__evil.com_evil",
254
255      "Name=Google Chrome\n"
256      "Exec=/opt/google/chrome/google-chrome %U\n",
257
258      "#!/usr/bin/env xdg-open\n"
259      "Name=http://evil.com/evil%20--join-the-b0tnet\n"
260      "Exec=/opt/google/chrome/google-chrome "
261      "--app=http://evil.com/evil%20--join-the-b0tnet\n"
262      "StartupWMClass=evil.com__evil%20--join-the-b0tnet\n"
263    },
264    { "http://evil.com/evil; rm -rf /; \"; rm -rf $HOME >ownz0red",
265      "Innocent Title",
266      "chrome-http__evil.com_evil",
267
268      "Name=Google Chrome\n"
269      "Exec=/opt/google/chrome/google-chrome %U\n",
270
271      "#!/usr/bin/env xdg-open\n"
272      "Name=Innocent Title\n"
273      "Exec=/opt/google/chrome/google-chrome "
274      "\"--app=http://evil.com/evil;%20rm%20-rf%20/;%20%22;%20rm%20"
275      // Note: $ is escaped as \$ within an arg to Exec, and then
276      // the \ is escaped as \\ as all strings in a Desktop file should
277      // be; finally, \\ becomes \\\\ when represented in a C++ string!
278      "-rf%20\\\\$HOME%20%3Eownz0red\"\n"
279      "StartupWMClass=evil.com__evil;%20rm%20-rf%20_;%20%22;%20"
280      "rm%20-rf%20$HOME%20%3Eownz0red\n"
281    },
282    { "http://evil.com/evil | cat `echo ownz0red` >/dev/null",
283      "Innocent Title",
284      "chrome-http__evil.com_evil",
285
286      "Name=Google Chrome\n"
287      "Exec=/opt/google/chrome/google-chrome %U\n",
288
289      "#!/usr/bin/env xdg-open\n"
290      "Name=Innocent Title\n"
291      "Exec=/opt/google/chrome/google-chrome "
292      "--app=http://evil.com/evil%20%7C%20cat%20%60echo%20ownz0red"
293      "%60%20%3E/dev/null\n"
294      "StartupWMClass=evil.com__evil%20%7C%20cat%20%60echo%20ownz0red"
295      "%60%20%3E_dev_null\n"
296    },
297  };
298  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) {
299    SCOPED_TRACE(i);
300    EXPECT_EQ(
301        test_cases[i].expected_output,
302        ShellIntegration::GetDesktopFileContents(
303            test_cases[i].template_contents,
304            web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)),
305            GURL(test_cases[i].url),
306            "",
307            ASCIIToUTF16(test_cases[i].title),
308            test_cases[i].icon_name));
309  }
310}
311#elif defined(OS_WIN)
312TEST(ShellIntegrationTest, GetChromiumAppIdTest) {
313  // Empty profile path should get chrome::kBrowserAppID
314  FilePath empty_path;
315  EXPECT_EQ(BrowserDistribution::GetDistribution()->GetBrowserAppId(),
316            ShellIntegration::GetChromiumAppId(empty_path));
317
318  // Default profile path should get chrome::kBrowserAppID
319  FilePath default_user_data_dir;
320  chrome::GetDefaultUserDataDirectory(&default_user_data_dir);
321  FilePath default_profile_path =
322      default_user_data_dir.AppendASCII(chrome::kNotSignedInProfile);
323  EXPECT_EQ(BrowserDistribution::GetDistribution()->GetBrowserAppId(),
324            ShellIntegration::GetChromiumAppId(default_profile_path));
325
326  // Non-default profile path should get chrome::kBrowserAppID joined with
327  // profile info.
328  FilePath profile_path(FILE_PATH_LITERAL("root"));
329  profile_path = profile_path.Append(FILE_PATH_LITERAL("udd"));
330  profile_path = profile_path.Append(FILE_PATH_LITERAL("User Data - Test"));
331  EXPECT_EQ(BrowserDistribution::GetDistribution()->GetBrowserAppId() +
332            L".udd.UserDataTest",
333            ShellIntegration::GetChromiumAppId(profile_path));
334}
335#endif
336