Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / shell_integration_linux.cc
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/shell_integration_linux.h"
6
7 #include <fcntl.h>
8 #include <glib.h>
9 #include <stdlib.h>
10 #include <sys/stat.h>
11 #include <sys/types.h>
12 #include <unistd.h>
13
14 #include <string>
15 #include <vector>
16
17 #include "base/base_paths.h"
18 #include "base/command_line.h"
19 #include "base/environment.h"
20 #include "base/file_util.h"
21 #include "base/files/file_enumerator.h"
22 #include "base/files/file_path.h"
23 #include "base/files/scoped_temp_dir.h"
24 #include "base/i18n/file_util_icu.h"
25 #include "base/memory/ref_counted_memory.h"
26 #include "base/memory/scoped_ptr.h"
27 #include "base/message_loop/message_loop.h"
28 #include "base/path_service.h"
29 #include "base/posix/eintr_wrapper.h"
30 #include "base/process/kill.h"
31 #include "base/process/launch.h"
32 #include "base/strings/string_number_conversions.h"
33 #include "base/strings/string_tokenizer.h"
34 #include "base/strings/string_util.h"
35 #include "base/strings/utf_string_conversions.h"
36 #include "base/threading/thread.h"
37 #include "base/threading/thread_restrictions.h"
38 #include "build/build_config.h"
39 #include "chrome/browser/web_applications/web_app.h"
40 #include "chrome/common/chrome_constants.h"
41 #include "chrome/common/chrome_switches.h"
42 #include "chrome/common/chrome_version_info.h"
43 #include "content/public/browser/browser_thread.h"
44 #include "grit/chrome_unscaled_resources.h"
45 #include "ui/base/resource/resource_bundle.h"
46 #include "ui/gfx/image/image_family.h"
47 #include "url/gurl.h"
48
49 using content::BrowserThread;
50
51 namespace {
52
53 // Helper to launch xdg scripts. We don't want them to ask any questions on the
54 // terminal etc. The function returns true if the utility launches and exits
55 // cleanly, in which case |exit_code| returns the utility's exit code.
56 bool LaunchXdgUtility(const std::vector<std::string>& argv, int* exit_code) {
57   // xdg-settings internally runs xdg-mime, which uses mv to move newly-created
58   // files on top of originals after making changes to them. In the event that
59   // the original files are owned by another user (e.g. root, which can happen
60   // if they are updated within sudo), mv will prompt the user to confirm if
61   // standard input is a terminal (otherwise it just does it). So make sure it's
62   // not, to avoid locking everything up waiting for mv.
63   *exit_code = EXIT_FAILURE;
64   int devnull = open("/dev/null", O_RDONLY);
65   if (devnull < 0)
66     return false;
67   base::FileHandleMappingVector no_stdin;
68   no_stdin.push_back(std::make_pair(devnull, STDIN_FILENO));
69
70   base::ProcessHandle handle;
71   base::LaunchOptions options;
72   options.fds_to_remap = &no_stdin;
73   if (!base::LaunchProcess(argv, options, &handle)) {
74     close(devnull);
75     return false;
76   }
77   close(devnull);
78
79   return base::WaitForExitCode(handle, exit_code);
80 }
81
82 std::string CreateShortcutIcon(const gfx::ImageFamily& icon_images,
83                                const base::FilePath& shortcut_filename) {
84   if (icon_images.empty())
85     return std::string();
86
87   // TODO(phajdan.jr): Report errors from this function, possibly as infobars.
88   base::ScopedTempDir temp_dir;
89   if (!temp_dir.CreateUniqueTempDir())
90     return std::string();
91
92   base::FilePath temp_file_path = temp_dir.path().Append(
93       shortcut_filename.ReplaceExtension("png"));
94   std::string icon_name = temp_file_path.BaseName().RemoveExtension().value();
95
96   for (gfx::ImageFamily::const_iterator it = icon_images.begin();
97        it != icon_images.end(); ++it) {
98     int width = it->Width();
99     scoped_refptr<base::RefCountedMemory> png_data = it->As1xPNGBytes();
100     if (png_data->size() == 0) {
101       // If the bitmap could not be encoded to PNG format, skip it.
102       LOG(WARNING) << "Could not encode icon " << icon_name << ".png at size "
103                    << width << ".";
104       continue;
105     }
106     int bytes_written = file_util::WriteFile(temp_file_path,
107                                              png_data->front_as<char>(),
108                                              png_data->size());
109
110     if (bytes_written != static_cast<int>(png_data->size()))
111       return std::string();
112
113     std::vector<std::string> argv;
114     argv.push_back("xdg-icon-resource");
115     argv.push_back("install");
116
117     // Always install in user mode, even if someone runs the browser as root
118     // (people do that).
119     argv.push_back("--mode");
120     argv.push_back("user");
121
122     argv.push_back("--size");
123     argv.push_back(base::IntToString(width));
124
125     argv.push_back(temp_file_path.value());
126     argv.push_back(icon_name);
127     int exit_code;
128     if (!LaunchXdgUtility(argv, &exit_code) || exit_code) {
129       LOG(WARNING) << "Could not install icon " << icon_name << ".png at size "
130                    << width << ".";
131     }
132   }
133   return icon_name;
134 }
135
136 bool CreateShortcutOnDesktop(const base::FilePath& shortcut_filename,
137                              const std::string& contents) {
138   // Make sure that we will later call openat in a secure way.
139   DCHECK_EQ(shortcut_filename.BaseName().value(), shortcut_filename.value());
140
141   base::FilePath desktop_path;
142   if (!PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
143     return false;
144
145   int desktop_fd = open(desktop_path.value().c_str(), O_RDONLY | O_DIRECTORY);
146   if (desktop_fd < 0)
147     return false;
148
149   int fd = openat(desktop_fd, shortcut_filename.value().c_str(),
150                   O_CREAT | O_EXCL | O_WRONLY,
151                   S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
152   if (fd < 0) {
153     if (IGNORE_EINTR(close(desktop_fd)) < 0)
154       PLOG(ERROR) << "close";
155     return false;
156   }
157
158   ssize_t bytes_written = file_util::WriteFileDescriptor(fd, contents.data(),
159                                                          contents.length());
160   if (IGNORE_EINTR(close(fd)) < 0)
161     PLOG(ERROR) << "close";
162
163   if (bytes_written != static_cast<ssize_t>(contents.length())) {
164     // Delete the file. No shortuct is better than corrupted one. Use unlinkat
165     // to make sure we're deleting the file in the directory we think we are.
166     // Even if an attacker manager to put something other at
167     // |shortcut_filename| we'll just undo his action.
168     unlinkat(desktop_fd, shortcut_filename.value().c_str(), 0);
169   }
170
171   if (IGNORE_EINTR(close(desktop_fd)) < 0)
172     PLOG(ERROR) << "close";
173
174   return true;
175 }
176
177 void DeleteShortcutOnDesktop(const base::FilePath& shortcut_filename) {
178   base::FilePath desktop_path;
179   if (PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
180     base::DeleteFile(desktop_path.Append(shortcut_filename), false);
181 }
182
183 // Creates a shortcut with |shortcut_filename| and |contents| in the system
184 // applications menu. If |directory_filename| is non-empty, creates a sub-menu
185 // with |directory_filename| and |directory_contents|, and stores the shortcut
186 // under the sub-menu.
187 bool CreateShortcutInApplicationsMenu(const base::FilePath& shortcut_filename,
188                                       const std::string& contents,
189                                       const base::FilePath& directory_filename,
190                                       const std::string& directory_contents) {
191   base::ScopedTempDir temp_dir;
192   if (!temp_dir.CreateUniqueTempDir())
193     return false;
194
195   base::FilePath temp_directory_path;
196   if (!directory_filename.empty()) {
197     temp_directory_path = temp_dir.path().Append(directory_filename);
198
199     int bytes_written = file_util::WriteFile(temp_directory_path,
200                                              directory_contents.data(),
201                                              directory_contents.length());
202
203     if (bytes_written != static_cast<int>(directory_contents.length()))
204       return false;
205   }
206
207   base::FilePath temp_file_path = temp_dir.path().Append(shortcut_filename);
208
209   int bytes_written = file_util::WriteFile(temp_file_path, contents.data(),
210                                            contents.length());
211
212   if (bytes_written != static_cast<int>(contents.length()))
213     return false;
214
215   std::vector<std::string> argv;
216   argv.push_back("xdg-desktop-menu");
217   argv.push_back("install");
218
219   // Always install in user mode, even if someone runs the browser as root
220   // (people do that).
221   argv.push_back("--mode");
222   argv.push_back("user");
223
224   // If provided, install the shortcut file inside the given directory.
225   if (!directory_filename.empty())
226     argv.push_back(temp_directory_path.value());
227   argv.push_back(temp_file_path.value());
228   int exit_code;
229   LaunchXdgUtility(argv, &exit_code);
230   return exit_code == 0;
231 }
232
233 void DeleteShortcutInApplicationsMenu(
234     const base::FilePath& shortcut_filename,
235     const base::FilePath& directory_filename) {
236   std::vector<std::string> argv;
237   argv.push_back("xdg-desktop-menu");
238   argv.push_back("uninstall");
239
240   // Uninstall in user mode, to match the install.
241   argv.push_back("--mode");
242   argv.push_back("user");
243
244   // The file does not need to exist anywhere - xdg-desktop-menu will uninstall
245   // items from the menu with a matching name.
246   // If |directory_filename| is supplied, this will also remove the item from
247   // the directory, and remove the directory if it is empty.
248   if (!directory_filename.empty())
249     argv.push_back(directory_filename.value());
250   argv.push_back(shortcut_filename.value());
251   int exit_code;
252   LaunchXdgUtility(argv, &exit_code);
253 }
254
255 // Quote a string such that it appears as one verbatim argument for the Exec
256 // key in a desktop file.
257 std::string QuoteArgForDesktopFileExec(const std::string& arg) {
258   // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
259
260   // Quoting is only necessary if the argument has a reserved character.
261   if (arg.find_first_of(" \t\n\"'\\><~|&;$*?#()`") == std::string::npos)
262     return arg;  // No quoting necessary.
263
264   std::string quoted = "\"";
265   for (size_t i = 0; i < arg.size(); ++i) {
266     // Note that the set of backslashed characters is smaller than the
267     // set of reserved characters.
268     switch (arg[i]) {
269       case '"':
270       case '`':
271       case '$':
272       case '\\':
273         quoted += '\\';
274         break;
275     }
276     quoted += arg[i];
277   }
278   quoted += '"';
279
280   return quoted;
281 }
282
283 // Quote a command line so it is suitable for use as the Exec key in a desktop
284 // file. Note: This should be used instead of GetCommandLineString, which does
285 // not properly quote the string; this function is designed for the Exec key.
286 std::string QuoteCommandLineForDesktopFileExec(
287     const CommandLine& command_line) {
288   // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
289
290   std::string quoted_path = "";
291   const CommandLine::StringVector& argv = command_line.argv();
292   for (CommandLine::StringVector::const_iterator i = argv.begin();
293        i != argv.end(); ++i) {
294     if (i != argv.begin())
295       quoted_path += " ";
296     quoted_path += QuoteArgForDesktopFileExec(*i);
297   }
298
299   return quoted_path;
300 }
301
302 const char kDesktopEntry[] = "Desktop Entry";
303
304 const char kXdgOpenShebang[] = "#!/usr/bin/env xdg-open";
305
306 const char kXdgSettings[] = "xdg-settings";
307 const char kXdgSettingsDefaultBrowser[] = "default-web-browser";
308 const char kXdgSettingsDefaultSchemeHandler[] = "default-url-scheme-handler";
309
310 const char kDirectoryFilename[] = "chrome-apps.directory";
311
312 #if defined(GOOGLE_CHROME_BUILD)
313 const char kAppListDesktopName[] = "chrome-app-list";
314 #else  // CHROMIUM_BUILD
315 const char kAppListDesktopName[] = "chromium-app-list";
316 #endif
317
318 }  // namespace
319
320 namespace {
321
322 // Utility function to get the path to the version of a script shipped with
323 // Chrome. |script| gives the name of the script. |chrome_version| returns the
324 // path to the Chrome version of the script, and the return value of the
325 // function is true if the function is successful and the Chrome version is
326 // not the script found on the PATH.
327 bool GetChromeVersionOfScript(const std::string& script,
328                                std::string* chrome_version) {
329   // Get the path to the Chrome version.
330   base::FilePath chrome_dir;
331   if (!PathService::Get(base::DIR_EXE, &chrome_dir))
332     return false;
333
334   base::FilePath chrome_version_path = chrome_dir.Append(script);
335   *chrome_version = chrome_version_path.value();
336
337   // Check if this is different to the one on path.
338   std::vector<std::string> argv;
339   argv.push_back("which");
340   argv.push_back(script);
341   std::string path_version;
342   if (base::GetAppOutput(CommandLine(argv), &path_version)) {
343     // Remove trailing newline
344     path_version.erase(path_version.length() - 1, 1);
345     base::FilePath path_version_path(path_version);
346     return (chrome_version_path != path_version_path);
347   }
348   return false;
349 }
350
351 // Value returned by xdg-settings if it can't understand our request.
352 const int EXIT_XDG_SETTINGS_SYNTAX_ERROR = 1;
353
354 // We delegate the difficulty of setting the default browser and default url
355 // scheme handler in Linux desktop environments to an xdg utility, xdg-settings.
356
357 // When calling this script we first try to use the script on PATH. If that
358 // fails we then try to use the script that we have included. This gives
359 // scripts on the system priority over ours, as distribution vendors may have
360 // tweaked the script, but still allows our copy to be used if the script on the
361 // system fails, as the system copy may be missing capabilities of the Chrome
362 // copy.
363
364 // If |protocol| is empty this function sets Chrome as the default browser,
365 // otherwise it sets Chrome as the default handler application for |protocol|.
366 bool SetDefaultWebClient(const std::string& protocol) {
367 #if defined(OS_CHROMEOS)
368   return true;
369 #else
370   scoped_ptr<base::Environment> env(base::Environment::Create());
371
372   std::vector<std::string> argv;
373   argv.push_back(kXdgSettings);
374   argv.push_back("set");
375   if (protocol.empty()) {
376     argv.push_back(kXdgSettingsDefaultBrowser);
377   } else {
378     argv.push_back(kXdgSettingsDefaultSchemeHandler);
379     argv.push_back(protocol);
380   }
381   argv.push_back(ShellIntegrationLinux::GetDesktopName(env.get()));
382
383   int exit_code;
384   bool ran_ok = LaunchXdgUtility(argv, &exit_code);
385   if (ran_ok && exit_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) {
386     if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
387       ran_ok = LaunchXdgUtility(argv, &exit_code);
388     }
389   }
390
391   return ran_ok && exit_code == EXIT_SUCCESS;
392 #endif
393 }
394
395 // If |protocol| is empty this function checks if Chrome is the default browser,
396 // otherwise it checks if Chrome is the default handler application for
397 // |protocol|.
398 ShellIntegration::DefaultWebClientState GetIsDefaultWebClient(
399     const std::string& protocol) {
400 #if defined(OS_CHROMEOS)
401   return ShellIntegration::UNKNOWN_DEFAULT;
402 #else
403   base::ThreadRestrictions::AssertIOAllowed();
404
405   scoped_ptr<base::Environment> env(base::Environment::Create());
406
407   std::vector<std::string> argv;
408   argv.push_back(kXdgSettings);
409   argv.push_back("check");
410   if (protocol.empty()) {
411     argv.push_back(kXdgSettingsDefaultBrowser);
412   } else {
413     argv.push_back(kXdgSettingsDefaultSchemeHandler);
414     argv.push_back(protocol);
415   }
416   argv.push_back(ShellIntegrationLinux::GetDesktopName(env.get()));
417
418   std::string reply;
419   int success_code;
420   bool ran_ok = base::GetAppOutputWithExitCode(CommandLine(argv), &reply,
421                                                &success_code);
422   if (ran_ok && success_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) {
423     if (GetChromeVersionOfScript(kXdgSettings, &argv[0])) {
424       ran_ok = base::GetAppOutputWithExitCode(CommandLine(argv), &reply,
425                                               &success_code);
426     }
427   }
428
429   if (!ran_ok || success_code != EXIT_SUCCESS) {
430     // xdg-settings failed: we can't determine or set the default browser.
431     return ShellIntegration::UNKNOWN_DEFAULT;
432   }
433
434   // Allow any reply that starts with "yes".
435   return (reply.find("yes") == 0) ? ShellIntegration::IS_DEFAULT :
436                                     ShellIntegration::NOT_DEFAULT;
437 #endif
438 }
439
440 // Get the value of NoDisplay from the [Desktop Entry] section of a .desktop
441 // file, given in |shortcut_contents|. If the key is not found, returns false.
442 bool GetNoDisplayFromDesktopFile(const std::string& shortcut_contents) {
443   // An empty file causes a crash with glib <= 2.32, so special case here.
444   if (shortcut_contents.empty())
445     return false;
446
447   GKeyFile* key_file = g_key_file_new();
448   GError* err = NULL;
449   if (!g_key_file_load_from_data(key_file, shortcut_contents.c_str(),
450                                  shortcut_contents.size(), G_KEY_FILE_NONE,
451                                  &err)) {
452     LOG(WARNING) << "Unable to read desktop file template: " << err->message;
453     g_error_free(err);
454     g_key_file_free(key_file);
455     return false;
456   }
457
458   bool nodisplay = false;
459   char* nodisplay_c_string = g_key_file_get_string(key_file, kDesktopEntry,
460                                                    "NoDisplay", &err);
461   if (nodisplay_c_string) {
462     if (!g_strcmp0(nodisplay_c_string, "true"))
463       nodisplay = true;
464     g_free(nodisplay_c_string);
465   } else {
466     g_error_free(err);
467   }
468
469   g_key_file_free(key_file);
470   return nodisplay;
471 }
472
473 // Gets the path to the Chrome executable or wrapper script.
474 // Returns an empty path if the executable path could not be found.
475 base::FilePath GetChromeExePath() {
476   // Try to get the name of the wrapper script that launched Chrome.
477   scoped_ptr<base::Environment> environment(base::Environment::Create());
478   std::string wrapper_script;
479   if (environment->GetVar("CHROME_WRAPPER", &wrapper_script)) {
480     return base::FilePath(wrapper_script);
481   }
482
483   // Just return the name of the executable path for Chrome.
484   base::FilePath chrome_exe_path;
485   PathService::Get(base::FILE_EXE, &chrome_exe_path);
486   return chrome_exe_path;
487 }
488
489 } // namespace
490
491 // static
492 ShellIntegration::DefaultWebClientSetPermission
493     ShellIntegration::CanSetAsDefaultBrowser() {
494   return SET_DEFAULT_UNATTENDED;
495 }
496
497 // static
498 bool ShellIntegration::SetAsDefaultBrowser() {
499   return SetDefaultWebClient(std::string());
500 }
501
502 // static
503 bool ShellIntegration::SetAsDefaultProtocolClient(const std::string& protocol) {
504   return SetDefaultWebClient(protocol);
505 }
506
507 // static
508 ShellIntegration::DefaultWebClientState ShellIntegration::GetDefaultBrowser() {
509   return GetIsDefaultWebClient(std::string());
510 }
511
512 // static
513 base::string16 ShellIntegration::GetApplicationForProtocol(const GURL& url) {
514   return base::ASCIIToUTF16("xdg-open");
515 }
516
517 // static
518 ShellIntegration::DefaultWebClientState
519 ShellIntegration::IsDefaultProtocolClient(const std::string& protocol) {
520   return GetIsDefaultWebClient(protocol);
521 }
522
523 // static
524 bool ShellIntegration::IsFirefoxDefaultBrowser() {
525   std::vector<std::string> argv;
526   argv.push_back(kXdgSettings);
527   argv.push_back("get");
528   argv.push_back(kXdgSettingsDefaultBrowser);
529
530   std::string browser;
531   // We don't care about the return value here.
532   base::GetAppOutput(CommandLine(argv), &browser);
533   return browser.find("irefox") != std::string::npos;
534 }
535
536 namespace ShellIntegrationLinux {
537
538 bool GetDataWriteLocation(base::Environment* env, base::FilePath* search_path) {
539   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
540
541   std::string xdg_data_home;
542   std::string home;
543   if (env->GetVar("XDG_DATA_HOME", &xdg_data_home) && !xdg_data_home.empty()) {
544     *search_path = base::FilePath(xdg_data_home);
545     return true;
546   } else if (env->GetVar("HOME", &home) && !home.empty()) {
547     *search_path = base::FilePath(home).Append(".local").Append("share");
548     return true;
549   }
550   return false;
551 }
552
553 std::vector<base::FilePath> GetDataSearchLocations(base::Environment* env) {
554   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
555
556   std::vector<base::FilePath> search_paths;
557
558   base::FilePath write_location;
559   if (GetDataWriteLocation(env, &write_location))
560     search_paths.push_back(write_location);
561
562   std::string xdg_data_dirs;
563   if (env->GetVar("XDG_DATA_DIRS", &xdg_data_dirs) && !xdg_data_dirs.empty()) {
564     base::StringTokenizer tokenizer(xdg_data_dirs, ":");
565     while (tokenizer.GetNext()) {
566       base::FilePath data_dir(tokenizer.token());
567       search_paths.push_back(data_dir);
568     }
569   } else {
570     search_paths.push_back(base::FilePath("/usr/local/share"));
571     search_paths.push_back(base::FilePath("/usr/share"));
572   }
573
574   return search_paths;
575 }
576
577 std::string GetProgramClassName() {
578   DCHECK(CommandLine::InitializedForCurrentProcess());
579   // Get the res_name component from argv[0].
580   const CommandLine* command_line = CommandLine::ForCurrentProcess();
581   std::string class_name = command_line->GetProgram().BaseName().value();
582   if (!class_name.empty())
583     class_name[0] = base::ToUpperASCII(class_name[0]);
584   return class_name;
585 }
586
587 std::string GetDesktopName(base::Environment* env) {
588 #if defined(GOOGLE_CHROME_BUILD)
589   return "google-chrome.desktop";
590 #else  // CHROMIUM_BUILD
591   // Allow $CHROME_DESKTOP to override the built-in value, so that development
592   // versions can set themselves as the default without interfering with
593   // non-official, packaged versions using the built-in value.
594   std::string name;
595   if (env->GetVar("CHROME_DESKTOP", &name) && !name.empty())
596     return name;
597   return "chromium-browser.desktop";
598 #endif
599 }
600
601 std::string GetIconName() {
602 #if defined(GOOGLE_CHROME_BUILD)
603   return "google-chrome";
604 #else  // CHROMIUM_BUILD
605   return "chromium-browser";
606 #endif
607 }
608
609 ShellIntegration::ShortcutLocations GetExistingShortcutLocations(
610     base::Environment* env,
611     const base::FilePath& profile_path,
612     const std::string& extension_id) {
613   base::FilePath desktop_path;
614   // If Get returns false, just leave desktop_path empty.
615   PathService::Get(base::DIR_USER_DESKTOP, &desktop_path);
616   return GetExistingShortcutLocations(env, profile_path, extension_id,
617                                       desktop_path);
618 }
619
620 ShellIntegration::ShortcutLocations GetExistingShortcutLocations(
621     base::Environment* env,
622     const base::FilePath& profile_path,
623     const std::string& extension_id,
624     const base::FilePath& desktop_path) {
625   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
626
627   base::FilePath shortcut_filename = GetExtensionShortcutFilename(
628       profile_path, extension_id);
629   DCHECK(!shortcut_filename.empty());
630   ShellIntegration::ShortcutLocations locations;
631
632   // Determine whether there is a shortcut on desktop.
633   if (!desktop_path.empty()) {
634     locations.on_desktop =
635         base::PathExists(desktop_path.Append(shortcut_filename));
636   }
637
638   // Determine whether there is a shortcut in the applications directory.
639   std::string shortcut_contents;
640   if (GetExistingShortcutContents(env, shortcut_filename, &shortcut_contents)) {
641     // Whether this counts as "hidden" or "APP_MENU_LOCATION_SUBDIR_CHROMEAPPS"
642     // depends on whether it contains NoDisplay=true. Since these shortcuts are
643     // for apps, they are always in the "Chrome Apps" directory.
644     if (GetNoDisplayFromDesktopFile(shortcut_contents)) {
645       locations.hidden = true;
646     } else {
647       locations.applications_menu_location =
648           ShellIntegration::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS;
649     }
650   }
651
652   return locations;
653 }
654
655 bool GetExistingShortcutContents(base::Environment* env,
656                                  const base::FilePath& desktop_filename,
657                                  std::string* output) {
658   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
659
660   std::vector<base::FilePath> search_paths = GetDataSearchLocations(env);
661
662   for (std::vector<base::FilePath>::const_iterator i = search_paths.begin();
663        i != search_paths.end(); ++i) {
664     base::FilePath path = i->Append("applications").Append(desktop_filename);
665     VLOG(1) << "Looking for desktop file in " << path.value();
666     if (base::PathExists(path)) {
667       VLOG(1) << "Found desktop file at " << path.value();
668       return base::ReadFileToString(path, output);
669     }
670   }
671
672   return false;
673 }
674
675 base::FilePath GetWebShortcutFilename(const GURL& url) {
676   // Use a prefix, because xdg-desktop-menu requires it.
677   std::string filename =
678       std::string(chrome::kBrowserProcessExecutableName) + "-" + url.spec();
679   file_util::ReplaceIllegalCharactersInPath(&filename, '_');
680
681   base::FilePath desktop_path;
682   if (!PathService::Get(base::DIR_USER_DESKTOP, &desktop_path))
683     return base::FilePath();
684
685   base::FilePath filepath = desktop_path.Append(filename);
686   base::FilePath alternative_filepath(filepath.value() + ".desktop");
687   for (size_t i = 1; i < 100; ++i) {
688     if (base::PathExists(base::FilePath(alternative_filepath))) {
689       alternative_filepath = base::FilePath(
690           filepath.value() + "_" + base::IntToString(i) + ".desktop");
691     } else {
692       return base::FilePath(alternative_filepath).BaseName();
693     }
694   }
695
696   return base::FilePath();
697 }
698
699 base::FilePath GetExtensionShortcutFilename(const base::FilePath& profile_path,
700                                             const std::string& extension_id) {
701   DCHECK(!extension_id.empty());
702
703   // Use a prefix, because xdg-desktop-menu requires it.
704   std::string filename(chrome::kBrowserProcessExecutableName);
705   filename.append("-")
706       .append(extension_id)
707       .append("-")
708       .append(profile_path.BaseName().value());
709   file_util::ReplaceIllegalCharactersInPath(&filename, '_');
710   // Spaces in filenames break xdg-desktop-menu
711   // (see https://bugs.freedesktop.org/show_bug.cgi?id=66605).
712   base::ReplaceChars(filename, " ", "_", &filename);
713   return base::FilePath(filename.append(".desktop"));
714 }
715
716 std::vector<base::FilePath> GetExistingProfileShortcutFilenames(
717     const base::FilePath& profile_path,
718     const base::FilePath& directory) {
719   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
720   // Use a prefix, because xdg-desktop-menu requires it.
721   std::string prefix(chrome::kBrowserProcessExecutableName);
722   prefix.append("-");
723   std::string suffix("-");
724   suffix.append(profile_path.BaseName().value());
725   file_util::ReplaceIllegalCharactersInPath(&suffix, '_');
726   // Spaces in filenames break xdg-desktop-menu
727   // (see https://bugs.freedesktop.org/show_bug.cgi?id=66605).
728   base::ReplaceChars(suffix, " ", "_", &suffix);
729   std::string glob = prefix + "*" + suffix + ".desktop";
730
731   base::FileEnumerator files(directory, false, base::FileEnumerator::FILES,
732                              glob);
733   base::FilePath shortcut_file = files.Next();
734   std::vector<base::FilePath> shortcut_paths;
735   while (!shortcut_file.empty()) {
736     shortcut_paths.push_back(shortcut_file.BaseName());
737     shortcut_file = files.Next();
738   }
739   return shortcut_paths;
740 }
741
742 std::string GetDesktopFileContents(
743     const base::FilePath& chrome_exe_path,
744     const std::string& app_name,
745     const GURL& url,
746     const std::string& extension_id,
747     const base::string16& title,
748     const std::string& icon_name,
749     const base::FilePath& profile_path,
750     bool no_display) {
751   CommandLine cmd_line = ShellIntegration::CommandLineArgsForLauncher(
752       url, extension_id, profile_path);
753   cmd_line.SetProgram(chrome_exe_path);
754   return GetDesktopFileContentsForCommand(cmd_line, app_name, url, title,
755                                           icon_name, no_display);
756 }
757
758 std::string GetDesktopFileContentsForCommand(
759     const CommandLine& command_line,
760     const std::string& app_name,
761     const GURL& url,
762     const base::string16& title,
763     const std::string& icon_name,
764     bool no_display) {
765   // Although not required by the spec, Nautilus on Ubuntu Karmic creates its
766   // launchers with an xdg-open shebang. Follow that convention.
767   std::string output_buffer = std::string(kXdgOpenShebang) + "\n";
768
769   // See http://standards.freedesktop.org/desktop-entry-spec/latest/
770   GKeyFile* key_file = g_key_file_new();
771
772   // Set keys with fixed values.
773   g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
774   g_key_file_set_string(key_file, kDesktopEntry, "Terminal", "false");
775   g_key_file_set_string(key_file, kDesktopEntry, "Type", "Application");
776
777   // Set the "Name" key.
778   std::string final_title = base::UTF16ToUTF8(title);
779   // Make sure no endline characters can slip in and possibly introduce
780   // additional lines (like Exec, which makes it a security risk). Also
781   // use the URL as a default when the title is empty.
782   if (final_title.empty() ||
783       final_title.find("\n") != std::string::npos ||
784       final_title.find("\r") != std::string::npos) {
785     final_title = url.spec();
786   }
787   g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
788
789   // Set the "Exec" key.
790   std::string final_path = QuoteCommandLineForDesktopFileExec(command_line);
791   g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str());
792
793   // Set the "Icon" key.
794   if (!icon_name.empty()) {
795     g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
796   } else {
797     g_key_file_set_string(key_file, kDesktopEntry, "Icon",
798                           GetIconName().c_str());
799   }
800
801   // Set the "NoDisplay" key.
802   if (no_display)
803     g_key_file_set_string(key_file, kDesktopEntry, "NoDisplay", "true");
804
805   std::string wmclass = web_app::GetWMClassFromAppName(app_name);
806   g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass",
807                         wmclass.c_str());
808
809   gsize length = 0;
810   gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
811   if (data_dump) {
812     // If strlen(data_dump[0]) == 0, this check will fail.
813     if (data_dump[0] == '\n') {
814       // Older versions of glib produce a leading newline. If this is the case,
815       // remove it to avoid double-newline after the shebang.
816       output_buffer += (data_dump + 1);
817     } else {
818       output_buffer += data_dump;
819     }
820     g_free(data_dump);
821   }
822
823   g_key_file_free(key_file);
824   return output_buffer;
825 }
826
827 std::string GetDirectoryFileContents(const base::string16& title,
828                                      const std::string& icon_name) {
829   // See http://standards.freedesktop.org/desktop-entry-spec/latest/
830   GKeyFile* key_file = g_key_file_new();
831
832   g_key_file_set_string(key_file, kDesktopEntry, "Version", "1.0");
833   g_key_file_set_string(key_file, kDesktopEntry, "Type", "Directory");
834   std::string final_title = base::UTF16ToUTF8(title);
835   g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str());
836   if (!icon_name.empty()) {
837     g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str());
838   } else {
839     g_key_file_set_string(key_file, kDesktopEntry, "Icon",
840                           GetIconName().c_str());
841   }
842
843   gsize length = 0;
844   gchar* data_dump = g_key_file_to_data(key_file, &length, NULL);
845   std::string output_buffer;
846   if (data_dump) {
847     // If strlen(data_dump[0]) == 0, this check will fail.
848     if (data_dump[0] == '\n') {
849       // Older versions of glib produce a leading newline. If this is the case,
850       // remove it to avoid double-newline after the shebang.
851       output_buffer += (data_dump + 1);
852     } else {
853       output_buffer += data_dump;
854     }
855     g_free(data_dump);
856   }
857
858   g_key_file_free(key_file);
859   return output_buffer;
860 }
861
862 bool CreateDesktopShortcut(
863     const ShellIntegration::ShortcutInfo& shortcut_info,
864     const ShellIntegration::ShortcutLocations& creation_locations) {
865   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
866
867   base::FilePath shortcut_filename;
868   if (!shortcut_info.extension_id.empty()) {
869     shortcut_filename = GetExtensionShortcutFilename(
870         shortcut_info.profile_path, shortcut_info.extension_id);
871     // For extensions we do not want duplicate shortcuts. So, delete any that
872     // already exist and replace them.
873     if (creation_locations.on_desktop)
874       DeleteShortcutOnDesktop(shortcut_filename);
875     // The 'applications_menu_location' and 'hidden' locations are actually the
876     // same place ('applications').
877     if (creation_locations.applications_menu_location !=
878             ShellIntegration::APP_MENU_LOCATION_NONE ||
879         creation_locations.hidden)
880       DeleteShortcutInApplicationsMenu(shortcut_filename, base::FilePath());
881   } else {
882     shortcut_filename = GetWebShortcutFilename(shortcut_info.url);
883   }
884   if (shortcut_filename.empty())
885     return false;
886
887   std::string icon_name =
888       CreateShortcutIcon(shortcut_info.favicon, shortcut_filename);
889
890   std::string app_name =
891       web_app::GenerateApplicationNameFromInfo(shortcut_info);
892
893   bool success = true;
894
895   base::FilePath chrome_exe_path = GetChromeExePath();
896   if (chrome_exe_path.empty()) {
897     LOG(WARNING) << "Could not get executable path.";
898     return false;
899   }
900
901   if (creation_locations.on_desktop) {
902     std::string contents = ShellIntegrationLinux::GetDesktopFileContents(
903         chrome_exe_path,
904         app_name,
905         shortcut_info.url,
906         shortcut_info.extension_id,
907         shortcut_info.title,
908         icon_name,
909         shortcut_info.profile_path,
910         false);
911     success = CreateShortcutOnDesktop(shortcut_filename, contents);
912   }
913
914   if (creation_locations.applications_menu_location !=
915           ShellIntegration::APP_MENU_LOCATION_NONE ||
916       creation_locations.hidden) {
917     base::FilePath directory_filename;
918     std::string directory_contents;
919     switch (creation_locations.applications_menu_location) {
920       case ShellIntegration::APP_MENU_LOCATION_NONE:
921       case ShellIntegration::APP_MENU_LOCATION_ROOT:
922         break;
923       case ShellIntegration::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS:
924         directory_filename = base::FilePath(kDirectoryFilename);
925         directory_contents = ShellIntegrationLinux::GetDirectoryFileContents(
926             ShellIntegration::GetAppShortcutsSubdirName(), "");
927         break;
928       default:
929         NOTREACHED();
930         break;
931     }
932     // Set NoDisplay=true if hidden but not in the applications menu. This will
933     // hide the application from user-facing menus.
934     std::string contents = ShellIntegrationLinux::GetDesktopFileContents(
935         chrome_exe_path,
936         app_name,
937         shortcut_info.url,
938         shortcut_info.extension_id,
939         shortcut_info.title,
940         icon_name,
941         shortcut_info.profile_path,
942         creation_locations.applications_menu_location ==
943             ShellIntegration::APP_MENU_LOCATION_NONE);
944     success = CreateShortcutInApplicationsMenu(
945         shortcut_filename, contents, directory_filename, directory_contents) &&
946         success;
947   }
948
949   return success;
950 }
951
952 bool CreateAppListDesktopShortcut(
953     const std::string& wm_class,
954     const std::string& title) {
955   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
956
957   base::FilePath desktop_name(kAppListDesktopName);
958   base::FilePath shortcut_filename = desktop_name.AddExtension("desktop");
959
960   // We do not want duplicate shortcuts. Delete any that already exist and
961   // replace them.
962   DeleteShortcutInApplicationsMenu(shortcut_filename, base::FilePath());
963
964   base::FilePath chrome_exe_path = GetChromeExePath();
965   if (chrome_exe_path.empty()) {
966     LOG(WARNING) << "Could not get executable path.";
967     return false;
968   }
969
970   gfx::ImageFamily icon_images;
971   ResourceBundle& resource_bundle = ResourceBundle::GetSharedInstance();
972   icon_images.Add(*resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_16));
973   icon_images.Add(*resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_32));
974   icon_images.Add(*resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_48));
975   icon_images.Add(*resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_256));
976   std::string icon_name = CreateShortcutIcon(icon_images, desktop_name);
977
978   CommandLine command_line(chrome_exe_path);
979   command_line.AppendSwitch(switches::kShowAppList);
980   std::string contents = GetDesktopFileContentsForCommand(
981       command_line, wm_class, GURL(), base::UTF8ToUTF16(title), icon_name,
982       false);
983   return CreateShortcutInApplicationsMenu(
984       shortcut_filename, contents, base::FilePath(), "");
985 }
986
987 void DeleteDesktopShortcuts(const base::FilePath& profile_path,
988                             const std::string& extension_id) {
989   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
990
991   base::FilePath shortcut_filename = GetExtensionShortcutFilename(
992       profile_path, extension_id);
993   DCHECK(!shortcut_filename.empty());
994
995   DeleteShortcutOnDesktop(shortcut_filename);
996   // Delete shortcuts from |kDirectoryFilename|.
997   // Note that it is possible that shortcuts were not created in the Chrome Apps
998   // directory. It doesn't matter: this will still delete the shortcut even if
999   // it isn't in the directory.
1000   DeleteShortcutInApplicationsMenu(shortcut_filename,
1001                                    base::FilePath(kDirectoryFilename));
1002 }
1003
1004 void DeleteAllDesktopShortcuts(const base::FilePath& profile_path) {
1005   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
1006
1007   scoped_ptr<base::Environment> env(base::Environment::Create());
1008
1009   // Delete shortcuts from Desktop.
1010   base::FilePath desktop_path;
1011   if (PathService::Get(base::DIR_USER_DESKTOP, &desktop_path)) {
1012     std::vector<base::FilePath> shortcut_filenames_desktop =
1013         GetExistingProfileShortcutFilenames(profile_path, desktop_path);
1014     for (std::vector<base::FilePath>::const_iterator it =
1015          shortcut_filenames_desktop.begin();
1016          it != shortcut_filenames_desktop.end(); ++it) {
1017       DeleteShortcutOnDesktop(*it);
1018     }
1019   }
1020
1021   // Delete shortcuts from |kDirectoryFilename|.
1022   base::FilePath applications_menu;
1023   if (GetDataWriteLocation(env.get(), &applications_menu)) {
1024     applications_menu = applications_menu.AppendASCII("applications");
1025     std::vector<base::FilePath> shortcut_filenames_app_menu =
1026         GetExistingProfileShortcutFilenames(profile_path, applications_menu);
1027     for (std::vector<base::FilePath>::const_iterator it =
1028          shortcut_filenames_app_menu.begin();
1029          it != shortcut_filenames_app_menu.end(); ++it) {
1030       DeleteShortcutInApplicationsMenu(*it,
1031                                        base::FilePath(kDirectoryFilename));
1032     }
1033   }
1034 }
1035
1036 }  // namespace ShellIntegrationLinux