blob: ae4a4ea701eab8a5cc75a7a91e01922ba5ca7a0f [file] [log] [blame]
[email protected]e7f79872013-09-13 01:39:311// Copyright 2013 The Chromium Authors. All rights reserved.
[email protected]b96aa932009-08-12 21:34:492// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
[email protected]e7f79872013-09-13 01:39:315#include "chrome/browser/shell_integration_linux.h"
[email protected]b96aa932009-08-12 21:34:496
avi664c07b2015-12-26 02:18:317#include <stddef.h>
8
[email protected]111f0282013-08-05 10:09:299#include <algorithm>
[email protected]07753f0f2013-02-05 07:52:5910#include <cstdlib>
[email protected]af71d642010-03-12 10:29:0411#include <map>
thestigd0b662342015-01-16 08:21:1912#include <vector>
[email protected]af71d642010-03-12 10:29:0413
[email protected]14fbaed2013-05-02 07:54:0214#include "base/base_paths.h"
[email protected]2164e512014-01-22 09:32:1015#include "base/command_line.h"
Jan Wilken Dörriebf97d442020-12-09 19:01:5816#include "base/containers/contains.h"
[email protected]e7f79872013-09-13 01:39:3117#include "base/environment.h"
[email protected]57999812013-02-24 05:40:5218#include "base/files/file_path.h"
thestig18dfb7a52014-08-26 10:44:0419#include "base/files/file_util.h"
[email protected]ea1a3f62012-11-16 20:34:2320#include "base/files/scoped_temp_dir.h"
Peter Kasting9eb10b02021-04-29 03:10:4821#include "base/strings/string_piece.h"
[email protected]24a555b62013-06-10 22:01:1722#include "base/strings/string_util.h"
[email protected]e309f312013-06-07 21:50:0823#include "base/strings/utf_string_conversions.h"
Mandy Chen0cd67e02021-09-09 20:22:1224#include "base/test/scoped_feature_list.h"
[email protected]14fbaed2013-05-02 07:54:0225#include "base/test/scoped_path_override.h"
Nico Webereaa08412019-08-14 01:24:3726#include "build/branding_buildflags.h"
Song Fangzhen49468b22021-09-02 15:41:5927#include "chrome/browser/web_applications/web_app_helpers.h"
Song Fangzhencda4af62021-09-09 05:24:0228#include "chrome/browser/web_applications/web_app_id.h"
29#include "chrome/browser/web_applications/web_app_shortcut.h"
[email protected]42896802009-08-28 23:39:4430#include "chrome/common/chrome_constants.h"
Mandy Chen0cd67e02021-09-09 20:22:1231#include "chrome/common/chrome_features.h"
Robert Woods954a2182020-03-20 06:08:2732#include "components/services/app_service/public/cpp/file_handler.h"
Gabriel Charettec7108742019-08-23 03:31:4033#include "content/public/test/browser_task_environment.h"
[email protected]111f0282013-08-05 10:09:2934#include "testing/gmock/include/gmock/gmock.h"
[email protected]b96aa932009-08-12 21:34:4935#include "testing/gtest/include/gtest/gtest.h"
[email protected]761fa4702013-07-02 15:25:1536#include "url/gurl.h"
[email protected]b96aa932009-08-12 21:34:4937
Alexander Dunaev66b50ba2021-08-10 04:11:4638#if defined(USE_OZONE)
39#include "ui/base/ui_base_features.h"
40#include "ui/ozone/public/ozone_platform.h"
41#endif
42
[email protected]111f0282013-08-05 10:09:2943using ::testing::ElementsAre;
[email protected]631bb742011-11-02 11:29:3944
[email protected]06bfeb12014-05-27 14:00:0945namespace shell_integration_linux {
46
[email protected]af71d642010-03-12 10:29:0447namespace {
48
49// Provides mock environment variables values based on a stored map.
[email protected]76b90d312010-08-03 03:00:5050class MockEnvironment : public base::Environment {
[email protected]af71d642010-03-12 10:29:0451 public:
[email protected]76b90d312010-08-03 03:00:5052 MockEnvironment() {}
[email protected]af71d642010-03-12 10:29:0453
thestig0c412e852016-06-30 08:04:4054 void Set(base::StringPiece name, const std::string& value) {
Peter Kasting9eb10b02021-04-29 03:10:4855 variables_[std::string(name)] = value;
[email protected]af71d642010-03-12 10:29:0456 }
57
thestig0c412e852016-06-30 08:04:4058 bool GetVar(base::StringPiece variable_name, std::string* result) override {
Peter Kasting9eb10b02021-04-29 03:10:4859 if (base::Contains(variables_, std::string(variable_name))) {
60 *result = variables_[std::string(variable_name)];
[email protected]af71d642010-03-12 10:29:0461 return true;
62 }
63
64 return false;
65 }
66
thestig0c412e852016-06-30 08:04:4067 bool SetVar(base::StringPiece variable_name,
dchenge1bc7982014-10-30 00:32:4068 const std::string& new_value) override {
[email protected]fc586c72010-07-31 16:55:4069 ADD_FAILURE();
70 return false;
71 }
72
thestig0c412e852016-06-30 08:04:4073 bool UnSetVar(base::StringPiece variable_name) override {
[email protected]fc586c72010-07-31 16:55:4074 ADD_FAILURE();
[email protected]e9032c62010-07-16 03:34:2575 return false;
[email protected]ac7264c2010-07-08 13:32:5176 }
77
[email protected]af71d642010-03-12 10:29:0478 private:
79 std::map<std::string, std::string> variables_;
80
[email protected]76b90d312010-08-03 03:00:5081 DISALLOW_COPY_AND_ASSIGN(MockEnvironment);
[email protected]af71d642010-03-12 10:29:0482};
83
thestigd0b662342015-01-16 08:21:1984// This helps EXPECT_THAT(..., ElementsAre(...)) print out more meaningful
85// failure messages.
86std::vector<std::string> FilePathsToStrings(
87 const std::vector<base::FilePath>& paths) {
88 std::vector<std::string> values;
89 for (const auto& path : paths)
90 values.push_back(path.value());
91 return values;
92}
93
[email protected]af71d642010-03-12 10:29:0494} // namespace
95
[email protected]111f0282013-08-05 10:09:2996TEST(ShellIntegrationTest, GetDataWriteLocation) {
Gabriel Charette798fde72019-08-20 22:24:0497 content::BrowserTaskEnvironment task_environment;
[email protected]111f0282013-08-05 10:09:2998
99 // Test that it returns $XDG_DATA_HOME.
100 {
101 MockEnvironment env;
thestigd2b1fcf2015-01-21 22:11:49102 base::ScopedPathOverride home_override(base::DIR_HOME,
103 base::FilePath("/home/user"),
104 true /* absolute? */,
105 false /* create? */);
[email protected]111f0282013-08-05 10:09:29106 env.Set("XDG_DATA_HOME", "/user/path");
thestigd2b1fcf2015-01-21 22:11:49107 base::FilePath path = GetDataWriteLocation(&env);
thestigd0b662342015-01-16 08:21:19108 EXPECT_EQ("/user/path", path.value());
[email protected]111f0282013-08-05 10:09:29109 }
110
111 // Test that $XDG_DATA_HOME falls back to $HOME/.local/share.
112 {
113 MockEnvironment env;
thestigd2b1fcf2015-01-21 22:11:49114 base::ScopedPathOverride home_override(base::DIR_HOME,
115 base::FilePath("/home/user"),
116 true /* absolute? */,
117 false /* create? */);
118 base::FilePath path = GetDataWriteLocation(&env);
thestigd0b662342015-01-16 08:21:19119 EXPECT_EQ("/home/user/.local/share", path.value());
[email protected]111f0282013-08-05 10:09:29120 }
[email protected]111f0282013-08-05 10:09:29121}
122
123TEST(ShellIntegrationTest, GetDataSearchLocations) {
Gabriel Charette798fde72019-08-20 22:24:04124 content::BrowserTaskEnvironment task_environment;
[email protected]111f0282013-08-05 10:09:29125
126 // Test that it returns $XDG_DATA_HOME + $XDG_DATA_DIRS.
127 {
128 MockEnvironment env;
thestigd2b1fcf2015-01-21 22:11:49129 base::ScopedPathOverride home_override(base::DIR_HOME,
130 base::FilePath("/home/user"),
131 true /* absolute? */,
132 false /* create? */);
[email protected]111f0282013-08-05 10:09:29133 env.Set("XDG_DATA_HOME", "/user/path");
134 env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2");
135 EXPECT_THAT(
thestigd0b662342015-01-16 08:21:19136 FilePathsToStrings(GetDataSearchLocations(&env)),
137 ElementsAre("/user/path",
138 "/system/path/1",
139 "/system/path/2"));
[email protected]111f0282013-08-05 10:09:29140 }
141
142 // Test that $XDG_DATA_HOME falls back to $HOME/.local/share.
143 {
144 MockEnvironment env;
thestigd2b1fcf2015-01-21 22:11:49145 base::ScopedPathOverride home_override(base::DIR_HOME,
146 base::FilePath("/home/user"),
147 true /* absolute? */,
148 false /* create? */);
[email protected]111f0282013-08-05 10:09:29149 env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2");
150 EXPECT_THAT(
thestigd0b662342015-01-16 08:21:19151 FilePathsToStrings(GetDataSearchLocations(&env)),
152 ElementsAre("/home/user/.local/share",
153 "/system/path/1",
154 "/system/path/2"));
[email protected]111f0282013-08-05 10:09:29155 }
156
157 // Test that if neither $XDG_DATA_HOME nor $HOME are specified, it still
158 // succeeds.
159 {
160 MockEnvironment env;
161 env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2");
thestigd2b1fcf2015-01-21 22:11:49162 std::vector<std::string> results =
163 FilePathsToStrings(GetDataSearchLocations(&env));
164 ASSERT_EQ(3U, results.size());
165 EXPECT_FALSE(results[0].empty());
166 EXPECT_EQ("/system/path/1", results[1]);
167 EXPECT_EQ("/system/path/2", results[2]);
[email protected]111f0282013-08-05 10:09:29168 }
169
170 // Test that $XDG_DATA_DIRS falls back to the two default paths.
171 {
172 MockEnvironment env;
thestigd2b1fcf2015-01-21 22:11:49173 base::ScopedPathOverride home_override(base::DIR_HOME,
174 base::FilePath("/home/user"),
175 true /* absolute? */,
176 false /* create? */);
[email protected]111f0282013-08-05 10:09:29177 env.Set("XDG_DATA_HOME", "/user/path");
178 EXPECT_THAT(
thestigd0b662342015-01-16 08:21:19179 FilePathsToStrings(GetDataSearchLocations(&env)),
180 ElementsAre("/user/path",
181 "/usr/local/share",
182 "/usr/share"));
[email protected]111f0282013-08-05 10:09:29183 }
184}
185
[email protected]d81a63c02013-03-07 08:49:04186TEST(ShellIntegrationTest, GetExistingShortcutContents) {
187 const char kTemplateFilename[] = "shortcut-test.desktop";
188 base::FilePath kTemplateFilepath(kTemplateFilename);
[email protected]af71d642010-03-12 10:29:04189 const char kTestData1[] = "a magical testing string";
190 const char kTestData2[] = "a different testing string";
191
Gabriel Charette798fde72019-08-20 22:24:04192 content::BrowserTaskEnvironment task_environment;
[email protected]af71d642010-03-12 10:29:04193
[email protected]58bf9252013-03-06 04:12:36194 // Test that it searches $XDG_DATA_HOME/applications.
[email protected]af71d642010-03-12 10:29:04195 {
[email protected]ea1a3f62012-11-16 20:34:23196 base::ScopedTempDir temp_dir;
[email protected]af71d642010-03-12 10:29:04197 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
198
[email protected]76b90d312010-08-03 03:00:50199 MockEnvironment env;
vabr8c498ea42016-09-15 12:41:58200 env.Set("XDG_DATA_HOME", temp_dir.GetPath().value());
[email protected]58bf9252013-03-06 04:12:36201 // Create a file in a non-applications directory. This should be ignored.
Lei Zhangeebbef62020-05-05 20:16:05202 ASSERT_TRUE(base::WriteFile(temp_dir.GetPath().Append(kTemplateFilename),
203 kTestData2));
vabr8c498ea42016-09-15 12:41:58204 ASSERT_TRUE(
205 base::CreateDirectory(temp_dir.GetPath().Append("applications")));
Lei Zhangeebbef62020-05-05 20:16:05206 ASSERT_TRUE(base::WriteFile(
vabr8c498ea42016-09-15 12:41:58207 temp_dir.GetPath().Append("applications").Append(kTemplateFilename),
thestigd0b662342015-01-16 08:21:19208 kTestData1));
[email protected]af71d642010-03-12 10:29:04209 std::string contents;
[email protected]d81a63c02013-03-07 08:49:04210 ASSERT_TRUE(
[email protected]06bfeb12014-05-27 14:00:09211 GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
[email protected]af71d642010-03-12 10:29:04212 EXPECT_EQ(kTestData1, contents);
213 }
214
[email protected]58bf9252013-03-06 04:12:36215 // Test that it falls back to $HOME/.local/share/applications.
216 {
217 base::ScopedTempDir temp_dir;
218 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
219
220 MockEnvironment env;
vabr8c498ea42016-09-15 12:41:58221 base::ScopedPathOverride home_override(base::DIR_HOME, temp_dir.GetPath(),
thestigd2b1fcf2015-01-21 22:11:49222 true /* absolute? */,
223 false /* create? */);
[email protected]426d1c92013-12-03 20:08:54224 ASSERT_TRUE(base::CreateDirectory(
vabr8c498ea42016-09-15 12:41:58225 temp_dir.GetPath().Append(".local/share/applications")));
Lei Zhangeebbef62020-05-05 20:16:05226 ASSERT_TRUE(base::WriteFile(temp_dir.GetPath()
227 .Append(".local/share/applications")
228 .Append(kTemplateFilename),
229 kTestData1));
[email protected]58bf9252013-03-06 04:12:36230 std::string contents;
[email protected]d81a63c02013-03-07 08:49:04231 ASSERT_TRUE(
[email protected]06bfeb12014-05-27 14:00:09232 GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
[email protected]58bf9252013-03-06 04:12:36233 EXPECT_EQ(kTestData1, contents);
234 }
235
236 // Test that it searches $XDG_DATA_DIRS/applications.
[email protected]af71d642010-03-12 10:29:04237 {
[email protected]ea1a3f62012-11-16 20:34:23238 base::ScopedTempDir temp_dir;
[email protected]af71d642010-03-12 10:29:04239 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
240
[email protected]76b90d312010-08-03 03:00:50241 MockEnvironment env;
vabr8c498ea42016-09-15 12:41:58242 env.Set("XDG_DATA_DIRS", temp_dir.GetPath().value());
243 ASSERT_TRUE(
244 base::CreateDirectory(temp_dir.GetPath().Append("applications")));
Lei Zhangeebbef62020-05-05 20:16:05245 ASSERT_TRUE(base::WriteFile(
vabr8c498ea42016-09-15 12:41:58246 temp_dir.GetPath().Append("applications").Append(kTemplateFilename),
thestigd0b662342015-01-16 08:21:19247 kTestData2));
[email protected]af71d642010-03-12 10:29:04248 std::string contents;
[email protected]d81a63c02013-03-07 08:49:04249 ASSERT_TRUE(
[email protected]06bfeb12014-05-27 14:00:09250 GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
[email protected]af71d642010-03-12 10:29:04251 EXPECT_EQ(kTestData2, contents);
252 }
253
[email protected]58bf9252013-03-06 04:12:36254 // Test that it searches $X/applications for each X in $XDG_DATA_DIRS.
[email protected]af71d642010-03-12 10:29:04255 {
[email protected]58bf9252013-03-06 04:12:36256 base::ScopedTempDir temp_dir1;
257 ASSERT_TRUE(temp_dir1.CreateUniqueTempDir());
258 base::ScopedTempDir temp_dir2;
259 ASSERT_TRUE(temp_dir2.CreateUniqueTempDir());
[email protected]af71d642010-03-12 10:29:04260
[email protected]76b90d312010-08-03 03:00:50261 MockEnvironment env;
vabr8c498ea42016-09-15 12:41:58262 env.Set("XDG_DATA_DIRS",
263 temp_dir1.GetPath().value() + ":" + temp_dir2.GetPath().value());
[email protected]58bf9252013-03-06 04:12:36264 // Create a file in a non-applications directory. This should be ignored.
Lei Zhangeebbef62020-05-05 20:16:05265 ASSERT_TRUE(base::WriteFile(temp_dir1.GetPath().Append(kTemplateFilename),
266 kTestData1));
[email protected]58bf9252013-03-06 04:12:36267 // Only create a findable desktop file in the second path.
vabr8c498ea42016-09-15 12:41:58268 ASSERT_TRUE(
269 base::CreateDirectory(temp_dir2.GetPath().Append("applications")));
Lei Zhangeebbef62020-05-05 20:16:05270 ASSERT_TRUE(base::WriteFile(
vabr8c498ea42016-09-15 12:41:58271 temp_dir2.GetPath().Append("applications").Append(kTemplateFilename),
thestigd0b662342015-01-16 08:21:19272 kTestData2));
[email protected]af71d642010-03-12 10:29:04273 std::string contents;
[email protected]d81a63c02013-03-07 08:49:04274 ASSERT_TRUE(
[email protected]06bfeb12014-05-27 14:00:09275 GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
[email protected]d81a63c02013-03-07 08:49:04276 EXPECT_EQ(kTestData2, contents);
277 }
278}
279
[email protected]111f0282013-08-05 10:09:29280TEST(ShellIntegrationTest, GetExistingProfileShortcutFilenames) {
281 base::FilePath kProfilePath("a/b/c/Profile Name?");
282 const char kApp1Filename[] = "chrome-extension1-Profile_Name_.desktop";
283 const char kApp2Filename[] = "chrome-extension2-Profile_Name_.desktop";
284 const char kUnrelatedAppFilename[] = "chrome-extension-Other_Profile.desktop";
285
Gabriel Charette798fde72019-08-20 22:24:04286 content::BrowserTaskEnvironment task_environment;
[email protected]111f0282013-08-05 10:09:29287
288 base::ScopedTempDir temp_dir;
289 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
Lei Zhangeebbef62020-05-05 20:16:05290 ASSERT_TRUE(base::WriteFile(temp_dir.GetPath().Append(kApp1Filename), ""));
291 ASSERT_TRUE(base::WriteFile(temp_dir.GetPath().Append(kApp2Filename), ""));
[email protected]111f0282013-08-05 10:09:29292 // This file should not be returned in the results.
Lei Zhangeebbef62020-05-05 20:16:05293 ASSERT_TRUE(
294 base::WriteFile(temp_dir.GetPath().Append(kUnrelatedAppFilename), ""));
[email protected]111f0282013-08-05 10:09:29295 std::vector<base::FilePath> paths =
vabr8c498ea42016-09-15 12:41:58296 GetExistingProfileShortcutFilenames(kProfilePath, temp_dir.GetPath());
[email protected]111f0282013-08-05 10:09:29297 // Path order is arbitrary. Sort the output for consistency.
298 std::sort(paths.begin(), paths.end());
299 EXPECT_THAT(paths,
300 ElementsAre(base::FilePath(kApp1Filename),
301 base::FilePath(kApp2Filename)));
302}
303
[email protected]0b7df36d2012-07-11 09:50:47304TEST(ShellIntegrationTest, GetWebShortcutFilename) {
[email protected]b96aa932009-08-12 21:34:49305 const struct {
thestigd0b662342015-01-16 08:21:19306 const char* const path;
307 const char* const url;
[email protected]b96aa932009-08-12 21:34:49308 } test_cases[] = {
thestigd0b662342015-01-16 08:21:19309 { "http___foo_.desktop", "http://foo" },
310 { "http___foo_bar_.desktop", "http://foo/bar/" },
311 { "http___foo_bar_a=b&c=d.desktop", "http://foo/bar?a=b&c=d" },
[email protected]b96aa932009-08-12 21:34:49312
313 // Now we're starting to be more evil...
thestigd0b662342015-01-16 08:21:19314 { "http___foo_.desktop", "http://foo/bar/baz/../../../../../" },
315 { "http___foo_.desktop", "http://foo/bar/././../baz/././../" },
316 { "http___.._.desktop", "http://../../../../" },
[email protected]b96aa932009-08-12 21:34:49317 };
Avi Drissman22f82872018-12-25 23:09:07318 for (size_t i = 0; i < base::size(test_cases); i++) {
[email protected]4f260d02010-12-23 18:35:42319 EXPECT_EQ(std::string(chrome::kBrowserProcessExecutableName) + "-" +
[email protected]42896802009-08-28 23:39:44320 test_cases[i].path,
[email protected]06bfeb12014-05-27 14:00:09321 GetWebShortcutFilename(GURL(test_cases[i].url)).value()) <<
[email protected]42896802009-08-28 23:39:44322 " while testing " << test_cases[i].url;
[email protected]b96aa932009-08-12 21:34:49323 }
324}
325
[email protected]a2778d32011-12-01 07:49:34326TEST(ShellIntegrationTest, GetDesktopFileContents) {
[email protected]14fbaed2013-05-02 07:54:02327 const base::FilePath kChromeExePath("/opt/google/chrome/google-chrome");
[email protected]b96aa932009-08-12 21:34:49328 const struct {
thestigd0b662342015-01-16 08:21:19329 const char* const url;
330 const char* const title;
331 const char* const icon_name;
332 const char* const categories;
Jay Harris05b50bd2019-10-30 01:48:38333 const char* const mime_type;
[email protected]d81a63c02013-03-07 08:49:04334 bool nodisplay;
thestigd0b662342015-01-16 08:21:19335 const char* const expected_output;
[email protected]b96aa932009-08-12 21:34:49336 } test_cases[] = {
Nico Webereaa08412019-08-14 01:24:37337 // Real-world case.
Jay Harris05b50bd2019-10-30 01:48:38338 {"http://gmail.com", "GMail", "chrome-http__gmail.com", "", "", false,
[email protected]b96aa932009-08-12 21:34:49339
Nico Webereaa08412019-08-14 01:24:37340 "#!/usr/bin/env xdg-open\n"
341 "[Desktop Entry]\n"
342 "Version=1.0\n"
343 "Terminal=false\n"
344 "Type=Application\n"
345 "Name=GMail\n"
346 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
347 "Icon=chrome-http__gmail.com\n"
348 "StartupWMClass=gmail.com\n"},
[email protected]82810fe12009-09-25 16:21:57349
Nico Webereaa08412019-08-14 01:24:37350 // Make sure that empty icons are replaced by the chrome icon.
Jay Harris05b50bd2019-10-30 01:48:38351 {"http://gmail.com", "GMail", "", "", "", false,
[email protected]0b303cc2009-09-28 22:35:15352
Nico Webereaa08412019-08-14 01:24:37353 "#!/usr/bin/env xdg-open\n"
354 "[Desktop Entry]\n"
355 "Version=1.0\n"
356 "Terminal=false\n"
357 "Type=Application\n"
358 "Name=GMail\n"
359 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
360#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
361 "Icon=google-chrome\n"
[email protected]a7e18f42013-12-19 02:10:37362#else
Nico Webereaa08412019-08-14 01:24:37363 "Icon=chromium-browser\n"
[email protected]a7e18f42013-12-19 02:10:37364#endif
Nico Webereaa08412019-08-14 01:24:37365 "StartupWMClass=gmail.com\n"},
[email protected]0b303cc2009-09-28 22:35:15366
Nico Webereaa08412019-08-14 01:24:37367 // Test adding categories and NoDisplay=true.
368 {"http://gmail.com", "GMail", "chrome-http__gmail.com",
Jay Harris05b50bd2019-10-30 01:48:38369 "Graphics;Education;", "", true,
[email protected]d81a63c02013-03-07 08:49:04370
Nico Webereaa08412019-08-14 01:24:37371 "#!/usr/bin/env xdg-open\n"
372 "[Desktop Entry]\n"
373 "Version=1.0\n"
374 "Terminal=false\n"
375 "Type=Application\n"
376 "Name=GMail\n"
377 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
378 "Icon=chrome-http__gmail.com\n"
379 "Categories=Graphics;Education;\n"
380 "NoDisplay=true\n"
381 "StartupWMClass=gmail.com\n"},
[email protected]d81a63c02013-03-07 08:49:04382
Nico Webereaa08412019-08-14 01:24:37383 // Now we're starting to be more evil...
384 {"http://evil.com/evil --join-the-b0tnet", "Ownz0red\nExec=rm -rf /",
Jay Harris05b50bd2019-10-30 01:48:38385 "chrome-http__evil.com_evil", "", "", false,
[email protected]b96aa932009-08-12 21:34:49386
Nico Webereaa08412019-08-14 01:24:37387 "#!/usr/bin/env xdg-open\n"
388 "[Desktop Entry]\n"
389 "Version=1.0\n"
390 "Terminal=false\n"
391 "Type=Application\n"
392 "Name=http://evil.com/evil%20--join-the-b0tnet\n"
393 "Exec=/opt/google/chrome/google-chrome "
394 "--app=http://evil.com/evil%20--join-the-b0tnet\n"
395 "Icon=chrome-http__evil.com_evil\n"
396 "StartupWMClass=evil.com__evil%20--join-the-b0tnet\n"},
397 {"http://evil.com/evil; rm -rf /; \"; rm -rf $HOME >ownz0red",
Jay Harris05b50bd2019-10-30 01:48:38398 "Innocent Title", "chrome-http__evil.com_evil", "", "", false,
[email protected]82810fe12009-09-25 16:21:57399
Nico Webereaa08412019-08-14 01:24:37400 "#!/usr/bin/env xdg-open\n"
401 "[Desktop Entry]\n"
402 "Version=1.0\n"
403 "Terminal=false\n"
404 "Type=Application\n"
405 "Name=Innocent Title\n"
406 "Exec=/opt/google/chrome/google-chrome "
407 "\"--app=http://evil.com/evil;%20rm%20-rf%20/;%20%22;%20rm%20"
408 // Note: $ is escaped as \$ within an arg to Exec, and then
409 // the \ is escaped as \\ as all strings in a Desktop file should
410 // be; finally, \\ becomes \\\\ when represented in a C++ string!
411 "-rf%20\\\\$HOME%20%3Eownz0red\"\n"
412 "Icon=chrome-http__evil.com_evil\n"
413 "StartupWMClass=evil.com__evil;%20rm%20-rf%20_;%20%22;%20"
414 "rm%20-rf%20$HOME%20%3Eownz0red\n"},
415 {"http://evil.com/evil | cat `echo ownz0red` >/dev/null",
Jay Harris05b50bd2019-10-30 01:48:38416 "Innocent Title", "chrome-http__evil.com_evil", "", "", false,
[email protected]82810fe12009-09-25 16:21:57417
Nico Webereaa08412019-08-14 01:24:37418 "#!/usr/bin/env xdg-open\n"
419 "[Desktop Entry]\n"
420 "Version=1.0\n"
421 "Terminal=false\n"
422 "Type=Application\n"
423 "Name=Innocent Title\n"
424 "Exec=/opt/google/chrome/google-chrome "
425 "--app=http://evil.com/evil%20%7C%20cat%20%60echo%20ownz0red"
426 "%60%20%3E/dev/null\n"
427 "Icon=chrome-http__evil.com_evil\n"
428 "StartupWMClass=evil.com__evil%20%7C%20cat%20%60echo%20ownz0red"
429 "%60%20%3E_dev_null\n"},
Jay Harris05b50bd2019-10-30 01:48:38430 // Test setting mime type
431 {"https://paint.app", "Paint", "chrome-https__paint.app", "Image",
432 "image/png;image/jpg", false,
433
434 "#!/usr/bin/env xdg-open\n"
435 "[Desktop Entry]\n"
436 "Version=1.0\n"
437 "Terminal=false\n"
438 "Type=Application\n"
439 "Name=Paint\n"
Jay Harrisf730b6e2019-11-25 23:49:06440 "MimeType=image/png;image/jpg\n"
Mike Jacksoncfb1bc12021-03-24 01:22:22441 "Exec=/opt/google/chrome/google-chrome --app=https://paint.app/ %U\n"
Jay Harris05b50bd2019-10-30 01:48:38442 "Icon=chrome-https__paint.app\n"
443 "Categories=Image\n"
Jay Harris05b50bd2019-10-30 01:48:38444 "StartupWMClass=paint.app\n"},
445
446 // Test evil mime type.
447 {"https://paint.app", "Evil Paint", "chrome-https__paint.app", "Image",
448 "image/png\nExec=rm -rf /", false,
449
450 "#!/usr/bin/env xdg-open\n"
451 "[Desktop Entry]\n"
452 "Version=1.0\n"
453 "Terminal=false\n"
454 "Type=Application\n"
455 "Name=Evil Paint\n"
456 "Exec=/opt/google/chrome/google-chrome --app=https://paint.app/\n"
457 "Icon=chrome-https__paint.app\n"
458 "Categories=Image\n"
459 "StartupWMClass=paint.app\n"}};
[email protected]07753f0f2013-02-05 07:52:59460
Avi Drissman22f82872018-12-25 23:09:07461 for (size_t i = 0; i < base::size(test_cases); i++) {
[email protected]b10392932011-03-08 21:28:14462 SCOPED_TRACE(i);
[email protected]a0b60cfd2011-04-06 18:02:41463 EXPECT_EQ(
464 test_cases[i].expected_output,
[email protected]06bfeb12014-05-27 14:00:09465 GetDesktopFileContents(
[email protected]14fbaed2013-05-02 07:54:02466 kChromeExePath,
[email protected]a0b60cfd2011-04-06 18:02:41467 web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)),
Jay Harris05b50bd2019-10-30 01:48:38468 GURL(test_cases[i].url), std::string(),
469 base::ASCIIToUTF16(test_cases[i].title), test_cases[i].icon_name,
470 base::FilePath(), test_cases[i].categories, test_cases[i].mime_type,
Mandy Chend57606f2021-08-31 17:49:10471 test_cases[i].nodisplay, "", {}));
472 }
473}
474
475TEST(ShellIntegrationTest, GetDesktopFileContentsForApps) {
Mandy Chen0cd67e02021-09-09 20:22:12476 base::test::ScopedFeatureList feature_list;
477 feature_list.InitAndEnableFeature(
478 features::kDesktopPWAsAppIconShortcutsMenuUI);
Mandy Chend57606f2021-08-31 17:49:10479 const base::FilePath kChromeExePath("/opt/google/chrome/google-chrome");
480 const struct {
481 const char* const url;
482 const char* const title;
483 const char* const icon_name;
484 bool nodisplay;
485 std::set<web_app::DesktopActionInfo> action_info;
486 const char* const expected_output;
487 } test_cases[] = {
488 // Test Shortcut Menu actions.
489 {"https://example.app",
490 "Lawful example",
491 "IconName",
492 false,
493 {
494 web_app::DesktopActionInfo("action1", "Action 1",
495 GURL("https://example.com/action1")),
496 web_app::DesktopActionInfo("action2", "Action 2",
497 GURL("https://example.com/action2")),
498 web_app::DesktopActionInfo("action3", "Action 3",
499 GURL("https://example.com/action3")),
500 web_app::DesktopActionInfo("action4", "Action 4",
501 GURL("https://example.com/action4")),
502 },
503
504 "#!/usr/bin/env xdg-open\n"
505 "[Desktop Entry]\n"
506 "Version=1.0\n"
507 "Terminal=false\n"
508 "Type=Application\n"
509 "Name=Lawful example\n"
510 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId\n"
511 "Icon=IconName\n"
512 "StartupWMClass=example.app\n"
513 "Actions=action1;action2;action3;action4\n\n"
514 "[Desktop Action action1]\n"
515 "Name=Action 1\n"
516 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
517 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
518 "action1\n\n"
519 "[Desktop Action action2]\n"
520 "Name=Action 2\n"
521 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
522 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
523 "action2\n\n"
524 "[Desktop Action action3]\n"
525 "Name=Action 3\n"
526 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
527 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
528 "action3\n\n"
529 "[Desktop Action action4]\n"
530 "Name=Action 4\n"
531 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
532 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
533 "action4\n"},
534 };
535
536 for (size_t i = 0; i < base::size(test_cases); i++) {
537 SCOPED_TRACE(i);
538 EXPECT_EQ(
539 test_cases[i].expected_output,
540 GetDesktopFileContents(
541 kChromeExePath,
542 web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)),
543 GURL(test_cases[i].url), "TestAppId",
544 base::ASCIIToUTF16(test_cases[i].title), test_cases[i].icon_name,
545 base::FilePath(), "", "", test_cases[i].nodisplay, "",
546 test_cases[i].action_info));
[email protected]b96aa932009-08-12 21:34:49547 }
548}
[email protected]d81a63c02013-03-07 08:49:04549
[email protected]a3c25952013-05-02 13:16:06550TEST(ShellIntegrationTest, GetDirectoryFileContents) {
551 const struct {
thestigd0b662342015-01-16 08:21:19552 const char* const title;
553 const char* const icon_name;
554 const char* const expected_output;
[email protected]a3c25952013-05-02 13:16:06555 } test_cases[] = {
Nico Webereaa08412019-08-14 01:24:37556 // Real-world case.
557 {"Chrome Apps", "chrome-apps",
[email protected]a3c25952013-05-02 13:16:06558
Nico Webereaa08412019-08-14 01:24:37559 "[Desktop Entry]\n"
560 "Version=1.0\n"
561 "Type=Directory\n"
562 "Name=Chrome Apps\n"
563 "Icon=chrome-apps\n"},
[email protected]a3c25952013-05-02 13:16:06564
Nico Webereaa08412019-08-14 01:24:37565 // Make sure that empty icons are replaced by the chrome icon.
566 {"Chrome Apps", "",
[email protected]a3c25952013-05-02 13:16:06567
Nico Webereaa08412019-08-14 01:24:37568 "[Desktop Entry]\n"
569 "Version=1.0\n"
570 "Type=Directory\n"
571 "Name=Chrome Apps\n"
572#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
573 "Icon=google-chrome\n"
[email protected]a7e18f42013-12-19 02:10:37574#else
Nico Webereaa08412019-08-14 01:24:37575 "Icon=chromium-browser\n"
[email protected]a7e18f42013-12-19 02:10:37576#endif
Nico Webereaa08412019-08-14 01:24:37577 },
[email protected]a3c25952013-05-02 13:16:06578 };
579
Avi Drissman22f82872018-12-25 23:09:07580 for (size_t i = 0; i < base::size(test_cases); i++) {
[email protected]a3c25952013-05-02 13:16:06581 SCOPED_TRACE(i);
[email protected]06bfeb12014-05-27 14:00:09582 EXPECT_EQ(test_cases[i].expected_output,
583 GetDirectoryFileContents(base::ASCIIToUTF16(test_cases[i].title),
584 test_cases[i].icon_name));
[email protected]a3c25952013-05-02 13:16:06585 }
586}
[email protected]06bfeb12014-05-27 14:00:09587
Robert Woods954a2182020-03-20 06:08:27588TEST(ShellIntegrationTest, GetMimeTypesRegistrationFilename) {
589 const struct {
590 const char* const profile_path;
591 const char* const app_id;
592 const char* const expected_filename;
593 } test_cases[] = {
594 {"Default", "app-id", "-app-id-Default.xml"},
595 {"Default Profile", "app-id", "-app-id-Default_Profile.xml"},
596 {"foo/Default", "app-id", "-app-id-Default.xml"},
597 {"Default*Profile", "app-id", "-app-id-Default_Profile.xml"}};
598 std::string browser_name(chrome::kBrowserProcessExecutableName);
599
600 for (const auto& test_case : test_cases) {
601 const base::FilePath filename =
602 GetMimeTypesRegistrationFilename(base::FilePath(test_case.profile_path),
603 web_app::AppId(test_case.app_id));
604 EXPECT_EQ(browser_name + test_case.expected_filename, filename.value());
605 }
606}
607
608TEST(ShellIntegrationTest, GetMimeTypesRegistrationFileContents) {
609 apps::FileHandlers file_handlers;
610 {
611 apps::FileHandler file_handler;
612 {
613 apps::FileHandler::AcceptEntry accept_entry;
614 accept_entry.mime_type = "application/foo";
615 accept_entry.file_extensions.insert(".foo");
616 file_handler.accept.push_back(accept_entry);
617 }
Evan Stadeb8f43a42021-09-14 16:25:14618 file_handler.display_name = u"FoO";
Robert Woods954a2182020-03-20 06:08:27619 file_handlers.push_back(file_handler);
620 }
621 {
622 apps::FileHandler file_handler;
623 {
624 apps::FileHandler::AcceptEntry accept_entry;
625 accept_entry.mime_type = "application/foobar";
626 accept_entry.file_extensions.insert(".foobar");
627 file_handler.accept.push_back(accept_entry);
628 }
629 file_handlers.push_back(file_handler);
630 }
631 {
632 apps::FileHandler file_handler;
633 {
634 apps::FileHandler::AcceptEntry accept_entry;
635 accept_entry.mime_type = "application/bar";
Evan Stadeb8f43a42021-09-14 16:25:14636 // A name that has a reserved XML character.
637 file_handler.display_name = u"ba<r";
Robert Woods954a2182020-03-20 06:08:27638 accept_entry.file_extensions.insert(".bar");
639 accept_entry.file_extensions.insert(".baz");
640 file_handler.accept.push_back(accept_entry);
641 }
642 file_handlers.push_back(file_handler);
643 }
644
645 const std::string file_contents =
646 GetMimeTypesRegistrationFileContents(file_handlers);
647 const std::string expected_file_contents =
Evan Stadeb8f43a42021-09-14 16:25:14648 "<?xml version=\"1.0\"?>\n"
Robert Woods954a2182020-03-20 06:08:27649 "<mime-info "
650 "xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n"
Evan Stadeb8f43a42021-09-14 16:25:14651 " <mime-type type=\"application/foo\">\n"
652 " <comment>FoO</comment>\n"
653 " <glob pattern=\"*.foo\"/>\n"
654 " </mime-type>\n"
655 " <mime-type type=\"application/foobar\">\n"
656 " <glob pattern=\"*.foobar\"/>\n"
657 " </mime-type>\n"
658 " <mime-type type=\"application/bar\">\n"
659 " <comment>ba&lt;r</comment>\n"
660 " <glob pattern=\"*.bar\"/>\n"
661 " <glob pattern=\"*.baz\"/>\n"
662 " </mime-type>\n"
Robert Woods954a2182020-03-20 06:08:27663 "</mime-info>\n";
664
665 EXPECT_EQ(file_contents, expected_file_contents);
666}
667
Alexander Dunaev1016e632021-08-10 15:13:34668// The WM class name may be either capitalised or not, depending on the
669// platform.
670void CheckProgramClassClass(const std::string& class_name) {
671#if defined(USE_OZONE)
672 if (features::IsUsingOzonePlatform() &&
673 ui::OzonePlatform::GetPlatformNameForTest() != "x11") {
674 EXPECT_EQ("foo", class_name);
675 } else {
676 EXPECT_EQ("Foo", class_name);
677 }
678#else
679 EXPECT_EQ("foo", class_name);
680#endif
681}
682
thomasanderson12d87582016-07-29 21:17:41683TEST(ShellIntegrationTest, WmClass) {
684 base::CommandLine command_line((base::FilePath()));
685 EXPECT_EQ("foo", internal::GetProgramClassName(command_line, "foo.desktop"));
Alexander Dunaev1016e632021-08-10 15:13:34686 CheckProgramClassClass(
687 internal::GetProgramClassClass(command_line, "foo.desktop"));
thomasanderson12d87582016-07-29 21:17:41688
689 command_line.AppendSwitchASCII("class", "baR");
690 EXPECT_EQ("foo", internal::GetProgramClassName(command_line, "foo.desktop"));
691 EXPECT_EQ("baR", internal::GetProgramClassClass(command_line, "foo.desktop"));
692
693 command_line = base::CommandLine(base::FilePath());
694 command_line.AppendSwitchASCII("user-data-dir", "/tmp/baz");
695 EXPECT_EQ("foo (/tmp/baz)",
696 internal::GetProgramClassName(command_line, "foo.desktop"));
Alexander Dunaev1016e632021-08-10 15:13:34697 CheckProgramClassClass(
698 internal::GetProgramClassClass(command_line, "foo.desktop"));
thomasanderson12d87582016-07-29 21:17:41699}
700
[email protected]06bfeb12014-05-27 14:00:09701} // namespace shell_integration_linux