blob: 50e1b643987b81b231d00f9426d14ac19cd4920f [file] [log] [blame]
Avi Drissman4a8573c2022-09-09 19:35:541// Copyright 2013 The Chromium Authors
[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"
[email protected]14fbaed2013-05-02 07:54:0224#include "base/test/scoped_path_override.h"
Nico Webereaa08412019-08-14 01:24:3725#include "build/branding_buildflags.h"
Daniel Murphy2ddeccc42022-02-17 02:14:2226#include "chrome/browser/web_applications/os_integration/web_app_shortcut.h"
Song Fangzhen49468b22021-09-02 15:41:5927#include "chrome/browser/web_applications/web_app_helpers.h"
[email protected]42896802009-08-28 23:39:4428#include "chrome/common/chrome_constants.h"
Robert Woods954a2182020-03-20 06:08:2729#include "components/services/app_service/public/cpp/file_handler.h"
Glenn Hartmann37987f52023-09-26 21:06:1730#include "components/webapps/common/web_app_id.h"
Gabriel Charettec7108742019-08-23 03:31:4031#include "content/public/test/browser_task_environment.h"
[email protected]111f0282013-08-05 10:09:2932#include "testing/gmock/include/gmock/gmock.h"
[email protected]b96aa932009-08-12 21:34:4933#include "testing/gtest/include/gtest/gtest.h"
Alexander Dunaev66b50ba2021-08-10 04:11:4634#include "ui/ozone/public/ozone_platform.h"
Eric Willigers05e08652022-02-08 01:59:0935#include "url/gurl.h"
Alexander Dunaev66b50ba2021-08-10 04:11:4636
[email protected]111f0282013-08-05 10:09:2937using ::testing::ElementsAre;
[email protected]631bb742011-11-02 11:29:3938
[email protected]06bfeb12014-05-27 14:00:0939namespace shell_integration_linux {
40
[email protected]af71d642010-03-12 10:29:0441namespace {
42
43// Provides mock environment variables values based on a stored map.
[email protected]76b90d312010-08-03 03:00:5044class MockEnvironment : public base::Environment {
[email protected]af71d642010-03-12 10:29:0445 public:
[email protected]76b90d312010-08-03 03:00:5046 MockEnvironment() {}
[email protected]af71d642010-03-12 10:29:0447
Peter Boströmfadb1752021-09-30 19:17:0148 MockEnvironment(const MockEnvironment&) = delete;
49 MockEnvironment& operator=(const MockEnvironment&) = delete;
50
thestig0c412e852016-06-30 08:04:4051 void Set(base::StringPiece name, const std::string& value) {
Peter Kasting9eb10b02021-04-29 03:10:4852 variables_[std::string(name)] = value;
[email protected]af71d642010-03-12 10:29:0453 }
54
thestig0c412e852016-06-30 08:04:4055 bool GetVar(base::StringPiece variable_name, std::string* result) override {
Peter Kasting9eb10b02021-04-29 03:10:4856 if (base::Contains(variables_, std::string(variable_name))) {
57 *result = variables_[std::string(variable_name)];
[email protected]af71d642010-03-12 10:29:0458 return true;
59 }
60
61 return false;
62 }
63
thestig0c412e852016-06-30 08:04:4064 bool SetVar(base::StringPiece variable_name,
dchenge1bc7982014-10-30 00:32:4065 const std::string& new_value) override {
[email protected]fc586c72010-07-31 16:55:4066 ADD_FAILURE();
67 return false;
68 }
69
thestig0c412e852016-06-30 08:04:4070 bool UnSetVar(base::StringPiece variable_name) override {
[email protected]fc586c72010-07-31 16:55:4071 ADD_FAILURE();
[email protected]e9032c62010-07-16 03:34:2572 return false;
[email protected]ac7264c2010-07-08 13:32:5173 }
74
[email protected]af71d642010-03-12 10:29:0475 private:
76 std::map<std::string, std::string> variables_;
[email protected]af71d642010-03-12 10:29:0477};
78
[email protected]af71d642010-03-12 10:29:0479} // namespace
80
[email protected]d81a63c02013-03-07 08:49:0481TEST(ShellIntegrationTest, GetExistingShortcutContents) {
82 const char kTemplateFilename[] = "shortcut-test.desktop";
83 base::FilePath kTemplateFilepath(kTemplateFilename);
[email protected]af71d642010-03-12 10:29:0484 const char kTestData1[] = "a magical testing string";
85 const char kTestData2[] = "a different testing string";
86
Gabriel Charette798fde72019-08-20 22:24:0487 content::BrowserTaskEnvironment task_environment;
[email protected]af71d642010-03-12 10:29:0488
[email protected]58bf9252013-03-06 04:12:3689 // Test that it searches $XDG_DATA_HOME/applications.
[email protected]af71d642010-03-12 10:29:0490 {
[email protected]ea1a3f62012-11-16 20:34:2391 base::ScopedTempDir temp_dir;
[email protected]af71d642010-03-12 10:29:0492 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
93
[email protected]76b90d312010-08-03 03:00:5094 MockEnvironment env;
vabr8c498ea42016-09-15 12:41:5895 env.Set("XDG_DATA_HOME", temp_dir.GetPath().value());
[email protected]58bf9252013-03-06 04:12:3696 // Create a file in a non-applications directory. This should be ignored.
Lei Zhangeebbef62020-05-05 20:16:0597 ASSERT_TRUE(base::WriteFile(temp_dir.GetPath().Append(kTemplateFilename),
98 kTestData2));
vabr8c498ea42016-09-15 12:41:5899 ASSERT_TRUE(
100 base::CreateDirectory(temp_dir.GetPath().Append("applications")));
Lei Zhangeebbef62020-05-05 20:16:05101 ASSERT_TRUE(base::WriteFile(
vabr8c498ea42016-09-15 12:41:58102 temp_dir.GetPath().Append("applications").Append(kTemplateFilename),
thestigd0b662342015-01-16 08:21:19103 kTestData1));
[email protected]af71d642010-03-12 10:29:04104 std::string contents;
[email protected]d81a63c02013-03-07 08:49:04105 ASSERT_TRUE(
[email protected]06bfeb12014-05-27 14:00:09106 GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
[email protected]af71d642010-03-12 10:29:04107 EXPECT_EQ(kTestData1, contents);
108 }
109
[email protected]58bf9252013-03-06 04:12:36110 // Test that it falls back to $HOME/.local/share/applications.
111 {
112 base::ScopedTempDir temp_dir;
113 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
114
115 MockEnvironment env;
vabr8c498ea42016-09-15 12:41:58116 base::ScopedPathOverride home_override(base::DIR_HOME, temp_dir.GetPath(),
thestigd2b1fcf2015-01-21 22:11:49117 true /* absolute? */,
118 false /* create? */);
[email protected]426d1c92013-12-03 20:08:54119 ASSERT_TRUE(base::CreateDirectory(
vabr8c498ea42016-09-15 12:41:58120 temp_dir.GetPath().Append(".local/share/applications")));
Lei Zhangeebbef62020-05-05 20:16:05121 ASSERT_TRUE(base::WriteFile(temp_dir.GetPath()
122 .Append(".local/share/applications")
123 .Append(kTemplateFilename),
124 kTestData1));
[email protected]58bf9252013-03-06 04:12:36125 std::string contents;
[email protected]d81a63c02013-03-07 08:49:04126 ASSERT_TRUE(
[email protected]06bfeb12014-05-27 14:00:09127 GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
[email protected]58bf9252013-03-06 04:12:36128 EXPECT_EQ(kTestData1, contents);
129 }
130
131 // Test that it searches $XDG_DATA_DIRS/applications.
[email protected]af71d642010-03-12 10:29:04132 {
[email protected]ea1a3f62012-11-16 20:34:23133 base::ScopedTempDir temp_dir;
[email protected]af71d642010-03-12 10:29:04134 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
135
[email protected]76b90d312010-08-03 03:00:50136 MockEnvironment env;
vabr8c498ea42016-09-15 12:41:58137 env.Set("XDG_DATA_DIRS", temp_dir.GetPath().value());
138 ASSERT_TRUE(
139 base::CreateDirectory(temp_dir.GetPath().Append("applications")));
Lei Zhangeebbef62020-05-05 20:16:05140 ASSERT_TRUE(base::WriteFile(
vabr8c498ea42016-09-15 12:41:58141 temp_dir.GetPath().Append("applications").Append(kTemplateFilename),
thestigd0b662342015-01-16 08:21:19142 kTestData2));
[email protected]af71d642010-03-12 10:29:04143 std::string contents;
[email protected]d81a63c02013-03-07 08:49:04144 ASSERT_TRUE(
[email protected]06bfeb12014-05-27 14:00:09145 GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
[email protected]af71d642010-03-12 10:29:04146 EXPECT_EQ(kTestData2, contents);
147 }
148
[email protected]58bf9252013-03-06 04:12:36149 // Test that it searches $X/applications for each X in $XDG_DATA_DIRS.
[email protected]af71d642010-03-12 10:29:04150 {
[email protected]58bf9252013-03-06 04:12:36151 base::ScopedTempDir temp_dir1;
152 ASSERT_TRUE(temp_dir1.CreateUniqueTempDir());
153 base::ScopedTempDir temp_dir2;
154 ASSERT_TRUE(temp_dir2.CreateUniqueTempDir());
[email protected]af71d642010-03-12 10:29:04155
[email protected]76b90d312010-08-03 03:00:50156 MockEnvironment env;
vabr8c498ea42016-09-15 12:41:58157 env.Set("XDG_DATA_DIRS",
158 temp_dir1.GetPath().value() + ":" + temp_dir2.GetPath().value());
[email protected]58bf9252013-03-06 04:12:36159 // Create a file in a non-applications directory. This should be ignored.
Lei Zhangeebbef62020-05-05 20:16:05160 ASSERT_TRUE(base::WriteFile(temp_dir1.GetPath().Append(kTemplateFilename),
161 kTestData1));
[email protected]58bf9252013-03-06 04:12:36162 // Only create a findable desktop file in the second path.
vabr8c498ea42016-09-15 12:41:58163 ASSERT_TRUE(
164 base::CreateDirectory(temp_dir2.GetPath().Append("applications")));
Lei Zhangeebbef62020-05-05 20:16:05165 ASSERT_TRUE(base::WriteFile(
vabr8c498ea42016-09-15 12:41:58166 temp_dir2.GetPath().Append("applications").Append(kTemplateFilename),
thestigd0b662342015-01-16 08:21:19167 kTestData2));
[email protected]af71d642010-03-12 10:29:04168 std::string contents;
[email protected]d81a63c02013-03-07 08:49:04169 ASSERT_TRUE(
[email protected]06bfeb12014-05-27 14:00:09170 GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
[email protected]d81a63c02013-03-07 08:49:04171 EXPECT_EQ(kTestData2, contents);
172 }
173}
174
[email protected]111f0282013-08-05 10:09:29175TEST(ShellIntegrationTest, GetExistingProfileShortcutFilenames) {
176 base::FilePath kProfilePath("a/b/c/Profile Name?");
177 const char kApp1Filename[] = "chrome-extension1-Profile_Name_.desktop";
178 const char kApp2Filename[] = "chrome-extension2-Profile_Name_.desktop";
179 const char kUnrelatedAppFilename[] = "chrome-extension-Other_Profile.desktop";
180
Gabriel Charette798fde72019-08-20 22:24:04181 content::BrowserTaskEnvironment task_environment;
[email protected]111f0282013-08-05 10:09:29182
183 base::ScopedTempDir temp_dir;
184 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
Lei Zhangeebbef62020-05-05 20:16:05185 ASSERT_TRUE(base::WriteFile(temp_dir.GetPath().Append(kApp1Filename), ""));
186 ASSERT_TRUE(base::WriteFile(temp_dir.GetPath().Append(kApp2Filename), ""));
[email protected]111f0282013-08-05 10:09:29187 // This file should not be returned in the results.
Lei Zhangeebbef62020-05-05 20:16:05188 ASSERT_TRUE(
189 base::WriteFile(temp_dir.GetPath().Append(kUnrelatedAppFilename), ""));
[email protected]111f0282013-08-05 10:09:29190 std::vector<base::FilePath> paths =
vabr8c498ea42016-09-15 12:41:58191 GetExistingProfileShortcutFilenames(kProfilePath, temp_dir.GetPath());
[email protected]111f0282013-08-05 10:09:29192 // Path order is arbitrary. Sort the output for consistency.
193 std::sort(paths.begin(), paths.end());
194 EXPECT_THAT(paths,
195 ElementsAre(base::FilePath(kApp1Filename),
196 base::FilePath(kApp2Filename)));
197}
198
[email protected]0b7df36d2012-07-11 09:50:47199TEST(ShellIntegrationTest, GetWebShortcutFilename) {
[email protected]b96aa932009-08-12 21:34:49200 const struct {
thestigd0b662342015-01-16 08:21:19201 const char* const path;
202 const char* const url;
[email protected]b96aa932009-08-12 21:34:49203 } test_cases[] = {
thestigd0b662342015-01-16 08:21:19204 { "http___foo_.desktop", "http://foo" },
205 { "http___foo_bar_.desktop", "http://foo/bar/" },
206 { "http___foo_bar_a=b&c=d.desktop", "http://foo/bar?a=b&c=d" },
[email protected]b96aa932009-08-12 21:34:49207
208 // Now we're starting to be more evil...
thestigd0b662342015-01-16 08:21:19209 { "http___foo_.desktop", "http://foo/bar/baz/../../../../../" },
210 { "http___foo_.desktop", "http://foo/bar/././../baz/././../" },
211 { "http___.._.desktop", "http://../../../../" },
[email protected]b96aa932009-08-12 21:34:49212 };
Daniel Cheng7d9e3d52022-02-26 09:03:24213 for (size_t i = 0; i < std::size(test_cases); i++) {
[email protected]4f260d02010-12-23 18:35:42214 EXPECT_EQ(std::string(chrome::kBrowserProcessExecutableName) + "-" +
[email protected]42896802009-08-28 23:39:44215 test_cases[i].path,
[email protected]06bfeb12014-05-27 14:00:09216 GetWebShortcutFilename(GURL(test_cases[i].url)).value()) <<
[email protected]42896802009-08-28 23:39:44217 " while testing " << test_cases[i].url;
[email protected]b96aa932009-08-12 21:34:49218 }
219}
220
[email protected]a2778d32011-12-01 07:49:34221TEST(ShellIntegrationTest, GetDesktopFileContents) {
[email protected]14fbaed2013-05-02 07:54:02222 const base::FilePath kChromeExePath("/opt/google/chrome/google-chrome");
[email protected]b96aa932009-08-12 21:34:49223 const struct {
thestigd0b662342015-01-16 08:21:19224 const char* const url;
225 const char* const title;
226 const char* const icon_name;
227 const char* const categories;
Jay Harris05b50bd2019-10-30 01:48:38228 const char* const mime_type;
[email protected]d81a63c02013-03-07 08:49:04229 bool nodisplay;
thestigd0b662342015-01-16 08:21:19230 const char* const expected_output;
[email protected]b96aa932009-08-12 21:34:49231 } test_cases[] = {
Nico Webereaa08412019-08-14 01:24:37232 // Real-world case.
Jay Harris05b50bd2019-10-30 01:48:38233 {"http://gmail.com", "GMail", "chrome-http__gmail.com", "", "", false,
[email protected]b96aa932009-08-12 21:34:49234
Nico Webereaa08412019-08-14 01:24:37235 "#!/usr/bin/env xdg-open\n"
236 "[Desktop Entry]\n"
237 "Version=1.0\n"
238 "Terminal=false\n"
239 "Type=Application\n"
240 "Name=GMail\n"
241 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
242 "Icon=chrome-http__gmail.com\n"
243 "StartupWMClass=gmail.com\n"},
[email protected]82810fe12009-09-25 16:21:57244
Nico Webereaa08412019-08-14 01:24:37245 // Make sure that empty icons are replaced by the chrome icon.
Jay Harris05b50bd2019-10-30 01:48:38246 {"http://gmail.com", "GMail", "", "", "", false,
[email protected]0b303cc2009-09-28 22:35:15247
Nico Webereaa08412019-08-14 01:24:37248 "#!/usr/bin/env xdg-open\n"
249 "[Desktop Entry]\n"
250 "Version=1.0\n"
251 "Terminal=false\n"
252 "Type=Application\n"
253 "Name=GMail\n"
254 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
255#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
256 "Icon=google-chrome\n"
[email protected]a7e18f42013-12-19 02:10:37257#else
Nico Webereaa08412019-08-14 01:24:37258 "Icon=chromium-browser\n"
[email protected]a7e18f42013-12-19 02:10:37259#endif
Nico Webereaa08412019-08-14 01:24:37260 "StartupWMClass=gmail.com\n"},
[email protected]0b303cc2009-09-28 22:35:15261
Nico Webereaa08412019-08-14 01:24:37262 // Test adding categories and NoDisplay=true.
263 {"http://gmail.com", "GMail", "chrome-http__gmail.com",
Jay Harris05b50bd2019-10-30 01:48:38264 "Graphics;Education;", "", true,
[email protected]d81a63c02013-03-07 08:49:04265
Nico Webereaa08412019-08-14 01:24:37266 "#!/usr/bin/env xdg-open\n"
267 "[Desktop Entry]\n"
268 "Version=1.0\n"
269 "Terminal=false\n"
270 "Type=Application\n"
271 "Name=GMail\n"
272 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
273 "Icon=chrome-http__gmail.com\n"
274 "Categories=Graphics;Education;\n"
275 "NoDisplay=true\n"
276 "StartupWMClass=gmail.com\n"},
[email protected]d81a63c02013-03-07 08:49:04277
Nico Webereaa08412019-08-14 01:24:37278 // Now we're starting to be more evil...
279 {"http://evil.com/evil --join-the-b0tnet", "Ownz0red\nExec=rm -rf /",
Jay Harris05b50bd2019-10-30 01:48:38280 "chrome-http__evil.com_evil", "", "", false,
[email protected]b96aa932009-08-12 21:34:49281
Nico Webereaa08412019-08-14 01:24:37282 "#!/usr/bin/env xdg-open\n"
283 "[Desktop Entry]\n"
284 "Version=1.0\n"
285 "Terminal=false\n"
286 "Type=Application\n"
287 "Name=http://evil.com/evil%20--join-the-b0tnet\n"
288 "Exec=/opt/google/chrome/google-chrome "
289 "--app=http://evil.com/evil%20--join-the-b0tnet\n"
290 "Icon=chrome-http__evil.com_evil\n"
291 "StartupWMClass=evil.com__evil%20--join-the-b0tnet\n"},
292 {"http://evil.com/evil; rm -rf /; \"; rm -rf $HOME >ownz0red",
Jay Harris05b50bd2019-10-30 01:48:38293 "Innocent Title", "chrome-http__evil.com_evil", "", "", false,
[email protected]82810fe12009-09-25 16:21:57294
Nico Webereaa08412019-08-14 01:24:37295 "#!/usr/bin/env xdg-open\n"
296 "[Desktop Entry]\n"
297 "Version=1.0\n"
298 "Terminal=false\n"
299 "Type=Application\n"
300 "Name=Innocent Title\n"
301 "Exec=/opt/google/chrome/google-chrome "
302 "\"--app=http://evil.com/evil;%20rm%20-rf%20/;%20%22;%20rm%20"
303 // Note: $ is escaped as \$ within an arg to Exec, and then
304 // the \ is escaped as \\ as all strings in a Desktop file should
305 // be; finally, \\ becomes \\\\ when represented in a C++ string!
306 "-rf%20\\\\$HOME%20%3Eownz0red\"\n"
307 "Icon=chrome-http__evil.com_evil\n"
308 "StartupWMClass=evil.com__evil;%20rm%20-rf%20_;%20%22;%20"
309 "rm%20-rf%20$HOME%20%3Eownz0red\n"},
310 {"http://evil.com/evil | cat `echo ownz0red` >/dev/null",
Jay Harris05b50bd2019-10-30 01:48:38311 "Innocent Title", "chrome-http__evil.com_evil", "", "", false,
[email protected]82810fe12009-09-25 16:21:57312
Nico Webereaa08412019-08-14 01:24:37313 "#!/usr/bin/env xdg-open\n"
314 "[Desktop Entry]\n"
315 "Version=1.0\n"
316 "Terminal=false\n"
317 "Type=Application\n"
318 "Name=Innocent Title\n"
319 "Exec=/opt/google/chrome/google-chrome "
320 "--app=http://evil.com/evil%20%7C%20cat%20%60echo%20ownz0red"
321 "%60%20%3E/dev/null\n"
322 "Icon=chrome-http__evil.com_evil\n"
323 "StartupWMClass=evil.com__evil%20%7C%20cat%20%60echo%20ownz0red"
324 "%60%20%3E_dev_null\n"},
Jay Harris05b50bd2019-10-30 01:48:38325 // Test setting mime type
326 {"https://paint.app", "Paint", "chrome-https__paint.app", "Image",
327 "image/png;image/jpg", false,
328
329 "#!/usr/bin/env xdg-open\n"
330 "[Desktop Entry]\n"
331 "Version=1.0\n"
332 "Terminal=false\n"
333 "Type=Application\n"
334 "Name=Paint\n"
Jay Harrisf730b6e2019-11-25 23:49:06335 "MimeType=image/png;image/jpg\n"
Mike Jacksoncfb1bc12021-03-24 01:22:22336 "Exec=/opt/google/chrome/google-chrome --app=https://paint.app/ %U\n"
Jay Harris05b50bd2019-10-30 01:48:38337 "Icon=chrome-https__paint.app\n"
338 "Categories=Image\n"
Jay Harris05b50bd2019-10-30 01:48:38339 "StartupWMClass=paint.app\n"},
340
341 // Test evil mime type.
342 {"https://paint.app", "Evil Paint", "chrome-https__paint.app", "Image",
343 "image/png\nExec=rm -rf /", false,
344
345 "#!/usr/bin/env xdg-open\n"
346 "[Desktop Entry]\n"
347 "Version=1.0\n"
348 "Terminal=false\n"
349 "Type=Application\n"
350 "Name=Evil Paint\n"
351 "Exec=/opt/google/chrome/google-chrome --app=https://paint.app/\n"
352 "Icon=chrome-https__paint.app\n"
353 "Categories=Image\n"
354 "StartupWMClass=paint.app\n"}};
[email protected]07753f0f2013-02-05 07:52:59355
Daniel Cheng7d9e3d52022-02-26 09:03:24356 for (size_t i = 0; i < std::size(test_cases); i++) {
[email protected]b10392932011-03-08 21:28:14357 SCOPED_TRACE(i);
[email protected]a0b60cfd2011-04-06 18:02:41358 EXPECT_EQ(
359 test_cases[i].expected_output,
[email protected]06bfeb12014-05-27 14:00:09360 GetDesktopFileContents(
[email protected]14fbaed2013-05-02 07:54:02361 kChromeExePath,
[email protected]a0b60cfd2011-04-06 18:02:41362 web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)),
Jay Harris05b50bd2019-10-30 01:48:38363 GURL(test_cases[i].url), std::string(),
364 base::ASCIIToUTF16(test_cases[i].title), test_cases[i].icon_name,
365 base::FilePath(), test_cases[i].categories, test_cases[i].mime_type,
Mandy Chend57606f2021-08-31 17:49:10366 test_cases[i].nodisplay, "", {}));
367 }
368}
369
370TEST(ShellIntegrationTest, GetDesktopFileContentsForApps) {
371 const base::FilePath kChromeExePath("/opt/google/chrome/google-chrome");
372 const struct {
373 const char* const url;
374 const char* const title;
375 const char* const icon_name;
376 bool nodisplay;
377 std::set<web_app::DesktopActionInfo> action_info;
378 const char* const expected_output;
379 } test_cases[] = {
380 // Test Shortcut Menu actions.
381 {"https://example.app",
382 "Lawful example",
383 "IconName",
384 false,
385 {
386 web_app::DesktopActionInfo("action1", "Action 1",
387 GURL("https://example.com/action1")),
388 web_app::DesktopActionInfo("action2", "Action 2",
389 GURL("https://example.com/action2")),
390 web_app::DesktopActionInfo("action3", "Action 3",
391 GURL("https://example.com/action3")),
392 web_app::DesktopActionInfo("action4", "Action 4",
393 GURL("https://example.com/action4")),
Mandy Chen5de2f242022-01-12 00:30:21394 web_app::DesktopActionInfo("action5", "Action 5",
395 GURL("https://example.com/action%205")),
Mandy Chend57606f2021-08-31 17:49:10396 },
397
398 "#!/usr/bin/env xdg-open\n"
399 "[Desktop Entry]\n"
400 "Version=1.0\n"
401 "Terminal=false\n"
402 "Type=Application\n"
403 "Name=Lawful example\n"
404 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId\n"
405 "Icon=IconName\n"
406 "StartupWMClass=example.app\n"
Mandy Chen5de2f242022-01-12 00:30:21407 "Actions=action1;action2;action3;action4;action5\n\n"
Mandy Chend57606f2021-08-31 17:49:10408 "[Desktop Action action1]\n"
409 "Name=Action 1\n"
410 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
411 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
412 "action1\n\n"
413 "[Desktop Action action2]\n"
414 "Name=Action 2\n"
415 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
416 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
417 "action2\n\n"
418 "[Desktop Action action3]\n"
419 "Name=Action 3\n"
420 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
421 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
422 "action3\n\n"
423 "[Desktop Action action4]\n"
424 "Name=Action 4\n"
425 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
426 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
Mandy Chen5de2f242022-01-12 00:30:21427 "action4\n\n"
428 "[Desktop Action action5]\n"
429 "Name=Action 5\n"
430 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
431 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
432 "action%%205\n"},
Mandy Chend57606f2021-08-31 17:49:10433 };
434
Daniel Cheng7d9e3d52022-02-26 09:03:24435 for (size_t i = 0; i < std::size(test_cases); i++) {
Mandy Chend57606f2021-08-31 17:49:10436 SCOPED_TRACE(i);
437 EXPECT_EQ(
438 test_cases[i].expected_output,
439 GetDesktopFileContents(
440 kChromeExePath,
441 web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)),
442 GURL(test_cases[i].url), "TestAppId",
443 base::ASCIIToUTF16(test_cases[i].title), test_cases[i].icon_name,
444 base::FilePath(), "", "", test_cases[i].nodisplay, "",
445 test_cases[i].action_info));
[email protected]b96aa932009-08-12 21:34:49446 }
447}
[email protected]d81a63c02013-03-07 08:49:04448
[email protected]a3c25952013-05-02 13:16:06449TEST(ShellIntegrationTest, GetDirectoryFileContents) {
450 const struct {
thestigd0b662342015-01-16 08:21:19451 const char* const title;
452 const char* const icon_name;
453 const char* const expected_output;
[email protected]a3c25952013-05-02 13:16:06454 } test_cases[] = {
Nico Webereaa08412019-08-14 01:24:37455 // Real-world case.
456 {"Chrome Apps", "chrome-apps",
[email protected]a3c25952013-05-02 13:16:06457
Nico Webereaa08412019-08-14 01:24:37458 "[Desktop Entry]\n"
459 "Version=1.0\n"
460 "Type=Directory\n"
461 "Name=Chrome Apps\n"
462 "Icon=chrome-apps\n"},
[email protected]a3c25952013-05-02 13:16:06463
Nico Webereaa08412019-08-14 01:24:37464 // Make sure that empty icons are replaced by the chrome icon.
465 {"Chrome Apps", "",
[email protected]a3c25952013-05-02 13:16:06466
Nico Webereaa08412019-08-14 01:24:37467 "[Desktop Entry]\n"
468 "Version=1.0\n"
469 "Type=Directory\n"
470 "Name=Chrome Apps\n"
471#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
472 "Icon=google-chrome\n"
[email protected]a7e18f42013-12-19 02:10:37473#else
Nico Webereaa08412019-08-14 01:24:37474 "Icon=chromium-browser\n"
[email protected]a7e18f42013-12-19 02:10:37475#endif
Nico Webereaa08412019-08-14 01:24:37476 },
[email protected]a3c25952013-05-02 13:16:06477 };
478
Daniel Cheng7d9e3d52022-02-26 09:03:24479 for (size_t i = 0; i < std::size(test_cases); i++) {
[email protected]a3c25952013-05-02 13:16:06480 SCOPED_TRACE(i);
[email protected]06bfeb12014-05-27 14:00:09481 EXPECT_EQ(test_cases[i].expected_output,
482 GetDirectoryFileContents(base::ASCIIToUTF16(test_cases[i].title),
483 test_cases[i].icon_name));
[email protected]a3c25952013-05-02 13:16:06484 }
485}
[email protected]06bfeb12014-05-27 14:00:09486
Robert Woods954a2182020-03-20 06:08:27487TEST(ShellIntegrationTest, GetMimeTypesRegistrationFilename) {
488 const struct {
489 const char* const profile_path;
490 const char* const app_id;
491 const char* const expected_filename;
492 } test_cases[] = {
493 {"Default", "app-id", "-app-id-Default.xml"},
494 {"Default Profile", "app-id", "-app-id-Default_Profile.xml"},
495 {"foo/Default", "app-id", "-app-id-Default.xml"},
496 {"Default*Profile", "app-id", "-app-id-Default_Profile.xml"}};
497 std::string browser_name(chrome::kBrowserProcessExecutableName);
498
499 for (const auto& test_case : test_cases) {
500 const base::FilePath filename =
501 GetMimeTypesRegistrationFilename(base::FilePath(test_case.profile_path),
Glenn Hartmann37987f52023-09-26 21:06:17502 webapps::AppId(test_case.app_id));
Robert Woods954a2182020-03-20 06:08:27503 EXPECT_EQ(browser_name + test_case.expected_filename, filename.value());
504 }
505}
506
507TEST(ShellIntegrationTest, GetMimeTypesRegistrationFileContents) {
508 apps::FileHandlers file_handlers;
509 {
510 apps::FileHandler file_handler;
511 {
512 apps::FileHandler::AcceptEntry accept_entry;
513 accept_entry.mime_type = "application/foo";
514 accept_entry.file_extensions.insert(".foo");
515 file_handler.accept.push_back(accept_entry);
516 }
Evan Stadeb8f43a42021-09-14 16:25:14517 file_handler.display_name = u"FoO";
Robert Woods954a2182020-03-20 06:08:27518 file_handlers.push_back(file_handler);
519 }
520 {
521 apps::FileHandler file_handler;
522 {
523 apps::FileHandler::AcceptEntry accept_entry;
524 accept_entry.mime_type = "application/foobar";
525 accept_entry.file_extensions.insert(".foobar");
526 file_handler.accept.push_back(accept_entry);
527 }
528 file_handlers.push_back(file_handler);
529 }
530 {
531 apps::FileHandler file_handler;
532 {
533 apps::FileHandler::AcceptEntry accept_entry;
534 accept_entry.mime_type = "application/bar";
Evan Stadeb8f43a42021-09-14 16:25:14535 // A name that has a reserved XML character.
536 file_handler.display_name = u"ba<r";
Robert Woods954a2182020-03-20 06:08:27537 accept_entry.file_extensions.insert(".bar");
538 accept_entry.file_extensions.insert(".baz");
539 file_handler.accept.push_back(accept_entry);
540 }
541 file_handlers.push_back(file_handler);
542 }
543
544 const std::string file_contents =
545 GetMimeTypesRegistrationFileContents(file_handlers);
546 const std::string expected_file_contents =
Evan Stadeb8f43a42021-09-14 16:25:14547 "<?xml version=\"1.0\"?>\n"
Robert Woods954a2182020-03-20 06:08:27548 "<mime-info "
549 "xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n"
Evan Stadeb8f43a42021-09-14 16:25:14550 " <mime-type type=\"application/foo\">\n"
551 " <comment>FoO</comment>\n"
552 " <glob pattern=\"*.foo\"/>\n"
553 " </mime-type>\n"
554 " <mime-type type=\"application/foobar\">\n"
555 " <glob pattern=\"*.foobar\"/>\n"
556 " </mime-type>\n"
557 " <mime-type type=\"application/bar\">\n"
558 " <comment>ba&lt;r</comment>\n"
559 " <glob pattern=\"*.bar\"/>\n"
560 " <glob pattern=\"*.baz\"/>\n"
561 " </mime-type>\n"
Robert Woods954a2182020-03-20 06:08:27562 "</mime-info>\n";
563
564 EXPECT_EQ(file_contents, expected_file_contents);
565}
566
Alexander Dunaev1016e632021-08-10 15:13:34567// The WM class name may be either capitalised or not, depending on the
568// platform.
569void CheckProgramClassClass(const std::string& class_name) {
Maksim Sisov213ddfa2021-09-30 08:18:56570 if (ui::OzonePlatform::GetPlatformNameForTest() == "x11") {
Alexander Dunaev1016e632021-08-10 15:13:34571 EXPECT_EQ("Foo", class_name);
Maksim Sisov213ddfa2021-09-30 08:18:56572 } else {
573 EXPECT_EQ("foo", class_name);
Alexander Dunaev1016e632021-08-10 15:13:34574 }
Alexander Dunaev1016e632021-08-10 15:13:34575}
576
thomasanderson12d87582016-07-29 21:17:41577TEST(ShellIntegrationTest, WmClass) {
578 base::CommandLine command_line((base::FilePath()));
579 EXPECT_EQ("foo", internal::GetProgramClassName(command_line, "foo.desktop"));
Alexander Dunaev1016e632021-08-10 15:13:34580 CheckProgramClassClass(
581 internal::GetProgramClassClass(command_line, "foo.desktop"));
thomasanderson12d87582016-07-29 21:17:41582
583 command_line.AppendSwitchASCII("class", "baR");
584 EXPECT_EQ("foo", internal::GetProgramClassName(command_line, "foo.desktop"));
585 EXPECT_EQ("baR", internal::GetProgramClassClass(command_line, "foo.desktop"));
586
587 command_line = base::CommandLine(base::FilePath());
588 command_line.AppendSwitchASCII("user-data-dir", "/tmp/baz");
589 EXPECT_EQ("foo (/tmp/baz)",
590 internal::GetProgramClassName(command_line, "foo.desktop"));
Alexander Dunaev1016e632021-08-10 15:13:34591 CheckProgramClassClass(
592 internal::GetProgramClassClass(command_line, "foo.desktop"));
thomasanderson12d87582016-07-29 21:17:41593}
594
Mike Jackson0cb0ba72022-09-26 23:22:21595TEST(ShellIntegrationTest, GetDesktopEntryStringValueFromFromDesktopFile) {
596 const char* const kDesktopFileContents =
597 "#!/usr/bin/env xdg-open\n"
598 "[Desktop Entry]\n"
599 "Version=1.0\n"
600 "Terminal=false\n"
601 "Type=Application\n"
602 "Name=Lawful example\n"
603 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId\n"
604 "Icon=IconName\n"
605 "StartupWMClass=example.app\n"
606 "Actions=action1\n\n"
607 "[Desktop Action action1]\n"
608 "Name=Action 1\n"
609 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId --Test"
610 "Action1=Value";
611
612 // Verify basic strings return the right value.
613 EXPECT_EQ("Lawful example",
614 shell_integration_linux::internal::
615 GetDesktopEntryStringValueFromFromDesktopFileForTest(
616 "Name", kDesktopFileContents));
617 EXPECT_EQ("example.app",
618 shell_integration_linux::internal::
619 GetDesktopEntryStringValueFromFromDesktopFileForTest(
620 "StartupWMClass", kDesktopFileContents));
621 // Verify that booleans are returned correctly.
622 EXPECT_EQ("false", shell_integration_linux::internal::
623 GetDesktopEntryStringValueFromFromDesktopFileForTest(
624 "Terminal", kDesktopFileContents));
625 // Verify that numbers are returned correctly.
626 EXPECT_EQ("1.0", shell_integration_linux::internal::
627 GetDesktopEntryStringValueFromFromDesktopFileForTest(
628 "Version", kDesktopFileContents));
629 // Verify that a non-existent key returns an empty string.
630 EXPECT_EQ("", shell_integration_linux::internal::
631 GetDesktopEntryStringValueFromFromDesktopFileForTest(
632 "DoesNotExistKey", kDesktopFileContents));
633 // Verify that a non-existent key in [Desktop Entry] section returns an empty
634 // string.
635 EXPECT_EQ("", shell_integration_linux::internal::
636 GetDesktopEntryStringValueFromFromDesktopFileForTest(
637 "Action1", kDesktopFileContents));
638}
639
[email protected]06bfeb12014-05-27 14:00:09640} // namespace shell_integration_linux