blob: cac3cabecf3fc9b100a2188c7dfe70dbb55886a8 [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"
Alexander Dunaev66b50ba2021-08-10 04:11:4637#include "ui/ozone/public/ozone_platform.h"
Alexander Dunaev66b50ba2021-08-10 04:11:4638
[email protected]111f0282013-08-05 10:09:2939using ::testing::ElementsAre;
[email protected]631bb742011-11-02 11:29:3940
[email protected]06bfeb12014-05-27 14:00:0941namespace shell_integration_linux {
42
[email protected]af71d642010-03-12 10:29:0443namespace {
44
45// Provides mock environment variables values based on a stored map.
[email protected]76b90d312010-08-03 03:00:5046class MockEnvironment : public base::Environment {
[email protected]af71d642010-03-12 10:29:0447 public:
[email protected]76b90d312010-08-03 03:00:5048 MockEnvironment() {}
[email protected]af71d642010-03-12 10:29:0449
thestig0c412e852016-06-30 08:04:4050 void Set(base::StringPiece name, const std::string& value) {
Peter Kasting9eb10b02021-04-29 03:10:4851 variables_[std::string(name)] = value;
[email protected]af71d642010-03-12 10:29:0452 }
53
thestig0c412e852016-06-30 08:04:4054 bool GetVar(base::StringPiece variable_name, std::string* result) override {
Peter Kasting9eb10b02021-04-29 03:10:4855 if (base::Contains(variables_, std::string(variable_name))) {
56 *result = variables_[std::string(variable_name)];
[email protected]af71d642010-03-12 10:29:0457 return true;
58 }
59
60 return false;
61 }
62
thestig0c412e852016-06-30 08:04:4063 bool SetVar(base::StringPiece variable_name,
dchenge1bc7982014-10-30 00:32:4064 const std::string& new_value) override {
[email protected]fc586c72010-07-31 16:55:4065 ADD_FAILURE();
66 return false;
67 }
68
thestig0c412e852016-06-30 08:04:4069 bool UnSetVar(base::StringPiece variable_name) override {
[email protected]fc586c72010-07-31 16:55:4070 ADD_FAILURE();
[email protected]e9032c62010-07-16 03:34:2571 return false;
[email protected]ac7264c2010-07-08 13:32:5172 }
73
[email protected]af71d642010-03-12 10:29:0474 private:
75 std::map<std::string, std::string> variables_;
76
[email protected]76b90d312010-08-03 03:00:5077 DISALLOW_COPY_AND_ASSIGN(MockEnvironment);
[email protected]af71d642010-03-12 10:29:0478};
79
thestigd0b662342015-01-16 08:21:1980// This helps EXPECT_THAT(..., ElementsAre(...)) print out more meaningful
81// failure messages.
82std::vector<std::string> FilePathsToStrings(
83 const std::vector<base::FilePath>& paths) {
84 std::vector<std::string> values;
85 for (const auto& path : paths)
86 values.push_back(path.value());
87 return values;
88}
89
[email protected]af71d642010-03-12 10:29:0490} // namespace
91
[email protected]111f0282013-08-05 10:09:2992TEST(ShellIntegrationTest, GetDataWriteLocation) {
Gabriel Charette798fde72019-08-20 22:24:0493 content::BrowserTaskEnvironment task_environment;
[email protected]111f0282013-08-05 10:09:2994
95 // Test that it returns $XDG_DATA_HOME.
96 {
97 MockEnvironment env;
thestigd2b1fcf2015-01-21 22:11:4998 base::ScopedPathOverride home_override(base::DIR_HOME,
99 base::FilePath("/home/user"),
100 true /* absolute? */,
101 false /* create? */);
[email protected]111f0282013-08-05 10:09:29102 env.Set("XDG_DATA_HOME", "/user/path");
thestigd2b1fcf2015-01-21 22:11:49103 base::FilePath path = GetDataWriteLocation(&env);
thestigd0b662342015-01-16 08:21:19104 EXPECT_EQ("/user/path", path.value());
[email protected]111f0282013-08-05 10:09:29105 }
106
107 // Test that $XDG_DATA_HOME falls back to $HOME/.local/share.
108 {
109 MockEnvironment env;
thestigd2b1fcf2015-01-21 22:11:49110 base::ScopedPathOverride home_override(base::DIR_HOME,
111 base::FilePath("/home/user"),
112 true /* absolute? */,
113 false /* create? */);
114 base::FilePath path = GetDataWriteLocation(&env);
thestigd0b662342015-01-16 08:21:19115 EXPECT_EQ("/home/user/.local/share", path.value());
[email protected]111f0282013-08-05 10:09:29116 }
[email protected]111f0282013-08-05 10:09:29117}
118
119TEST(ShellIntegrationTest, GetDataSearchLocations) {
Gabriel Charette798fde72019-08-20 22:24:04120 content::BrowserTaskEnvironment task_environment;
[email protected]111f0282013-08-05 10:09:29121
122 // Test that it returns $XDG_DATA_HOME + $XDG_DATA_DIRS.
123 {
124 MockEnvironment env;
thestigd2b1fcf2015-01-21 22:11:49125 base::ScopedPathOverride home_override(base::DIR_HOME,
126 base::FilePath("/home/user"),
127 true /* absolute? */,
128 false /* create? */);
[email protected]111f0282013-08-05 10:09:29129 env.Set("XDG_DATA_HOME", "/user/path");
130 env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2");
131 EXPECT_THAT(
thestigd0b662342015-01-16 08:21:19132 FilePathsToStrings(GetDataSearchLocations(&env)),
133 ElementsAre("/user/path",
134 "/system/path/1",
135 "/system/path/2"));
[email protected]111f0282013-08-05 10:09:29136 }
137
138 // Test that $XDG_DATA_HOME falls back to $HOME/.local/share.
139 {
140 MockEnvironment env;
thestigd2b1fcf2015-01-21 22:11:49141 base::ScopedPathOverride home_override(base::DIR_HOME,
142 base::FilePath("/home/user"),
143 true /* absolute? */,
144 false /* create? */);
[email protected]111f0282013-08-05 10:09:29145 env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2");
146 EXPECT_THAT(
thestigd0b662342015-01-16 08:21:19147 FilePathsToStrings(GetDataSearchLocations(&env)),
148 ElementsAre("/home/user/.local/share",
149 "/system/path/1",
150 "/system/path/2"));
[email protected]111f0282013-08-05 10:09:29151 }
152
153 // Test that if neither $XDG_DATA_HOME nor $HOME are specified, it still
154 // succeeds.
155 {
156 MockEnvironment env;
157 env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2");
thestigd2b1fcf2015-01-21 22:11:49158 std::vector<std::string> results =
159 FilePathsToStrings(GetDataSearchLocations(&env));
160 ASSERT_EQ(3U, results.size());
161 EXPECT_FALSE(results[0].empty());
162 EXPECT_EQ("/system/path/1", results[1]);
163 EXPECT_EQ("/system/path/2", results[2]);
[email protected]111f0282013-08-05 10:09:29164 }
165
166 // Test that $XDG_DATA_DIRS falls back to the two default paths.
167 {
168 MockEnvironment env;
thestigd2b1fcf2015-01-21 22:11:49169 base::ScopedPathOverride home_override(base::DIR_HOME,
170 base::FilePath("/home/user"),
171 true /* absolute? */,
172 false /* create? */);
[email protected]111f0282013-08-05 10:09:29173 env.Set("XDG_DATA_HOME", "/user/path");
174 EXPECT_THAT(
thestigd0b662342015-01-16 08:21:19175 FilePathsToStrings(GetDataSearchLocations(&env)),
176 ElementsAre("/user/path",
177 "/usr/local/share",
178 "/usr/share"));
[email protected]111f0282013-08-05 10:09:29179 }
180}
181
[email protected]d81a63c02013-03-07 08:49:04182TEST(ShellIntegrationTest, GetExistingShortcutContents) {
183 const char kTemplateFilename[] = "shortcut-test.desktop";
184 base::FilePath kTemplateFilepath(kTemplateFilename);
[email protected]af71d642010-03-12 10:29:04185 const char kTestData1[] = "a magical testing string";
186 const char kTestData2[] = "a different testing string";
187
Gabriel Charette798fde72019-08-20 22:24:04188 content::BrowserTaskEnvironment task_environment;
[email protected]af71d642010-03-12 10:29:04189
[email protected]58bf9252013-03-06 04:12:36190 // Test that it searches $XDG_DATA_HOME/applications.
[email protected]af71d642010-03-12 10:29:04191 {
[email protected]ea1a3f62012-11-16 20:34:23192 base::ScopedTempDir temp_dir;
[email protected]af71d642010-03-12 10:29:04193 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
194
[email protected]76b90d312010-08-03 03:00:50195 MockEnvironment env;
vabr8c498ea42016-09-15 12:41:58196 env.Set("XDG_DATA_HOME", temp_dir.GetPath().value());
[email protected]58bf9252013-03-06 04:12:36197 // Create a file in a non-applications directory. This should be ignored.
Lei Zhangeebbef62020-05-05 20:16:05198 ASSERT_TRUE(base::WriteFile(temp_dir.GetPath().Append(kTemplateFilename),
199 kTestData2));
vabr8c498ea42016-09-15 12:41:58200 ASSERT_TRUE(
201 base::CreateDirectory(temp_dir.GetPath().Append("applications")));
Lei Zhangeebbef62020-05-05 20:16:05202 ASSERT_TRUE(base::WriteFile(
vabr8c498ea42016-09-15 12:41:58203 temp_dir.GetPath().Append("applications").Append(kTemplateFilename),
thestigd0b662342015-01-16 08:21:19204 kTestData1));
[email protected]af71d642010-03-12 10:29:04205 std::string contents;
[email protected]d81a63c02013-03-07 08:49:04206 ASSERT_TRUE(
[email protected]06bfeb12014-05-27 14:00:09207 GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
[email protected]af71d642010-03-12 10:29:04208 EXPECT_EQ(kTestData1, contents);
209 }
210
[email protected]58bf9252013-03-06 04:12:36211 // Test that it falls back to $HOME/.local/share/applications.
212 {
213 base::ScopedTempDir temp_dir;
214 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
215
216 MockEnvironment env;
vabr8c498ea42016-09-15 12:41:58217 base::ScopedPathOverride home_override(base::DIR_HOME, temp_dir.GetPath(),
thestigd2b1fcf2015-01-21 22:11:49218 true /* absolute? */,
219 false /* create? */);
[email protected]426d1c92013-12-03 20:08:54220 ASSERT_TRUE(base::CreateDirectory(
vabr8c498ea42016-09-15 12:41:58221 temp_dir.GetPath().Append(".local/share/applications")));
Lei Zhangeebbef62020-05-05 20:16:05222 ASSERT_TRUE(base::WriteFile(temp_dir.GetPath()
223 .Append(".local/share/applications")
224 .Append(kTemplateFilename),
225 kTestData1));
[email protected]58bf9252013-03-06 04:12:36226 std::string contents;
[email protected]d81a63c02013-03-07 08:49:04227 ASSERT_TRUE(
[email protected]06bfeb12014-05-27 14:00:09228 GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
[email protected]58bf9252013-03-06 04:12:36229 EXPECT_EQ(kTestData1, contents);
230 }
231
232 // Test that it searches $XDG_DATA_DIRS/applications.
[email protected]af71d642010-03-12 10:29:04233 {
[email protected]ea1a3f62012-11-16 20:34:23234 base::ScopedTempDir temp_dir;
[email protected]af71d642010-03-12 10:29:04235 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
236
[email protected]76b90d312010-08-03 03:00:50237 MockEnvironment env;
vabr8c498ea42016-09-15 12:41:58238 env.Set("XDG_DATA_DIRS", temp_dir.GetPath().value());
239 ASSERT_TRUE(
240 base::CreateDirectory(temp_dir.GetPath().Append("applications")));
Lei Zhangeebbef62020-05-05 20:16:05241 ASSERT_TRUE(base::WriteFile(
vabr8c498ea42016-09-15 12:41:58242 temp_dir.GetPath().Append("applications").Append(kTemplateFilename),
thestigd0b662342015-01-16 08:21:19243 kTestData2));
[email protected]af71d642010-03-12 10:29:04244 std::string contents;
[email protected]d81a63c02013-03-07 08:49:04245 ASSERT_TRUE(
[email protected]06bfeb12014-05-27 14:00:09246 GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
[email protected]af71d642010-03-12 10:29:04247 EXPECT_EQ(kTestData2, contents);
248 }
249
[email protected]58bf9252013-03-06 04:12:36250 // Test that it searches $X/applications for each X in $XDG_DATA_DIRS.
[email protected]af71d642010-03-12 10:29:04251 {
[email protected]58bf9252013-03-06 04:12:36252 base::ScopedTempDir temp_dir1;
253 ASSERT_TRUE(temp_dir1.CreateUniqueTempDir());
254 base::ScopedTempDir temp_dir2;
255 ASSERT_TRUE(temp_dir2.CreateUniqueTempDir());
[email protected]af71d642010-03-12 10:29:04256
[email protected]76b90d312010-08-03 03:00:50257 MockEnvironment env;
vabr8c498ea42016-09-15 12:41:58258 env.Set("XDG_DATA_DIRS",
259 temp_dir1.GetPath().value() + ":" + temp_dir2.GetPath().value());
[email protected]58bf9252013-03-06 04:12:36260 // Create a file in a non-applications directory. This should be ignored.
Lei Zhangeebbef62020-05-05 20:16:05261 ASSERT_TRUE(base::WriteFile(temp_dir1.GetPath().Append(kTemplateFilename),
262 kTestData1));
[email protected]58bf9252013-03-06 04:12:36263 // Only create a findable desktop file in the second path.
vabr8c498ea42016-09-15 12:41:58264 ASSERT_TRUE(
265 base::CreateDirectory(temp_dir2.GetPath().Append("applications")));
Lei Zhangeebbef62020-05-05 20:16:05266 ASSERT_TRUE(base::WriteFile(
vabr8c498ea42016-09-15 12:41:58267 temp_dir2.GetPath().Append("applications").Append(kTemplateFilename),
thestigd0b662342015-01-16 08:21:19268 kTestData2));
[email protected]af71d642010-03-12 10:29:04269 std::string contents;
[email protected]d81a63c02013-03-07 08:49:04270 ASSERT_TRUE(
[email protected]06bfeb12014-05-27 14:00:09271 GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
[email protected]d81a63c02013-03-07 08:49:04272 EXPECT_EQ(kTestData2, contents);
273 }
274}
275
[email protected]111f0282013-08-05 10:09:29276TEST(ShellIntegrationTest, GetExistingProfileShortcutFilenames) {
277 base::FilePath kProfilePath("a/b/c/Profile Name?");
278 const char kApp1Filename[] = "chrome-extension1-Profile_Name_.desktop";
279 const char kApp2Filename[] = "chrome-extension2-Profile_Name_.desktop";
280 const char kUnrelatedAppFilename[] = "chrome-extension-Other_Profile.desktop";
281
Gabriel Charette798fde72019-08-20 22:24:04282 content::BrowserTaskEnvironment task_environment;
[email protected]111f0282013-08-05 10:09:29283
284 base::ScopedTempDir temp_dir;
285 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
Lei Zhangeebbef62020-05-05 20:16:05286 ASSERT_TRUE(base::WriteFile(temp_dir.GetPath().Append(kApp1Filename), ""));
287 ASSERT_TRUE(base::WriteFile(temp_dir.GetPath().Append(kApp2Filename), ""));
[email protected]111f0282013-08-05 10:09:29288 // This file should not be returned in the results.
Lei Zhangeebbef62020-05-05 20:16:05289 ASSERT_TRUE(
290 base::WriteFile(temp_dir.GetPath().Append(kUnrelatedAppFilename), ""));
[email protected]111f0282013-08-05 10:09:29291 std::vector<base::FilePath> paths =
vabr8c498ea42016-09-15 12:41:58292 GetExistingProfileShortcutFilenames(kProfilePath, temp_dir.GetPath());
[email protected]111f0282013-08-05 10:09:29293 // Path order is arbitrary. Sort the output for consistency.
294 std::sort(paths.begin(), paths.end());
295 EXPECT_THAT(paths,
296 ElementsAre(base::FilePath(kApp1Filename),
297 base::FilePath(kApp2Filename)));
298}
299
[email protected]0b7df36d2012-07-11 09:50:47300TEST(ShellIntegrationTest, GetWebShortcutFilename) {
[email protected]b96aa932009-08-12 21:34:49301 const struct {
thestigd0b662342015-01-16 08:21:19302 const char* const path;
303 const char* const url;
[email protected]b96aa932009-08-12 21:34:49304 } test_cases[] = {
thestigd0b662342015-01-16 08:21:19305 { "http___foo_.desktop", "http://foo" },
306 { "http___foo_bar_.desktop", "http://foo/bar/" },
307 { "http___foo_bar_a=b&c=d.desktop", "http://foo/bar?a=b&c=d" },
[email protected]b96aa932009-08-12 21:34:49308
309 // Now we're starting to be more evil...
thestigd0b662342015-01-16 08:21:19310 { "http___foo_.desktop", "http://foo/bar/baz/../../../../../" },
311 { "http___foo_.desktop", "http://foo/bar/././../baz/././../" },
312 { "http___.._.desktop", "http://../../../../" },
[email protected]b96aa932009-08-12 21:34:49313 };
Avi Drissman22f82872018-12-25 23:09:07314 for (size_t i = 0; i < base::size(test_cases); i++) {
[email protected]4f260d02010-12-23 18:35:42315 EXPECT_EQ(std::string(chrome::kBrowserProcessExecutableName) + "-" +
[email protected]42896802009-08-28 23:39:44316 test_cases[i].path,
[email protected]06bfeb12014-05-27 14:00:09317 GetWebShortcutFilename(GURL(test_cases[i].url)).value()) <<
[email protected]42896802009-08-28 23:39:44318 " while testing " << test_cases[i].url;
[email protected]b96aa932009-08-12 21:34:49319 }
320}
321
[email protected]a2778d32011-12-01 07:49:34322TEST(ShellIntegrationTest, GetDesktopFileContents) {
[email protected]14fbaed2013-05-02 07:54:02323 const base::FilePath kChromeExePath("/opt/google/chrome/google-chrome");
[email protected]b96aa932009-08-12 21:34:49324 const struct {
thestigd0b662342015-01-16 08:21:19325 const char* const url;
326 const char* const title;
327 const char* const icon_name;
328 const char* const categories;
Jay Harris05b50bd2019-10-30 01:48:38329 const char* const mime_type;
[email protected]d81a63c02013-03-07 08:49:04330 bool nodisplay;
thestigd0b662342015-01-16 08:21:19331 const char* const expected_output;
[email protected]b96aa932009-08-12 21:34:49332 } test_cases[] = {
Nico Webereaa08412019-08-14 01:24:37333 // Real-world case.
Jay Harris05b50bd2019-10-30 01:48:38334 {"http://gmail.com", "GMail", "chrome-http__gmail.com", "", "", false,
[email protected]b96aa932009-08-12 21:34:49335
Nico Webereaa08412019-08-14 01:24:37336 "#!/usr/bin/env xdg-open\n"
337 "[Desktop Entry]\n"
338 "Version=1.0\n"
339 "Terminal=false\n"
340 "Type=Application\n"
341 "Name=GMail\n"
342 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
343 "Icon=chrome-http__gmail.com\n"
344 "StartupWMClass=gmail.com\n"},
[email protected]82810fe12009-09-25 16:21:57345
Nico Webereaa08412019-08-14 01:24:37346 // Make sure that empty icons are replaced by the chrome icon.
Jay Harris05b50bd2019-10-30 01:48:38347 {"http://gmail.com", "GMail", "", "", "", false,
[email protected]0b303cc2009-09-28 22:35:15348
Nico Webereaa08412019-08-14 01:24:37349 "#!/usr/bin/env xdg-open\n"
350 "[Desktop Entry]\n"
351 "Version=1.0\n"
352 "Terminal=false\n"
353 "Type=Application\n"
354 "Name=GMail\n"
355 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
356#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
357 "Icon=google-chrome\n"
[email protected]a7e18f42013-12-19 02:10:37358#else
Nico Webereaa08412019-08-14 01:24:37359 "Icon=chromium-browser\n"
[email protected]a7e18f42013-12-19 02:10:37360#endif
Nico Webereaa08412019-08-14 01:24:37361 "StartupWMClass=gmail.com\n"},
[email protected]0b303cc2009-09-28 22:35:15362
Nico Webereaa08412019-08-14 01:24:37363 // Test adding categories and NoDisplay=true.
364 {"http://gmail.com", "GMail", "chrome-http__gmail.com",
Jay Harris05b50bd2019-10-30 01:48:38365 "Graphics;Education;", "", true,
[email protected]d81a63c02013-03-07 08:49:04366
Nico Webereaa08412019-08-14 01:24:37367 "#!/usr/bin/env xdg-open\n"
368 "[Desktop Entry]\n"
369 "Version=1.0\n"
370 "Terminal=false\n"
371 "Type=Application\n"
372 "Name=GMail\n"
373 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
374 "Icon=chrome-http__gmail.com\n"
375 "Categories=Graphics;Education;\n"
376 "NoDisplay=true\n"
377 "StartupWMClass=gmail.com\n"},
[email protected]d81a63c02013-03-07 08:49:04378
Nico Webereaa08412019-08-14 01:24:37379 // Now we're starting to be more evil...
380 {"http://evil.com/evil --join-the-b0tnet", "Ownz0red\nExec=rm -rf /",
Jay Harris05b50bd2019-10-30 01:48:38381 "chrome-http__evil.com_evil", "", "", false,
[email protected]b96aa932009-08-12 21:34:49382
Nico Webereaa08412019-08-14 01:24:37383 "#!/usr/bin/env xdg-open\n"
384 "[Desktop Entry]\n"
385 "Version=1.0\n"
386 "Terminal=false\n"
387 "Type=Application\n"
388 "Name=http://evil.com/evil%20--join-the-b0tnet\n"
389 "Exec=/opt/google/chrome/google-chrome "
390 "--app=http://evil.com/evil%20--join-the-b0tnet\n"
391 "Icon=chrome-http__evil.com_evil\n"
392 "StartupWMClass=evil.com__evil%20--join-the-b0tnet\n"},
393 {"http://evil.com/evil; rm -rf /; \"; rm -rf $HOME >ownz0red",
Jay Harris05b50bd2019-10-30 01:48:38394 "Innocent Title", "chrome-http__evil.com_evil", "", "", false,
[email protected]82810fe12009-09-25 16:21:57395
Nico Webereaa08412019-08-14 01:24:37396 "#!/usr/bin/env xdg-open\n"
397 "[Desktop Entry]\n"
398 "Version=1.0\n"
399 "Terminal=false\n"
400 "Type=Application\n"
401 "Name=Innocent Title\n"
402 "Exec=/opt/google/chrome/google-chrome "
403 "\"--app=http://evil.com/evil;%20rm%20-rf%20/;%20%22;%20rm%20"
404 // Note: $ is escaped as \$ within an arg to Exec, and then
405 // the \ is escaped as \\ as all strings in a Desktop file should
406 // be; finally, \\ becomes \\\\ when represented in a C++ string!
407 "-rf%20\\\\$HOME%20%3Eownz0red\"\n"
408 "Icon=chrome-http__evil.com_evil\n"
409 "StartupWMClass=evil.com__evil;%20rm%20-rf%20_;%20%22;%20"
410 "rm%20-rf%20$HOME%20%3Eownz0red\n"},
411 {"http://evil.com/evil | cat `echo ownz0red` >/dev/null",
Jay Harris05b50bd2019-10-30 01:48:38412 "Innocent Title", "chrome-http__evil.com_evil", "", "", false,
[email protected]82810fe12009-09-25 16:21:57413
Nico Webereaa08412019-08-14 01:24:37414 "#!/usr/bin/env xdg-open\n"
415 "[Desktop Entry]\n"
416 "Version=1.0\n"
417 "Terminal=false\n"
418 "Type=Application\n"
419 "Name=Innocent Title\n"
420 "Exec=/opt/google/chrome/google-chrome "
421 "--app=http://evil.com/evil%20%7C%20cat%20%60echo%20ownz0red"
422 "%60%20%3E/dev/null\n"
423 "Icon=chrome-http__evil.com_evil\n"
424 "StartupWMClass=evil.com__evil%20%7C%20cat%20%60echo%20ownz0red"
425 "%60%20%3E_dev_null\n"},
Jay Harris05b50bd2019-10-30 01:48:38426 // Test setting mime type
427 {"https://paint.app", "Paint", "chrome-https__paint.app", "Image",
428 "image/png;image/jpg", false,
429
430 "#!/usr/bin/env xdg-open\n"
431 "[Desktop Entry]\n"
432 "Version=1.0\n"
433 "Terminal=false\n"
434 "Type=Application\n"
435 "Name=Paint\n"
Jay Harrisf730b6e2019-11-25 23:49:06436 "MimeType=image/png;image/jpg\n"
Mike Jacksoncfb1bc12021-03-24 01:22:22437 "Exec=/opt/google/chrome/google-chrome --app=https://paint.app/ %U\n"
Jay Harris05b50bd2019-10-30 01:48:38438 "Icon=chrome-https__paint.app\n"
439 "Categories=Image\n"
Jay Harris05b50bd2019-10-30 01:48:38440 "StartupWMClass=paint.app\n"},
441
442 // Test evil mime type.
443 {"https://paint.app", "Evil Paint", "chrome-https__paint.app", "Image",
444 "image/png\nExec=rm -rf /", false,
445
446 "#!/usr/bin/env xdg-open\n"
447 "[Desktop Entry]\n"
448 "Version=1.0\n"
449 "Terminal=false\n"
450 "Type=Application\n"
451 "Name=Evil Paint\n"
452 "Exec=/opt/google/chrome/google-chrome --app=https://paint.app/\n"
453 "Icon=chrome-https__paint.app\n"
454 "Categories=Image\n"
455 "StartupWMClass=paint.app\n"}};
[email protected]07753f0f2013-02-05 07:52:59456
Avi Drissman22f82872018-12-25 23:09:07457 for (size_t i = 0; i < base::size(test_cases); i++) {
[email protected]b10392932011-03-08 21:28:14458 SCOPED_TRACE(i);
[email protected]a0b60cfd2011-04-06 18:02:41459 EXPECT_EQ(
460 test_cases[i].expected_output,
[email protected]06bfeb12014-05-27 14:00:09461 GetDesktopFileContents(
[email protected]14fbaed2013-05-02 07:54:02462 kChromeExePath,
[email protected]a0b60cfd2011-04-06 18:02:41463 web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)),
Jay Harris05b50bd2019-10-30 01:48:38464 GURL(test_cases[i].url), std::string(),
465 base::ASCIIToUTF16(test_cases[i].title), test_cases[i].icon_name,
466 base::FilePath(), test_cases[i].categories, test_cases[i].mime_type,
Mandy Chend57606f2021-08-31 17:49:10467 test_cases[i].nodisplay, "", {}));
468 }
469}
470
471TEST(ShellIntegrationTest, GetDesktopFileContentsForApps) {
Mandy Chen0cd67e02021-09-09 20:22:12472 base::test::ScopedFeatureList feature_list;
473 feature_list.InitAndEnableFeature(
474 features::kDesktopPWAsAppIconShortcutsMenuUI);
Mandy Chend57606f2021-08-31 17:49:10475 const base::FilePath kChromeExePath("/opt/google/chrome/google-chrome");
476 const struct {
477 const char* const url;
478 const char* const title;
479 const char* const icon_name;
480 bool nodisplay;
481 std::set<web_app::DesktopActionInfo> action_info;
482 const char* const expected_output;
483 } test_cases[] = {
484 // Test Shortcut Menu actions.
485 {"https://example.app",
486 "Lawful example",
487 "IconName",
488 false,
489 {
490 web_app::DesktopActionInfo("action1", "Action 1",
491 GURL("https://example.com/action1")),
492 web_app::DesktopActionInfo("action2", "Action 2",
493 GURL("https://example.com/action2")),
494 web_app::DesktopActionInfo("action3", "Action 3",
495 GURL("https://example.com/action3")),
496 web_app::DesktopActionInfo("action4", "Action 4",
497 GURL("https://example.com/action4")),
498 },
499
500 "#!/usr/bin/env xdg-open\n"
501 "[Desktop Entry]\n"
502 "Version=1.0\n"
503 "Terminal=false\n"
504 "Type=Application\n"
505 "Name=Lawful example\n"
506 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId\n"
507 "Icon=IconName\n"
508 "StartupWMClass=example.app\n"
509 "Actions=action1;action2;action3;action4\n\n"
510 "[Desktop Action action1]\n"
511 "Name=Action 1\n"
512 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
513 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
514 "action1\n\n"
515 "[Desktop Action action2]\n"
516 "Name=Action 2\n"
517 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
518 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
519 "action2\n\n"
520 "[Desktop Action action3]\n"
521 "Name=Action 3\n"
522 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
523 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
524 "action3\n\n"
525 "[Desktop Action action4]\n"
526 "Name=Action 4\n"
527 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
528 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
529 "action4\n"},
530 };
531
532 for (size_t i = 0; i < base::size(test_cases); i++) {
533 SCOPED_TRACE(i);
534 EXPECT_EQ(
535 test_cases[i].expected_output,
536 GetDesktopFileContents(
537 kChromeExePath,
538 web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)),
539 GURL(test_cases[i].url), "TestAppId",
540 base::ASCIIToUTF16(test_cases[i].title), test_cases[i].icon_name,
541 base::FilePath(), "", "", test_cases[i].nodisplay, "",
542 test_cases[i].action_info));
[email protected]b96aa932009-08-12 21:34:49543 }
544}
[email protected]d81a63c02013-03-07 08:49:04545
[email protected]a3c25952013-05-02 13:16:06546TEST(ShellIntegrationTest, GetDirectoryFileContents) {
547 const struct {
thestigd0b662342015-01-16 08:21:19548 const char* const title;
549 const char* const icon_name;
550 const char* const expected_output;
[email protected]a3c25952013-05-02 13:16:06551 } test_cases[] = {
Nico Webereaa08412019-08-14 01:24:37552 // Real-world case.
553 {"Chrome Apps", "chrome-apps",
[email protected]a3c25952013-05-02 13:16:06554
Nico Webereaa08412019-08-14 01:24:37555 "[Desktop Entry]\n"
556 "Version=1.0\n"
557 "Type=Directory\n"
558 "Name=Chrome Apps\n"
559 "Icon=chrome-apps\n"},
[email protected]a3c25952013-05-02 13:16:06560
Nico Webereaa08412019-08-14 01:24:37561 // Make sure that empty icons are replaced by the chrome icon.
562 {"Chrome Apps", "",
[email protected]a3c25952013-05-02 13:16:06563
Nico Webereaa08412019-08-14 01:24:37564 "[Desktop Entry]\n"
565 "Version=1.0\n"
566 "Type=Directory\n"
567 "Name=Chrome Apps\n"
568#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
569 "Icon=google-chrome\n"
[email protected]a7e18f42013-12-19 02:10:37570#else
Nico Webereaa08412019-08-14 01:24:37571 "Icon=chromium-browser\n"
[email protected]a7e18f42013-12-19 02:10:37572#endif
Nico Webereaa08412019-08-14 01:24:37573 },
[email protected]a3c25952013-05-02 13:16:06574 };
575
Avi Drissman22f82872018-12-25 23:09:07576 for (size_t i = 0; i < base::size(test_cases); i++) {
[email protected]a3c25952013-05-02 13:16:06577 SCOPED_TRACE(i);
[email protected]06bfeb12014-05-27 14:00:09578 EXPECT_EQ(test_cases[i].expected_output,
579 GetDirectoryFileContents(base::ASCIIToUTF16(test_cases[i].title),
580 test_cases[i].icon_name));
[email protected]a3c25952013-05-02 13:16:06581 }
582}
[email protected]06bfeb12014-05-27 14:00:09583
Robert Woods954a2182020-03-20 06:08:27584TEST(ShellIntegrationTest, GetMimeTypesRegistrationFilename) {
585 const struct {
586 const char* const profile_path;
587 const char* const app_id;
588 const char* const expected_filename;
589 } test_cases[] = {
590 {"Default", "app-id", "-app-id-Default.xml"},
591 {"Default Profile", "app-id", "-app-id-Default_Profile.xml"},
592 {"foo/Default", "app-id", "-app-id-Default.xml"},
593 {"Default*Profile", "app-id", "-app-id-Default_Profile.xml"}};
594 std::string browser_name(chrome::kBrowserProcessExecutableName);
595
596 for (const auto& test_case : test_cases) {
597 const base::FilePath filename =
598 GetMimeTypesRegistrationFilename(base::FilePath(test_case.profile_path),
599 web_app::AppId(test_case.app_id));
600 EXPECT_EQ(browser_name + test_case.expected_filename, filename.value());
601 }
602}
603
604TEST(ShellIntegrationTest, GetMimeTypesRegistrationFileContents) {
605 apps::FileHandlers file_handlers;
606 {
607 apps::FileHandler file_handler;
608 {
609 apps::FileHandler::AcceptEntry accept_entry;
610 accept_entry.mime_type = "application/foo";
611 accept_entry.file_extensions.insert(".foo");
612 file_handler.accept.push_back(accept_entry);
613 }
Evan Stadeb8f43a42021-09-14 16:25:14614 file_handler.display_name = u"FoO";
Robert Woods954a2182020-03-20 06:08:27615 file_handlers.push_back(file_handler);
616 }
617 {
618 apps::FileHandler file_handler;
619 {
620 apps::FileHandler::AcceptEntry accept_entry;
621 accept_entry.mime_type = "application/foobar";
622 accept_entry.file_extensions.insert(".foobar");
623 file_handler.accept.push_back(accept_entry);
624 }
625 file_handlers.push_back(file_handler);
626 }
627 {
628 apps::FileHandler file_handler;
629 {
630 apps::FileHandler::AcceptEntry accept_entry;
631 accept_entry.mime_type = "application/bar";
Evan Stadeb8f43a42021-09-14 16:25:14632 // A name that has a reserved XML character.
633 file_handler.display_name = u"ba<r";
Robert Woods954a2182020-03-20 06:08:27634 accept_entry.file_extensions.insert(".bar");
635 accept_entry.file_extensions.insert(".baz");
636 file_handler.accept.push_back(accept_entry);
637 }
638 file_handlers.push_back(file_handler);
639 }
640
641 const std::string file_contents =
642 GetMimeTypesRegistrationFileContents(file_handlers);
643 const std::string expected_file_contents =
Evan Stadeb8f43a42021-09-14 16:25:14644 "<?xml version=\"1.0\"?>\n"
Robert Woods954a2182020-03-20 06:08:27645 "<mime-info "
646 "xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n"
Evan Stadeb8f43a42021-09-14 16:25:14647 " <mime-type type=\"application/foo\">\n"
648 " <comment>FoO</comment>\n"
649 " <glob pattern=\"*.foo\"/>\n"
650 " </mime-type>\n"
651 " <mime-type type=\"application/foobar\">\n"
652 " <glob pattern=\"*.foobar\"/>\n"
653 " </mime-type>\n"
654 " <mime-type type=\"application/bar\">\n"
655 " <comment>ba&lt;r</comment>\n"
656 " <glob pattern=\"*.bar\"/>\n"
657 " <glob pattern=\"*.baz\"/>\n"
658 " </mime-type>\n"
Robert Woods954a2182020-03-20 06:08:27659 "</mime-info>\n";
660
661 EXPECT_EQ(file_contents, expected_file_contents);
662}
663
Alexander Dunaev1016e632021-08-10 15:13:34664// The WM class name may be either capitalised or not, depending on the
665// platform.
666void CheckProgramClassClass(const std::string& class_name) {
Maksim Sisov213ddfa2021-09-30 08:18:56667 if (ui::OzonePlatform::GetPlatformNameForTest() == "x11") {
Alexander Dunaev1016e632021-08-10 15:13:34668 EXPECT_EQ("Foo", class_name);
Maksim Sisov213ddfa2021-09-30 08:18:56669 } else {
670 EXPECT_EQ("foo", class_name);
Alexander Dunaev1016e632021-08-10 15:13:34671 }
Alexander Dunaev1016e632021-08-10 15:13:34672}
673
thomasanderson12d87582016-07-29 21:17:41674TEST(ShellIntegrationTest, WmClass) {
675 base::CommandLine command_line((base::FilePath()));
676 EXPECT_EQ("foo", internal::GetProgramClassName(command_line, "foo.desktop"));
Alexander Dunaev1016e632021-08-10 15:13:34677 CheckProgramClassClass(
678 internal::GetProgramClassClass(command_line, "foo.desktop"));
thomasanderson12d87582016-07-29 21:17:41679
680 command_line.AppendSwitchASCII("class", "baR");
681 EXPECT_EQ("foo", internal::GetProgramClassName(command_line, "foo.desktop"));
682 EXPECT_EQ("baR", internal::GetProgramClassClass(command_line, "foo.desktop"));
683
684 command_line = base::CommandLine(base::FilePath());
685 command_line.AppendSwitchASCII("user-data-dir", "/tmp/baz");
686 EXPECT_EQ("foo (/tmp/baz)",
687 internal::GetProgramClassName(command_line, "foo.desktop"));
Alexander Dunaev1016e632021-08-10 15:13:34688 CheckProgramClassClass(
689 internal::GetProgramClassClass(command_line, "foo.desktop"));
thomasanderson12d87582016-07-29 21:17:41690}
691
[email protected]06bfeb12014-05-27 14:00:09692} // namespace shell_integration_linux