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