| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/shell_integration.h" |
| |
| #include <map> |
| |
| #include "base/file_path.h" |
| #include "base/file_util.h" |
| #include "base/memory/scoped_temp_dir.h" |
| #include "base/message_loop.h" |
| #include "base/stl_util-inl.h" |
| #include "base/string_util.h" |
| #include "base/utf_string_conversions.h" |
| #include "chrome/browser/web_applications/web_app.h" |
| #include "chrome/common/chrome_constants.h" |
| #include "chrome/common/chrome_paths_internal.h" |
| #include "content/browser/browser_thread.h" |
| #include "googleurl/src/gurl.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| #if defined(OS_WIN) |
| #include "chrome/installer/util/browser_distribution.h" |
| #elif defined(OS_LINUX) |
| #include "base/environment.h" |
| #endif // defined(OS_LINUX) |
| |
| #define FPL FILE_PATH_LITERAL |
| |
| #if defined(OS_LINUX) |
| namespace { |
| |
| // Provides mock environment variables values based on a stored map. |
| class MockEnvironment : public base::Environment { |
| public: |
| MockEnvironment() {} |
| |
| void Set(const std::string& name, const std::string& value) { |
| variables_[name] = value; |
| } |
| |
| virtual bool GetVar(const char* variable_name, std::string* result) { |
| if (ContainsKey(variables_, variable_name)) { |
| *result = variables_[variable_name]; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| virtual bool SetVar(const char* variable_name, const std::string& new_value) { |
| ADD_FAILURE(); |
| return false; |
| } |
| |
| virtual bool UnSetVar(const char* variable_name) { |
| ADD_FAILURE(); |
| return false; |
| } |
| |
| private: |
| std::map<std::string, std::string> variables_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockEnvironment); |
| }; |
| |
| } // namespace |
| |
| TEST(ShellIntegrationTest, GetDesktopShortcutTemplate) { |
| #if defined(GOOGLE_CHROME_BUILD) |
| const char kTemplateFilename[] = "google-chrome.desktop"; |
| #else // CHROMIUM_BUILD |
| const char kTemplateFilename[] = "chromium-browser.desktop"; |
| #endif |
| |
| const char kTestData1[] = "a magical testing string"; |
| const char kTestData2[] = "a different testing string"; |
| |
| MessageLoop message_loop; |
| BrowserThread file_thread(BrowserThread::FILE, &message_loop); |
| |
| { |
| ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| |
| MockEnvironment env; |
| env.Set("XDG_DATA_HOME", temp_dir.path().value()); |
| ASSERT_TRUE(file_util::WriteFile( |
| temp_dir.path().AppendASCII(kTemplateFilename), |
| kTestData1, strlen(kTestData1))); |
| std::string contents; |
| ASSERT_TRUE(ShellIntegration::GetDesktopShortcutTemplate(&env, |
| &contents)); |
| EXPECT_EQ(kTestData1, contents); |
| } |
| |
| { |
| ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| |
| MockEnvironment env; |
| env.Set("XDG_DATA_DIRS", temp_dir.path().value()); |
| ASSERT_TRUE(file_util::CreateDirectory( |
| temp_dir.path().AppendASCII("applications"))); |
| ASSERT_TRUE(file_util::WriteFile( |
| temp_dir.path().AppendASCII("applications") |
| .AppendASCII(kTemplateFilename), |
| kTestData2, strlen(kTestData2))); |
| std::string contents; |
| ASSERT_TRUE(ShellIntegration::GetDesktopShortcutTemplate(&env, |
| &contents)); |
| EXPECT_EQ(kTestData2, contents); |
| } |
| |
| { |
| ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| |
| MockEnvironment env; |
| env.Set("XDG_DATA_DIRS", temp_dir.path().value() + ":" + |
| temp_dir.path().AppendASCII("applications").value()); |
| ASSERT_TRUE(file_util::CreateDirectory( |
| temp_dir.path().AppendASCII("applications"))); |
| ASSERT_TRUE(file_util::WriteFile( |
| temp_dir.path().AppendASCII(kTemplateFilename), |
| kTestData1, strlen(kTestData1))); |
| ASSERT_TRUE(file_util::WriteFile( |
| temp_dir.path().AppendASCII("applications") |
| .AppendASCII(kTemplateFilename), |
| kTestData2, strlen(kTestData2))); |
| std::string contents; |
| ASSERT_TRUE(ShellIntegration::GetDesktopShortcutTemplate(&env, |
| &contents)); |
| EXPECT_EQ(kTestData1, contents); |
| } |
| } |
| |
| TEST(ShellIntegrationTest, GetDesktopShortcutFilename) { |
| const struct { |
| const FilePath::CharType* path; |
| const char* url; |
| } test_cases[] = { |
| { FPL("http___foo_.desktop"), "http://foo" }, |
| { FPL("http___foo_bar_.desktop"), "http://foo/bar/" }, |
| { FPL("http___foo_bar_a=b&c=d.desktop"), "http://foo/bar?a=b&c=d" }, |
| |
| // Now we're starting to be more evil... |
| { FPL("http___foo_.desktop"), "http://foo/bar/baz/../../../../../" }, |
| { FPL("http___foo_.desktop"), "http://foo/bar/././../baz/././../" }, |
| { FPL("http___.._.desktop"), "http://../../../../" }, |
| }; |
| for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) { |
| EXPECT_EQ(std::string(chrome::kBrowserProcessExecutableName) + "-" + |
| test_cases[i].path, |
| ShellIntegration::GetDesktopShortcutFilename( |
| GURL(test_cases[i].url)).value()) << |
| " while testing " << test_cases[i].url; |
| } |
| } |
| |
| TEST(ShellIntegrationTest, GetDesktopFileContents) { |
| const struct { |
| const char* url; |
| const char* title; |
| const char* icon_name; |
| const char* template_contents; |
| const char* expected_output; |
| } test_cases[] = { |
| // Dumb case. |
| { "ignored", "ignored", "ignored", "", "#!/usr/bin/env xdg-open\n" }, |
| |
| // Real-world case. |
| { "http://gmail.com", |
| "GMail", |
| "chrome-http__gmail.com", |
| |
| "[Desktop Entry]\n" |
| "Version=1.0\n" |
| "Encoding=UTF-8\n" |
| "Name=Google Chrome\n" |
| "Comment=The web browser from Google\n" |
| "Exec=/opt/google/chrome/google-chrome %U\n" |
| "Terminal=false\n" |
| "Icon=/opt/google/chrome/product_logo_48.png\n" |
| "Type=Application\n" |
| "Categories=Application;Network;WebBrowser;\n" |
| "MimeType=text/html;text/xml;application/xhtml_xml;\n", |
| |
| "#!/usr/bin/env xdg-open\n" |
| "[Desktop Entry]\n" |
| "Version=1.0\n" |
| "Encoding=UTF-8\n" |
| "Name=GMail\n" |
| "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n" |
| "Terminal=false\n" |
| "Icon=chrome-http__gmail.com\n" |
| "Type=Application\n" |
| "Categories=Application;Network;WebBrowser;\n" |
| "StartupWMClass=gmail.com\n" |
| }, |
| |
| // Make sure we don't insert duplicate shebangs. |
| { "http://gmail.com", |
| "GMail", |
| "chrome-http__gmail.com", |
| |
| "#!/some/shebang\n" |
| "Name=Google Chrome\n" |
| "Exec=/opt/google/chrome/google-chrome %U\n", |
| |
| "#!/usr/bin/env xdg-open\n" |
| "Name=GMail\n" |
| "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n" |
| "StartupWMClass=gmail.com\n" |
| }, |
| |
| // Make sure i18n-ed comments are removed. |
| { "http://gmail.com", |
| "GMail", |
| "chrome-http__gmail.com", |
| |
| "Name=Google Chrome\n" |
| "Exec=/opt/google/chrome/google-chrome %U\n" |
| "Comment[pl]=Jakis komentarz.\n", |
| |
| "#!/usr/bin/env xdg-open\n" |
| "Name=GMail\n" |
| "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n" |
| "StartupWMClass=gmail.com\n" |
| }, |
| |
| // Make sure that empty icons are replaced by the chrome icon. |
| { "http://gmail.com", |
| "GMail", |
| "", |
| |
| "Name=Google Chrome\n" |
| "Exec=/opt/google/chrome/google-chrome %U\n" |
| "Comment[pl]=Jakis komentarz.\n" |
| "Icon=/opt/google/chrome/product_logo_48.png\n", |
| |
| "#!/usr/bin/env xdg-open\n" |
| "Name=GMail\n" |
| "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n" |
| "Icon=/opt/google/chrome/product_logo_48.png\n" |
| "StartupWMClass=gmail.com\n" |
| }, |
| |
| // Now we're starting to be more evil... |
| { "http://evil.com/evil --join-the-b0tnet", |
| "Ownz0red\nExec=rm -rf /", |
| "chrome-http__evil.com_evil", |
| |
| "Name=Google Chrome\n" |
| "Exec=/opt/google/chrome/google-chrome %U\n", |
| |
| "#!/usr/bin/env xdg-open\n" |
| "Name=http://evil.com/evil%20--join-the-b0tnet\n" |
| "Exec=/opt/google/chrome/google-chrome " |
| "--app=http://evil.com/evil%20--join-the-b0tnet\n" |
| "StartupWMClass=evil.com__evil%20--join-the-b0tnet\n" |
| }, |
| { "http://evil.com/evil; rm -rf /; \"; rm -rf $HOME >ownz0red", |
| "Innocent Title", |
| "chrome-http__evil.com_evil", |
| |
| "Name=Google Chrome\n" |
| "Exec=/opt/google/chrome/google-chrome %U\n", |
| |
| "#!/usr/bin/env xdg-open\n" |
| "Name=Innocent Title\n" |
| "Exec=/opt/google/chrome/google-chrome " |
| "\"--app=http://evil.com/evil;%20rm%20-rf%20/;%20%22;%20rm%20" |
| // Note: $ is escaped as \$ within an arg to Exec, and then |
| // the \ is escaped as \\ as all strings in a Desktop file should |
| // be; finally, \\ becomes \\\\ when represented in a C++ string! |
| "-rf%20\\\\$HOME%20%3Eownz0red\"\n" |
| "StartupWMClass=evil.com__evil;%20rm%20-rf%20_;%20%22;%20" |
| "rm%20-rf%20$HOME%20%3Eownz0red\n" |
| }, |
| { "http://evil.com/evil | cat `echo ownz0red` >/dev/null", |
| "Innocent Title", |
| "chrome-http__evil.com_evil", |
| |
| "Name=Google Chrome\n" |
| "Exec=/opt/google/chrome/google-chrome %U\n", |
| |
| "#!/usr/bin/env xdg-open\n" |
| "Name=Innocent Title\n" |
| "Exec=/opt/google/chrome/google-chrome " |
| "--app=http://evil.com/evil%20%7C%20cat%20%60echo%20ownz0red" |
| "%60%20%3E/dev/null\n" |
| "StartupWMClass=evil.com__evil%20%7C%20cat%20%60echo%20ownz0red" |
| "%60%20%3E_dev_null\n" |
| }, |
| }; |
| for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) { |
| SCOPED_TRACE(i); |
| EXPECT_EQ( |
| test_cases[i].expected_output, |
| ShellIntegration::GetDesktopFileContents( |
| test_cases[i].template_contents, |
| web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)), |
| GURL(test_cases[i].url), |
| "", |
| ASCIIToUTF16(test_cases[i].title), |
| test_cases[i].icon_name)); |
| } |
| } |
| #elif defined(OS_WIN) |
| TEST(ShellIntegrationTest, GetChromiumAppIdTest) { |
| // Empty profile path should get chrome::kBrowserAppID |
| FilePath empty_path; |
| EXPECT_EQ(BrowserDistribution::GetDistribution()->GetBrowserAppId(), |
| ShellIntegration::GetChromiumAppId(empty_path)); |
| |
| // Default profile path should get chrome::kBrowserAppID |
| FilePath default_user_data_dir; |
| chrome::GetDefaultUserDataDirectory(&default_user_data_dir); |
| FilePath default_profile_path = |
| default_user_data_dir.AppendASCII(chrome::kNotSignedInProfile); |
| EXPECT_EQ(BrowserDistribution::GetDistribution()->GetBrowserAppId(), |
| ShellIntegration::GetChromiumAppId(default_profile_path)); |
| |
| // Non-default profile path should get chrome::kBrowserAppID joined with |
| // profile info. |
| FilePath profile_path(FILE_PATH_LITERAL("root")); |
| profile_path = profile_path.Append(FILE_PATH_LITERAL("udd")); |
| profile_path = profile_path.Append(FILE_PATH_LITERAL("User Data - Test")); |
| EXPECT_EQ(BrowserDistribution::GetDistribution()->GetBrowserAppId() + |
| L".udd.UserDataTest", |
| ShellIntegration::GetChromiumAppId(profile_path)); |
| } |
| #endif |