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)
32 namespace {
33
34 // Provides mock environment variables values based on a stored map.
35 class MockEnvironment : public base::Environment {
36 public:
MockEnvironment()37 MockEnvironment() {}
38
Set(const std::string & name,const std::string & value)39 void Set(const std::string& name, const std::string& value) {
40 variables_[name] = value;
41 }
42
GetVar(const char * variable_name,std::string * result)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
SetVar(const char * variable_name,const std::string & new_value)52 virtual bool SetVar(const char* variable_name, const std::string& new_value) {
53 ADD_FAILURE();
54 return false;
55 }
56
UnSetVar(const char * variable_name)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
TEST(ShellIntegrationTest,GetDesktopShortcutTemplate)70 TEST(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
TEST(ShellIntegrationTest,GetDesktopShortcutFilename)139 TEST(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
TEST(ShellIntegrationTest,GetDesktopFileContents)162 TEST(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)
TEST(ShellIntegrationTest,GetChromiumAppIdTest)312 TEST(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