| OLD | NEW |
| 1 // Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. | 1 // 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/shell_integration.h" | 5 #include "chrome/browser/shell_integration.h" |
| 6 | 6 |
| 7 #include <fcntl.h> | 7 #include <fcntl.h> |
| 8 #include <stdlib.h> | 8 #include <stdlib.h> |
| 9 #include <sys/stat.h> | 9 #include <sys/stat.h> |
| 10 #include <sys/types.h> | 10 #include <sys/types.h> |
| 11 #include <unistd.h> | 11 #include <unistd.h> |
| 12 | 12 |
| 13 #include <string> | 13 #include <string> |
| 14 #include <vector> | 14 #include <vector> |
| 15 | 15 |
| 16 #include "base/command_line.h" | 16 #include "base/command_line.h" |
| 17 #include "base/eintr_wrapper.h" |
| 17 #include "base/file_path.h" | 18 #include "base/file_path.h" |
| 18 #include "base/file_util.h" | 19 #include "base/file_util.h" |
| 19 #include "base/message_loop.h" | 20 #include "base/message_loop.h" |
| 20 #include "base/path_service.h" | 21 #include "base/path_service.h" |
| 21 #include "base/process_util.h" | 22 #include "base/process_util.h" |
| 22 #include "base/scoped_temp_dir.h" | 23 #include "base/scoped_temp_dir.h" |
| 23 #include "base/string_tokenizer.h" | 24 #include "base/string_tokenizer.h" |
| 24 #include "base/string_util.h" | 25 #include "base/string_util.h" |
| 25 #include "base/task.h" | 26 #include "base/task.h" |
| 26 #include "base/thread.h" | 27 #include "base/thread.h" |
| (...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 110 CreateDesktopShortcutTask(const ShellIntegration::ShortcutInfo& shortcut_info) | 111 CreateDesktopShortcutTask(const ShellIntegration::ShortcutInfo& shortcut_info) |
| 111 : shortcut_info_(shortcut_info) { | 112 : shortcut_info_(shortcut_info) { |
| 112 } | 113 } |
| 113 | 114 |
| 114 virtual void Run() { | 115 virtual void Run() { |
| 115 // TODO(phajdan.jr): Report errors from this function, possibly as infobars. | 116 // TODO(phajdan.jr): Report errors from this function, possibly as infobars. |
| 116 std::string template_contents; | 117 std::string template_contents; |
| 117 if (!GetDesktopShortcutTemplate(&template_contents)) | 118 if (!GetDesktopShortcutTemplate(&template_contents)) |
| 118 return; | 119 return; |
| 119 | 120 |
| 120 std::string contents = ShellIntegration::GetDesktopFileContents( | |
| 121 template_contents, shortcut_info_.url, shortcut_info_.title); | |
| 122 | |
| 123 ScopedTempDir temp_dir; | |
| 124 if (!temp_dir.CreateUniqueTempDir()) | |
| 125 return; | |
| 126 | |
| 127 FilePath shortcut_filename = | 121 FilePath shortcut_filename = |
| 128 ShellIntegration::GetDesktopShortcutFilename(shortcut_info_.url); | 122 ShellIntegration::GetDesktopShortcutFilename(shortcut_info_.url); |
| 129 | 123 |
| 124 std::string contents = ShellIntegration::GetDesktopFileContents( |
| 125 template_contents, shortcut_info_.url, shortcut_info_.title); |
| 126 |
| 127 if (shortcut_info_.create_on_desktop) |
| 128 CreateOnDesktop(shortcut_filename, contents); |
| 129 |
| 130 if (shortcut_info_.create_in_applications_menu) |
| 131 CreateInApplicationsMenu(shortcut_filename, contents); |
| 132 } |
| 133 |
| 134 private: |
| 135 void CreateOnDesktop(const FilePath& shortcut_filename, |
| 136 const std::string& contents) { |
| 137 // TODO(phajdan.jr): Report errors from this function, possibly as infobars. |
| 138 |
| 139 // Make sure that we will later call openat in a secure way. |
| 140 DCHECK_EQ(shortcut_filename.BaseName().value(), shortcut_filename.value()); |
| 141 |
| 142 FilePath desktop_path; |
| 143 if (!PathService::Get(chrome::DIR_USER_DESKTOP, &desktop_path)) |
| 144 return; |
| 145 |
| 146 int desktop_fd = open(desktop_path.value().c_str(), O_RDONLY | O_DIRECTORY); |
| 147 if (desktop_fd < 0) |
| 148 return; |
| 149 |
| 150 int fd = openat(desktop_fd, shortcut_filename.value().c_str(), |
| 151 O_CREAT | O_EXCL | O_WRONLY, |
| 152 S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); |
| 153 if (fd < 0) { |
| 154 HANDLE_EINTR(close(desktop_fd)); |
| 155 return; |
| 156 } |
| 157 |
| 158 ssize_t bytes_written = file_util::WriteFileDescriptor(fd, contents.data(), |
| 159 contents.length()); |
| 160 HANDLE_EINTR(close(fd)); |
| 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 HANDLE_EINTR(close(desktop_fd)); |
| 171 } |
| 172 |
| 173 void CreateInApplicationsMenu(const FilePath& shortcut_filename, |
| 174 const std::string& contents) { |
| 175 // TODO(phajdan.jr): Report errors from this function, possibly as infobars. |
| 176 ScopedTempDir temp_dir; |
| 177 if (!temp_dir.CreateUniqueTempDir()) |
| 178 return; |
| 179 |
| 130 FilePath temp_file_path = temp_dir.path().Append(shortcut_filename); | 180 FilePath temp_file_path = temp_dir.path().Append(shortcut_filename); |
| 131 | 181 |
| 132 int bytes_written = file_util::WriteFile(temp_file_path, contents.data(), | 182 int bytes_written = file_util::WriteFile(temp_file_path, contents.data(), |
| 133 contents.length()); | 183 contents.length()); |
| 134 | 184 |
| 135 if (bytes_written != static_cast<int>(contents.length())) | 185 if (bytes_written != static_cast<int>(contents.length())) |
| 136 return; | 186 return; |
| 137 | 187 |
| 138 if (shortcut_info_.create_on_desktop) { | 188 std::vector<std::string> argv; |
| 139 FilePath desktop_path; | 189 argv.push_back("xdg-desktop-menu"); |
| 140 if (!PathService::Get(chrome::DIR_USER_DESKTOP, &desktop_path)) | 190 argv.push_back("install"); |
| 141 return; | |
| 142 desktop_path = desktop_path.Append(shortcut_filename); | |
| 143 | 191 |
| 144 if (!file_util::PathExists(desktop_path)) | 192 // Always install in user mode, even if someone runs the browser as root |
| 145 file_util::CopyFile(temp_file_path, desktop_path); | 193 // (people do that). |
| 146 } | 194 argv.push_back("--mode"); |
| 195 argv.push_back("user"); |
| 147 | 196 |
| 148 if (shortcut_info_.create_in_applications_menu) { | 197 argv.push_back(temp_file_path.value()); |
| 149 std::vector<std::string> argv; | 198 LaunchXdgUtility(argv); |
| 150 argv.push_back("xdg-desktop-menu"); | |
| 151 argv.push_back("install"); | |
| 152 | |
| 153 // Always install in user mode, even if someone runs the browser as root | |
| 154 // (people do that). | |
| 155 argv.push_back("--mode"); | |
| 156 argv.push_back("user"); | |
| 157 | |
| 158 argv.push_back(temp_file_path.value()); | |
| 159 LaunchXdgUtility(argv); | |
| 160 } | |
| 161 } | 199 } |
| 162 | 200 |
| 163 private: | |
| 164 const ShellIntegration::ShortcutInfo shortcut_info_; | 201 const ShellIntegration::ShortcutInfo shortcut_info_; |
| 165 | 202 |
| 166 DISALLOW_COPY_AND_ASSIGN(CreateDesktopShortcutTask); | 203 DISALLOW_COPY_AND_ASSIGN(CreateDesktopShortcutTask); |
| 167 }; | 204 }; |
| 168 | 205 |
| 169 } // namespace | 206 } // namespace |
| 170 | 207 |
| 171 // We delegate the difficulty of setting the default browser in Linux desktop | 208 // We delegate the difficulty of setting the default browser in Linux desktop |
| 172 // environments to a new xdg utility, xdg-settings. We have to include a copy of | 209 // environments to a new xdg utility, xdg-settings. We have to include a copy of |
| 173 // it for this to work, obviously, but that's actually the suggested approach | 210 // it for this to work, obviously, but that's actually the suggested approach |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 219 | 256 |
| 220 // Return BaseName to be absolutely sure we're not vulnerable to a directory | 257 // Return BaseName to be absolutely sure we're not vulnerable to a directory |
| 221 // traversal attack. | 258 // traversal attack. |
| 222 return FilePath::FromWStringHack(filename).BaseName(); | 259 return FilePath::FromWStringHack(filename).BaseName(); |
| 223 } | 260 } |
| 224 | 261 |
| 225 std::string ShellIntegration::GetDesktopFileContents( | 262 std::string ShellIntegration::GetDesktopFileContents( |
| 226 const std::string& template_contents, const GURL& url, | 263 const std::string& template_contents, const GURL& url, |
| 227 const string16& title) { | 264 const string16& title) { |
| 228 // See http://standards.freedesktop.org/desktop-entry-spec/latest/ | 265 // See http://standards.freedesktop.org/desktop-entry-spec/latest/ |
| 229 std::string output_buffer; | 266 // Although not required by the spec, Nautilus on Ubuntu Karmic creates its |
| 267 // launchers with an xdg-open shebang. Follow that convention. |
| 268 std::string output_buffer("#!/usr/bin/env xdg-open\n"); |
| 230 StringTokenizer tokenizer(template_contents, "\n"); | 269 StringTokenizer tokenizer(template_contents, "\n"); |
| 231 while (tokenizer.GetNext()) { | 270 while (tokenizer.GetNext()) { |
| 232 // TODO(phajdan.jr): Add the icon. | 271 // TODO(phajdan.jr): Add the icon. |
| 233 | 272 |
| 234 if (tokenizer.token().substr(0, 5) == "Exec=") { | 273 if (tokenizer.token().substr(0, 5) == "Exec=") { |
| 235 std::string exec_path = tokenizer.token().substr(5); | 274 std::string exec_path = tokenizer.token().substr(5); |
| 236 StringTokenizer exec_tokenizer(exec_path, " "); | 275 StringTokenizer exec_tokenizer(exec_path, " "); |
| 237 std::string final_path; | 276 std::string final_path; |
| 238 while (exec_tokenizer.GetNext()) { | 277 while (exec_tokenizer.GetNext()) { |
| 239 if (exec_tokenizer.token() != "%U") | 278 if (exec_tokenizer.token() != "%U") |
| 240 final_path += exec_tokenizer.token() + " "; | 279 final_path += exec_tokenizer.token() + " "; |
| 241 } | 280 } |
| 242 std::wstring app_switch_wide(switches::kApp); | 281 std::wstring app_switch_wide(switches::kApp); |
| 243 std::string app_switch(StringPrintf("\"--%s=%s\"", | 282 std::string app_switch(StringPrintf("\"--%s=%s\"", |
| 244 WideToUTF8(app_switch_wide).c_str(), | 283 WideToUTF8(app_switch_wide).c_str(), |
| 245 url.spec().c_str())); | 284 url.spec().c_str())); |
| 285 // Sanitize the command line string. |
| 246 ReplaceSubstringsAfterOffset(&app_switch, 0, "%", "%%"); | 286 ReplaceSubstringsAfterOffset(&app_switch, 0, "%", "%%"); |
| 287 ReplaceSubstringsAfterOffset(&app_switch, 0, ";", ""); |
| 288 ReplaceSubstringsAfterOffset(&app_switch, 0, "$", ""); |
| 247 output_buffer += std::string("Exec=") + final_path + app_switch + "\n"; | 289 output_buffer += std::string("Exec=") + final_path + app_switch + "\n"; |
| 248 } else if (tokenizer.token().substr(0, 5) == "Name=") { | 290 } else if (tokenizer.token().substr(0, 5) == "Name=") { |
| 249 std::string final_title = UTF16ToUTF8(title); | 291 std::string final_title = UTF16ToUTF8(title); |
| 250 // Make sure no endline characters can slip in and possibly introduce | 292 // Make sure no endline characters can slip in and possibly introduce |
| 251 // additional lines (like Exec, which makes it a security risk). Also | 293 // additional lines (like Exec, which makes it a security risk). Also |
| 252 // use the URL as a default when the title is empty. | 294 // use the URL as a default when the title is empty. |
| 253 if (final_title.empty() || | 295 if (final_title.empty() || |
| 254 final_title.find("\n") != std::string::npos || | 296 final_title.find("\n") != std::string::npos || |
| 255 final_title.find("\r") != std::string::npos) | 297 final_title.find("\r") != std::string::npos) |
| 256 final_title = url.spec(); | 298 final_title = url.spec(); |
| 257 output_buffer += StringPrintf("Name=%s\n", final_title.c_str()); | 299 output_buffer += StringPrintf("Name=%s\n", final_title.c_str()); |
| 258 } else if (tokenizer.token().substr(0, 8) == "Comment=") { | 300 } else if (tokenizer.token().substr(0, 11) == "GenericName" || |
| 259 // Skip the line. | 301 tokenizer.token().substr(0, 7) == "Comment" || |
| 302 tokenizer.token().substr(0, 1) == "#") { |
| 303 // Skip comment lines. |
| 260 } else { | 304 } else { |
| 261 output_buffer += tokenizer.token() + "\n"; | 305 output_buffer += tokenizer.token() + "\n"; |
| 262 } | 306 } |
| 263 } | 307 } |
| 264 return output_buffer; | 308 return output_buffer; |
| 265 } | 309 } |
| 266 | 310 |
| 267 void ShellIntegration::CreateDesktopShortcut( | 311 void ShellIntegration::CreateDesktopShortcut( |
| 268 const ShortcutInfo& shortcut_info) { | 312 const ShortcutInfo& shortcut_info) { |
| 269 g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE, | 313 g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE, |
| 270 new CreateDesktopShortcutTask(shortcut_info)); | 314 new CreateDesktopShortcutTask(shortcut_info)); |
| 271 } | 315 } |
| OLD | NEW |