OLD | NEW |
| (Empty) |
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/shell_integration.h" | |
6 | |
7 #include <cstdlib> | |
8 #include <map> | |
9 | |
10 #include "base/file_util.h" | |
11 #include "base/files/file_path.h" | |
12 #include "base/files/scoped_temp_dir.h" | |
13 #include "base/message_loop.h" | |
14 #include "base/stl_util.h" | |
15 #include "base/string_util.h" | |
16 #include "base/utf_string_conversions.h" | |
17 #include "chrome/browser/web_applications/web_app.h" | |
18 #include "chrome/common/chrome_constants.h" | |
19 #include "content/public/test/test_browser_thread.h" | |
20 #include "googleurl/src/gurl.h" | |
21 #include "testing/gtest/include/gtest/gtest.h" | |
22 | |
23 #if defined(OS_POSIX) && !defined(OS_MACOSX) | |
24 #include "base/environment.h" | |
25 #include "chrome/browser/shell_integration_linux.h" | |
26 #endif | |
27 | |
28 #define FPL FILE_PATH_LITERAL | |
29 | |
30 using content::BrowserThread; | |
31 | |
32 #if defined(OS_POSIX) && !defined(OS_MACOSX) | |
33 namespace { | |
34 | |
35 // Provides mock environment variables values based on a stored map. | |
36 class MockEnvironment : public base::Environment { | |
37 public: | |
38 MockEnvironment() {} | |
39 | |
40 void Set(const std::string& name, const std::string& value) { | |
41 variables_[name] = value; | |
42 } | |
43 | |
44 virtual bool GetVar(const char* variable_name, std::string* result) OVERRIDE { | |
45 if (ContainsKey(variables_, variable_name)) { | |
46 *result = variables_[variable_name]; | |
47 return true; | |
48 } | |
49 | |
50 return false; | |
51 } | |
52 | |
53 virtual bool SetVar(const char* variable_name, | |
54 const std::string& new_value) OVERRIDE { | |
55 ADD_FAILURE(); | |
56 return false; | |
57 } | |
58 | |
59 virtual bool UnSetVar(const char* variable_name) OVERRIDE { | |
60 ADD_FAILURE(); | |
61 return false; | |
62 } | |
63 | |
64 private: | |
65 std::map<std::string, std::string> variables_; | |
66 | |
67 DISALLOW_COPY_AND_ASSIGN(MockEnvironment); | |
68 }; | |
69 | |
70 // Allows you to change the real environment, but reverts changes upon | |
71 // destruction. | |
72 class ScopedEnvironment { | |
73 public: | |
74 ScopedEnvironment() {} | |
75 | |
76 ~ScopedEnvironment() { | |
77 for (std::map<std::string, std::string>::const_iterator | |
78 it = old_variables_.begin(); it != old_variables_.end(); ++it) { | |
79 if (it->second.empty()) { | |
80 unsetenv(it->first.c_str()); | |
81 } else { | |
82 setenv(it->first.c_str(), it->second.c_str(), 1); | |
83 } | |
84 } | |
85 } | |
86 | |
87 void Set(const std::string& name, const std::string& value) { | |
88 if (!ContainsKey(old_variables_, name)) { | |
89 const char* value = getenv(name.c_str()); | |
90 if (value != NULL) { | |
91 old_variables_[name] = value; | |
92 } else { | |
93 old_variables_[name] = std::string(); | |
94 } | |
95 } | |
96 setenv(name.c_str(), value.c_str(), 1); | |
97 } | |
98 | |
99 private: | |
100 // Map from name to original value, or the empty string if there was no | |
101 // previous value. | |
102 std::map<std::string, std::string> old_variables_; | |
103 | |
104 DISALLOW_COPY_AND_ASSIGN(ScopedEnvironment); | |
105 }; | |
106 | |
107 } // namespace | |
108 | |
109 TEST(ShellIntegrationTest, GetDesktopShortcutTemplate) { | |
110 #if defined(GOOGLE_CHROME_BUILD) | |
111 const char kTemplateFilename[] = "google-chrome.desktop"; | |
112 #else // CHROMIUM_BUILD | |
113 const char kTemplateFilename[] = "chromium-browser.desktop"; | |
114 #endif | |
115 | |
116 const char kTestData1[] = "a magical testing string"; | |
117 const char kTestData2[] = "a different testing string"; | |
118 | |
119 MessageLoop message_loop; | |
120 content::TestBrowserThread file_thread(BrowserThread::FILE, &message_loop); | |
121 | |
122 { | |
123 base::ScopedTempDir temp_dir; | |
124 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | |
125 | |
126 MockEnvironment env; | |
127 env.Set("XDG_DATA_HOME", temp_dir.path().value()); | |
128 ASSERT_TRUE(file_util::WriteFile( | |
129 temp_dir.path().AppendASCII(kTemplateFilename), | |
130 kTestData1, strlen(kTestData1))); | |
131 std::string contents; | |
132 ASSERT_TRUE(ShellIntegrationLinux::GetDesktopShortcutTemplate(&env, | |
133 &contents)); | |
134 EXPECT_EQ(kTestData1, contents); | |
135 } | |
136 | |
137 { | |
138 base::ScopedTempDir temp_dir; | |
139 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | |
140 | |
141 MockEnvironment env; | |
142 env.Set("XDG_DATA_DIRS", temp_dir.path().value()); | |
143 ASSERT_TRUE(file_util::CreateDirectory( | |
144 temp_dir.path().AppendASCII("applications"))); | |
145 ASSERT_TRUE(file_util::WriteFile( | |
146 temp_dir.path().AppendASCII("applications") | |
147 .AppendASCII(kTemplateFilename), | |
148 kTestData2, strlen(kTestData2))); | |
149 std::string contents; | |
150 ASSERT_TRUE(ShellIntegrationLinux::GetDesktopShortcutTemplate(&env, | |
151 &contents)); | |
152 EXPECT_EQ(kTestData2, contents); | |
153 } | |
154 | |
155 { | |
156 base::ScopedTempDir temp_dir; | |
157 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | |
158 | |
159 MockEnvironment env; | |
160 env.Set("XDG_DATA_DIRS", temp_dir.path().value() + ":" + | |
161 temp_dir.path().AppendASCII("applications").value()); | |
162 ASSERT_TRUE(file_util::CreateDirectory( | |
163 temp_dir.path().AppendASCII("applications"))); | |
164 ASSERT_TRUE(file_util::WriteFile( | |
165 temp_dir.path().AppendASCII(kTemplateFilename), | |
166 kTestData1, strlen(kTestData1))); | |
167 ASSERT_TRUE(file_util::WriteFile( | |
168 temp_dir.path().AppendASCII("applications") | |
169 .AppendASCII(kTemplateFilename), | |
170 kTestData2, strlen(kTestData2))); | |
171 std::string contents; | |
172 ASSERT_TRUE(ShellIntegrationLinux::GetDesktopShortcutTemplate(&env, | |
173 &contents)); | |
174 EXPECT_EQ(kTestData1, contents); | |
175 } | |
176 } | |
177 | |
178 TEST(ShellIntegrationTest, GetWebShortcutFilename) { | |
179 const struct { | |
180 const base::FilePath::CharType* path; | |
181 const char* url; | |
182 } test_cases[] = { | |
183 { FPL("http___foo_.desktop"), "http://foo" }, | |
184 { FPL("http___foo_bar_.desktop"), "http://foo/bar/" }, | |
185 { FPL("http___foo_bar_a=b&c=d.desktop"), "http://foo/bar?a=b&c=d" }, | |
186 | |
187 // Now we're starting to be more evil... | |
188 { FPL("http___foo_.desktop"), "http://foo/bar/baz/../../../../../" }, | |
189 { FPL("http___foo_.desktop"), "http://foo/bar/././../baz/././../" }, | |
190 { FPL("http___.._.desktop"), "http://../../../../" }, | |
191 }; | |
192 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) { | |
193 EXPECT_EQ(std::string(chrome::kBrowserProcessExecutableName) + "-" + | |
194 test_cases[i].path, | |
195 ShellIntegrationLinux::GetWebShortcutFilename( | |
196 GURL(test_cases[i].url)).value()) << | |
197 " while testing " << test_cases[i].url; | |
198 } | |
199 } | |
200 | |
201 TEST(ShellIntegrationTest, GetDesktopFileContents) { | |
202 const struct { | |
203 const char* url; | |
204 const char* title; | |
205 const char* icon_name; | |
206 const char* template_contents; | |
207 const char* expected_output; | |
208 } test_cases[] = { | |
209 // Dumb case. | |
210 { "ignored", "ignored", "ignored", "", "#!/usr/bin/env xdg-open\n" }, | |
211 | |
212 // Real-world case. | |
213 { "http://gmail.com", | |
214 "GMail", | |
215 "chrome-http__gmail.com", | |
216 | |
217 "[Desktop Entry]\n" | |
218 "Version=1.0\n" | |
219 "Encoding=UTF-8\n" | |
220 "Name=Google Chrome\n" | |
221 "GenericName=Web Browser\n" | |
222 "Comment=The web browser from Google\n" | |
223 "Exec=/opt/google/chrome/google-chrome %U\n" | |
224 "Terminal=false\n" | |
225 "Icon=/opt/google/chrome/product_logo_48.png\n" | |
226 "Type=Application\n" | |
227 "Categories=Application;Network;WebBrowser;\n" | |
228 "MimeType=text/html;text/xml;application/xhtml_xml;\n" | |
229 "X-Ayatana-Desktop-Shortcuts=NewWindow;\n" | |
230 "\n" | |
231 "[NewWindow Shortcut Group]\n" | |
232 "Name=Open New Window\n" | |
233 "Exec=/opt/google/chrome/google-chrome\n" | |
234 "TargetEnvironment=Unity\n", | |
235 | |
236 "#!/usr/bin/env xdg-open\n" | |
237 "[Desktop Entry]\n" | |
238 "Version=1.0\n" | |
239 "Encoding=UTF-8\n" | |
240 "Name=GMail\n" | |
241 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n" | |
242 "Terminal=false\n" | |
243 "Icon=chrome-http__gmail.com\n" | |
244 "Type=Application\n" | |
245 "Categories=Application;Network;WebBrowser;\n" | |
246 #if !defined(USE_AURA) | |
247 // Aura Chrome does not (yet) set WMClass, so we only expect | |
248 // StartupWMClass on non-Aura builds. | |
249 "StartupWMClass=gmail.com\n" | |
250 #endif | |
251 }, | |
252 | |
253 // Make sure we don't insert duplicate shebangs. | |
254 { "http://gmail.com", | |
255 "GMail", | |
256 "chrome-http__gmail.com", | |
257 | |
258 "#!/some/shebang\n" | |
259 "[Desktop Entry]\n" | |
260 "Name=Google Chrome\n" | |
261 "Exec=/opt/google/chrome/google-chrome %U\n", | |
262 | |
263 "#!/usr/bin/env xdg-open\n" | |
264 "[Desktop Entry]\n" | |
265 "Name=GMail\n" | |
266 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n" | |
267 "Icon=chrome-http__gmail.com\n" | |
268 #if !defined(USE_AURA) | |
269 // Aura Chrome does not (yet) set WMClass, so we only expect | |
270 // StartupWMClass on non-Aura builds. | |
271 "StartupWMClass=gmail.com\n" | |
272 #endif | |
273 }, | |
274 | |
275 // Make sure i18n-ed names and other fields are removed. | |
276 { "http://gmail.com", | |
277 "GMail", | |
278 "chrome-http__gmail.com", | |
279 | |
280 "[Desktop Entry]\n" | |
281 "Name=Google Chrome\n" | |
282 "Name[en_AU]=Google Chrome\n" | |
283 "Name[pl]=Google Chrome\n" | |
284 "GenericName=Web Browser\n" | |
285 "GenericName[en_AU]=Web Browser\n" | |
286 "GenericName[pl]=Navegador Web\n" | |
287 "Exec=/opt/google/chrome/google-chrome %U\n" | |
288 "Comment[en_AU]=Some comment.\n" | |
289 "Comment[pl]=Jakis komentarz.\n", | |
290 | |
291 "#!/usr/bin/env xdg-open\n" | |
292 "[Desktop Entry]\n" | |
293 "Name=GMail\n" | |
294 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n" | |
295 "Icon=chrome-http__gmail.com\n" | |
296 #if !defined(USE_AURA) | |
297 // Aura Chrome does not (yet) set WMClass, so we only expect | |
298 // StartupWMClass on non-Aura builds. | |
299 "StartupWMClass=gmail.com\n" | |
300 #endif | |
301 }, | |
302 | |
303 // Make sure that empty icons are replaced by the chrome icon. | |
304 { "http://gmail.com", | |
305 "GMail", | |
306 "", | |
307 | |
308 "[Desktop Entry]\n" | |
309 "Name=Google Chrome\n" | |
310 "Exec=/opt/google/chrome/google-chrome %U\n" | |
311 "Comment[pl]=Jakis komentarz.\n" | |
312 "Icon=/opt/google/chrome/product_logo_48.png\n", | |
313 | |
314 "#!/usr/bin/env xdg-open\n" | |
315 "[Desktop Entry]\n" | |
316 "Name=GMail\n" | |
317 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n" | |
318 "Icon=/opt/google/chrome/product_logo_48.png\n" | |
319 #if !defined(USE_AURA) | |
320 // Aura Chrome does not (yet) set WMClass, so we only expect | |
321 // StartupWMClass on non-Aura builds. | |
322 "StartupWMClass=gmail.com\n" | |
323 #endif | |
324 }, | |
325 | |
326 // Now we're starting to be more evil... | |
327 { "http://evil.com/evil --join-the-b0tnet", | |
328 "Ownz0red\nExec=rm -rf /", | |
329 "chrome-http__evil.com_evil", | |
330 | |
331 "[Desktop Entry]\n" | |
332 "Name=Google Chrome\n" | |
333 "Exec=/opt/google/chrome/google-chrome %U\n", | |
334 | |
335 "#!/usr/bin/env xdg-open\n" | |
336 "[Desktop Entry]\n" | |
337 "Name=http://evil.com/evil%20--join-the-b0tnet\n" | |
338 "Exec=/opt/google/chrome/google-chrome " | |
339 "--app=http://evil.com/evil%20--join-the-b0tnet\n" | |
340 "Icon=chrome-http__evil.com_evil\n" | |
341 #if !defined(USE_AURA) | |
342 // Aura Chrome does not (yet) set WMClass, so we only expect | |
343 // StartupWMClass on non-Aura builds. | |
344 "StartupWMClass=evil.com__evil%20--join-the-b0tnet\n" | |
345 #endif | |
346 }, | |
347 { "http://evil.com/evil; rm -rf /; \"; rm -rf $HOME >ownz0red", | |
348 "Innocent Title", | |
349 "chrome-http__evil.com_evil", | |
350 | |
351 "[Desktop Entry]\n" | |
352 "Name=Google Chrome\n" | |
353 "Exec=/opt/google/chrome/google-chrome %U\n", | |
354 | |
355 "#!/usr/bin/env xdg-open\n" | |
356 "[Desktop Entry]\n" | |
357 "Name=Innocent Title\n" | |
358 "Exec=/opt/google/chrome/google-chrome " | |
359 "\"--app=http://evil.com/evil;%20rm%20-rf%20/;%20%22;%20rm%20" | |
360 // Note: $ is escaped as \$ within an arg to Exec, and then | |
361 // the \ is escaped as \\ as all strings in a Desktop file should | |
362 // be; finally, \\ becomes \\\\ when represented in a C++ string! | |
363 "-rf%20\\\\$HOME%20%3Eownz0red\"\n" | |
364 "Icon=chrome-http__evil.com_evil\n" | |
365 #if !defined(USE_AURA) | |
366 // Aura Chrome does not (yet) set WMClass, so we only expect | |
367 // StartupWMClass on non-Aura builds. | |
368 "StartupWMClass=evil.com__evil;%20rm%20-rf%20_;%20%22;%20" | |
369 "rm%20-rf%20$HOME%20%3Eownz0red\n" | |
370 #endif | |
371 }, | |
372 { "http://evil.com/evil | cat `echo ownz0red` >/dev/null", | |
373 "Innocent Title", | |
374 "chrome-http__evil.com_evil", | |
375 | |
376 "[Desktop Entry]\n" | |
377 "Name=Google Chrome\n" | |
378 "Exec=/opt/google/chrome/google-chrome %U\n", | |
379 | |
380 "#!/usr/bin/env xdg-open\n" | |
381 "[Desktop Entry]\n" | |
382 "Name=Innocent Title\n" | |
383 "Exec=/opt/google/chrome/google-chrome " | |
384 "--app=http://evil.com/evil%20%7C%20cat%20%60echo%20ownz0red" | |
385 "%60%20%3E/dev/null\n" | |
386 "Icon=chrome-http__evil.com_evil\n" | |
387 #if !defined(USE_AURA) | |
388 // Aura Chrome does not (yet) set WMClass, so we only expect | |
389 // StartupWMClass on non-Aura builds. | |
390 "StartupWMClass=evil.com__evil%20%7C%20cat%20%60echo%20ownz0red" | |
391 "%60%20%3E_dev_null\n" | |
392 #endif | |
393 }, | |
394 }; | |
395 | |
396 // Set the language to en_AU. This causes glib to copy the en_AU localized | |
397 // strings into the shortcut file. (We want to test that they are removed.) | |
398 ScopedEnvironment env; | |
399 env.Set("LC_ALL", "en_AU.UTF-8"); | |
400 env.Set("LANGUAGE", "en_AU.UTF-8"); | |
401 | |
402 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) { | |
403 SCOPED_TRACE(i); | |
404 EXPECT_EQ( | |
405 test_cases[i].expected_output, | |
406 ShellIntegrationLinux::GetDesktopFileContents( | |
407 test_cases[i].template_contents, | |
408 web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)), | |
409 GURL(test_cases[i].url), | |
410 "", | |
411 base::FilePath(), | |
412 ASCIIToUTF16(test_cases[i].title), | |
413 test_cases[i].icon_name, | |
414 base::FilePath())); | |
415 } | |
416 } | |
417 #endif | |
OLD | NEW |