blob: 48abd49dd98b374993e0f78077e6261039e6d22c [file] [log] [blame]
[email protected]ef525cc2009-07-10 17:08:161// Copyright (c) 2006-2009 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.h"
6
[email protected]6584f0b12009-07-20 23:38:147#include <fcntl.h>
[email protected]ef525cc2009-07-10 17:08:168#include <stdlib.h>
[email protected]6584f0b12009-07-20 23:38:149#include <sys/stat.h>
10#include <sys/types.h>
11#include <unistd.h>
[email protected]ef525cc2009-07-10 17:08:1612
13#include <vector>
14
[email protected]b96aa932009-08-12 21:34:4915#include "base/file_path.h"
16#include "base/file_util.h"
17#include "base/message_loop.h"
18#include "base/path_service.h"
[email protected]ef525cc2009-07-10 17:08:1619#include "base/process_util.h"
[email protected]b96aa932009-08-12 21:34:4920#include "base/string_tokenizer.h"
21#include "base/string_util.h"
22#include "base/task.h"
23#include "base/thread.h"
24#include "chrome/browser/browser_process.h"
25#include "chrome/common/chrome_paths.h"
26#include "googleurl/src/gurl.h"
[email protected]ef525cc2009-07-10 17:08:1627
[email protected]b96aa932009-08-12 21:34:4928namespace {
29
30const char* GetDesktopName() {
[email protected]ef525cc2009-07-10 17:08:1631#if defined(GOOGLE_CHROME_BUILD)
[email protected]cdeb0a602009-07-21 01:29:0432 return "google-chrome.desktop";
[email protected]ef525cc2009-07-10 17:08:1633#else // CHROMIUM_BUILD
[email protected]cdeb0a602009-07-21 01:29:0434 static const char* name = NULL;
35 if (!name) {
36 // Allow $CHROME_DESKTOP to override the built-in value, so that development
37 // versions can set themselves as the default without interfering with
38 // non-official, packaged versions using the built-in value.
39 name = getenv("CHROME_DESKTOP");
40 if (!name)
41 name = "chromium-browser.desktop";
42 }
43 return name;
[email protected]ef525cc2009-07-10 17:08:1644#endif
[email protected]cdeb0a602009-07-21 01:29:0445}
[email protected]ef525cc2009-07-10 17:08:1646
[email protected]b96aa932009-08-12 21:34:4947bool GetDesktopShortcutTemplate(std::string* output) {
48 std::vector<std::string> search_paths;
49
50 const char* xdg_data_home = getenv("XDG_DATA_HOME");
51 if (xdg_data_home)
52 search_paths.push_back(xdg_data_home);
53
54 const char* xdg_data_dirs = getenv("XDG_DATA_DIRS");
55 if (xdg_data_dirs) {
56 StringTokenizer tokenizer(xdg_data_dirs, ":");
57 while (tokenizer.GetNext()) {
58 search_paths.push_back(tokenizer.token());
59 }
60 }
61
62 std::string template_filename(GetDesktopName());
63 for (std::vector<std::string>::const_iterator i = search_paths.begin();
64 i != search_paths.end(); ++i) {
65 FilePath path = FilePath(*i).Append(template_filename);
66 if (file_util::PathExists(path))
67 return file_util::ReadFileToString(path, output);
68 }
69
70 return false;
71}
72
73class CreateDesktopShortcutTask : public Task {
74 public:
75 CreateDesktopShortcutTask(const GURL& url, const string16& title)
76 : url_(url),
77 title_(title) {
78 }
79
80 virtual void Run() {
81 // TODO(phajdan.jr): Report errors from this function, possibly as infobars.
82 FilePath desktop_path;
83 if (!PathService::Get(chrome::DIR_USER_DESKTOP, &desktop_path))
84 return;
85 desktop_path =
86 desktop_path.Append(ShellIntegration::GetDesktopShortcutFilename(url_));
87
88 if (file_util::PathExists(desktop_path))
89 return;
90
91 std::string template_contents;
92 if (!GetDesktopShortcutTemplate(&template_contents))
93 return;
94
95 std::string contents = ShellIntegration::GetDesktopFileContents(
96 template_contents, url_, title_);
97 int bytes_written = file_util::WriteFile(desktop_path, contents.data(),
98 contents.length());
99 if (bytes_written != static_cast<int>(contents.length())) {
100 file_util::Delete(desktop_path, false);
101 }
102 }
103
104 private:
105 const GURL url_; // URL of the web application.
106 const string16 title_; // Title displayed to the user.
107
108 DISALLOW_COPY_AND_ASSIGN(CreateDesktopShortcutTask);
109};
110
111} // namespace
112
[email protected]ef525cc2009-07-10 17:08:16113// We delegate the difficult of setting the default browser in Linux desktop
114// environments to a new xdg utility, xdg-settings. We'll have to include a copy
115// of it for this to work, obviously, but that's actually the suggested approach
116// for xdg utilities anyway.
117
118bool ShellIntegration::SetAsDefaultBrowser() {
119 std::vector<std::string> argv;
120 argv.push_back("xdg-settings");
121 argv.push_back("set");
122 argv.push_back("default-web-browser");
[email protected]cdeb0a602009-07-21 01:29:04123 argv.push_back(GetDesktopName());
[email protected]ef525cc2009-07-10 17:08:16124
[email protected]6584f0b12009-07-20 23:38:14125 // xdg-settings internally runs xdg-mime, which uses mv to move newly-created
126 // files on top of originals after making changes to them. In the event that
127 // the original files are owned by another user (e.g. root, which can happen
128 // if they are updated within sudo), mv will prompt the user to confirm if
129 // standard input is a terminal (otherwise it just does it). So make sure it's
130 // not, to avoid locking everything up waiting for mv.
131 int devnull = open("/dev/null", O_RDONLY);
132 if (devnull < 0)
[email protected]ef525cc2009-07-10 17:08:16133 return false;
[email protected]6584f0b12009-07-20 23:38:14134 base::file_handle_mapping_vector no_stdin;
135 no_stdin.push_back(std::make_pair(devnull, STDIN_FILENO));
[email protected]ef525cc2009-07-10 17:08:16136
[email protected]6584f0b12009-07-20 23:38:14137 base::ProcessHandle handle;
138 if (!base::LaunchApp(argv, no_stdin, false, &handle)) {
139 close(devnull);
140 return false;
141 }
142 close(devnull);
143
144 int success_code;
[email protected]ef525cc2009-07-10 17:08:16145 base::WaitForExitCode(handle, &success_code);
146 return success_code == EXIT_SUCCESS;
147}
148
[email protected]7061b122009-07-22 02:24:35149bool ShellIntegration::IsDefaultBrowser() {
150 std::vector<std::string> argv;
151 argv.push_back("xdg-settings");
152 argv.push_back("check");
153 argv.push_back("default-web-browser");
154 argv.push_back(GetDesktopName());
155
156 std::string reply;
157 if (!base::GetAppOutput(CommandLine(argv), &reply)) {
158 // If xdg-settings fails, we assume that we should pretend we're the default
159 // browser to avoid giving repeated prompts to set ourselves as the default.
160 // TODO(mdm): Really, being the default browser should be a ternary query:
161 // yes, no, and "don't know" so the UI can reflect this more accurately.
162 return true;
163 }
164
165 // Allow any reply that starts with "yes".
166 return reply.find("yes") == 0;
167}
168
169bool ShellIntegration::IsFirefoxDefaultBrowser() {
[email protected]ef525cc2009-07-10 17:08:16170 std::vector<std::string> argv;
171 argv.push_back("xdg-settings");
172 argv.push_back("get");
173 argv.push_back("default-web-browser");
[email protected]ef525cc2009-07-10 17:08:16174
[email protected]6d335192009-07-20 20:50:45175 std::string browser;
176 // We don't care about the return value here.
[email protected]7061b122009-07-22 02:24:35177 base::GetAppOutput(CommandLine(argv), &browser);
[email protected]6d335192009-07-20 20:50:45178 return browser.find("irefox") != std::string::npos;
[email protected]ef525cc2009-07-10 17:08:16179}
[email protected]b96aa932009-08-12 21:34:49180
181FilePath ShellIntegration::GetDesktopShortcutFilename(const GURL& url) {
182 std::wstring filename = UTF8ToWide(url.spec()) + L".desktop";
183 file_util::ReplaceIllegalCharacters(&filename, '_');
184
185 // Return BaseName to be absolutely sure we're not vulnerable to a directory
186 // traversal attack.
187 return FilePath::FromWStringHack(filename).BaseName();
188}
189
190std::string ShellIntegration::GetDesktopFileContents(
191 const std::string& template_contents, const GURL& url,
192 const string16& title) {
193 // See http://standards.freedesktop.org/desktop-entry-spec/latest/
194 std::string output_buffer;
195 StringTokenizer tokenizer(template_contents, "\n");
196 while (tokenizer.GetNext()) {
197 // TODO(phajdan.jr): Add the icon.
198
199 if (tokenizer.token().substr(0, 5) == "Exec=") {
200 std::string exec_path = tokenizer.token().substr(5);
201 StringTokenizer exec_tokenizer(exec_path, " ");
202 std::string final_path;
203 while (exec_tokenizer.GetNext()) {
204 if (exec_tokenizer.token() != "%U")
205 final_path += exec_tokenizer.token() + " ";
206 }
207 std::string app_switch(StringPrintf("\"--app=%s\"",
208 url.spec().c_str()));
209 ReplaceSubstringsAfterOffset(&app_switch, 0, "%", "%%");
210 output_buffer += std::string("Exec=") + final_path + app_switch + "\n";
211 } else if (tokenizer.token().substr(0, 5) == "Name=") {
212 std::string final_title = UTF16ToUTF8(title);
213 // Make sure no endline characters can slip in and possibly introduce
214 // additional lines (like Exec, which makes it a security risk). Also
215 // use the URL as a default when the title is empty.
216 if (final_title.empty() ||
217 final_title.find("\n") != std::string::npos ||
218 final_title.find("\r") != std::string::npos)
219 final_title = url.spec();
220 output_buffer += StringPrintf("Name=%s\n", final_title.c_str());
221 } else if (tokenizer.token().substr(0, 8) == "Comment=") {
222 // Skip the line.
223 } else {
224 output_buffer += tokenizer.token() + "\n";
225 }
226 }
227 return output_buffer;
228}
229
230void ShellIntegration::CreateDesktopShortcut(const GURL& url,
231 const string16& title) {
232 g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE,
233 new CreateDesktopShortcutTask(url, title));
234}