OLD | NEW |
---|---|
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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 <glib.h> | |
8 #include <stdlib.h> | 9 #include <stdlib.h> |
9 #include <sys/stat.h> | 10 #include <sys/stat.h> |
10 #include <sys/types.h> | 11 #include <sys/types.h> |
11 #include <unistd.h> | 12 #include <unistd.h> |
12 | 13 |
13 #include <string> | 14 #include <string> |
14 #include <vector> | 15 #include <vector> |
15 | 16 |
16 #include "base/command_line.h" | 17 #include "base/command_line.h" |
17 #include "base/eintr_wrapper.h" | 18 #include "base/eintr_wrapper.h" |
(...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
197 quoted += '\\'; | 198 quoted += '\\'; |
198 break; | 199 break; |
199 } | 200 } |
200 quoted += arg[i]; | 201 quoted += arg[i]; |
201 } | 202 } |
202 quoted += '"'; | 203 quoted += '"'; |
203 | 204 |
204 return quoted; | 205 return quoted; |
205 } | 206 } |
206 | 207 |
207 // Escape a string if needed for the right side of a Key=Value | 208 // Remove keys that we certainly don't want. |
Evan Martin
2011/05/11 21:29:06
Can you make this comment describe what this is?
| |
208 // construct in a desktop file. (Note that for Exec= lines this | 209 const char* kDesktopKeysToDelete[] = { |
209 // should be used in conjunction with QuoteArgForDesktopFileExec, | 210 "GenericName", |
210 // possibly escaping a backslash twice.) | 211 "Comment", |
211 std::string EscapeStringForDesktopFile(const std::string& arg) { | 212 "MimeType", |
212 // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s03.html | 213 "X-Ayatana-Desktop-Shortcuts", |
213 if (arg.find('\\') == std::string::npos) | 214 "StartupWMClass", // read only on TOOLKIT_GTK |
Evan Martin
2011/05/11 21:29:06
what does this comment mean?
| |
214 return arg; | 215 NULL |
216 }; | |
215 | 217 |
216 std::string escaped; | 218 const char* kDesktopEntry = "Desktop Entry"; |
217 for (size_t i = 0; i < arg.size(); ++i) { | 219 |
218 if (arg[i] == '\\') | 220 const char* kXdgOpenShebang = "#!/usr/bin/env xdg-open\n"; |
219 escaped += '\\'; | |
220 escaped += arg[i]; | |
221 } | |
222 return escaped; | |
223 } | |
224 | 221 |
225 } // namespace | 222 } // namespace |
226 | 223 |
227 // static | 224 // static |
228 std::string ShellIntegration::GetDesktopName(base::Environment* env) { | 225 std::string ShellIntegration::GetDesktopName(base::Environment* env) { |
229 #if defined(GOOGLE_CHROME_BUILD) | 226 #if defined(GOOGLE_CHROME_BUILD) |
230 return "google-chrome.desktop"; | 227 return "google-chrome.desktop"; |
231 #else // CHROMIUM_BUILD | 228 #else // CHROMIUM_BUILD |
232 // Allow $CHROME_DESKTOP to override the built-in value, so that development | 229 // Allow $CHROME_DESKTOP to override the built-in value, so that development |
233 // versions can set themselves as the default without interfering with | 230 // versions can set themselves as the default without interfering with |
(...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
361 } | 358 } |
362 | 359 |
363 // static | 360 // static |
364 std::string ShellIntegration::GetDesktopFileContents( | 361 std::string ShellIntegration::GetDesktopFileContents( |
365 const std::string& template_contents, | 362 const std::string& template_contents, |
366 const std::string& app_name, | 363 const std::string& app_name, |
367 const GURL& url, | 364 const GURL& url, |
368 const std::string& extension_id, | 365 const std::string& extension_id, |
369 const string16& title, | 366 const string16& title, |
370 const std::string& icon_name) { | 367 const std::string& icon_name) { |
368 if (template_contents.empty()) | |
369 return kXdgOpenShebang; | |
370 | |
371 // See http://standards.freedesktop.org/desktop-entry-spec/latest/ | 371 // See http://standards.freedesktop.org/desktop-entry-spec/latest/ |
372 // Although not required by the spec, Nautilus on Ubuntu Karmic creates its | 372 // http://developer.gnome.org/glib/unstable/glib-Key-value-file-parser.html |
Evan Martin
2011/05/11 21:29:06
Oh wow, I had no idea this existed!
| |
373 // launchers with an xdg-open shebang. Follow that convention. | 373 GKeyFile* key_file = g_key_file_new(); |
374 std::string output_buffer("#!/usr/bin/env xdg-open\n"); | 374 GError* err = NULL; |
375 StringTokenizer tokenizer(template_contents, "\n"); | 375 // Loading the data will strip translations and comments from the desktop |
376 while (tokenizer.GetNext()) { | 376 // file (which we want to do!) |
377 if (tokenizer.token().substr(0, 5) == "Exec=") { | 377 if (!g_key_file_load_from_data( |
378 std::string exec_path = tokenizer.token().substr(5); | 378 key_file, |
379 StringTokenizer exec_tokenizer(exec_path, " "); | 379 template_contents.c_str(), |
380 std::string final_path; | 380 template_contents.size(), |
381 while (exec_tokenizer.GetNext() && exec_tokenizer.token() != "%U") { | 381 G_KEY_FILE_NONE, |
382 if (!final_path.empty()) | 382 &err)) { |
383 final_path += " "; | 383 NOTREACHED() << "Unable to read desktop file template:" << err->message; |
384 final_path += exec_tokenizer.token(); | 384 g_error_free(err); |
385 } | 385 return kXdgOpenShebang; |
386 CommandLine cmd_line = | 386 } |
387 ShellIntegration::CommandLineArgsForLauncher(url, extension_id); | 387 |
388 const CommandLine::SwitchMap& switch_map = cmd_line.GetSwitches(); | 388 // Remove all sections except for the Desktop Entry |
389 for (CommandLine::SwitchMap::const_iterator i = switch_map.begin(); | 389 gsize length = 0; |
390 i != switch_map.end(); ++i) { | 390 gchar** groups = g_key_file_get_groups(key_file, &length); |
391 if (i->second.empty()) { | 391 for (gsize i = 0; i < length; ++i) { |
392 final_path += " --" + i->first; | 392 if (strcmp(groups[i], kDesktopEntry) != 0) { |
393 } else { | 393 g_key_file_remove_group(key_file, groups[i], NULL); |
394 final_path += " " + QuoteArgForDesktopFileExec("--" + i->first + | |
395 "=" + i->second); | |
396 } | |
397 } | |
398 output_buffer += std::string("Exec=") + | |
399 EscapeStringForDesktopFile(final_path) + "\n"; | |
400 } else if (tokenizer.token().substr(0, 5) == "Name=") { | |
401 std::string final_title = UTF16ToUTF8(title); | |
402 // Make sure no endline characters can slip in and possibly introduce | |
403 // additional lines (like Exec, which makes it a security risk). Also | |
404 // use the URL as a default when the title is empty. | |
405 if (final_title.empty() || | |
406 final_title.find("\n") != std::string::npos || | |
407 final_title.find("\r") != std::string::npos) { | |
408 final_title = url.spec(); | |
409 } | |
410 output_buffer += StringPrintf("Name=%s\n", final_title.c_str()); | |
411 } else if (tokenizer.token().substr(0, 11) == "GenericName" || | |
412 tokenizer.token().substr(0, 7) == "Comment" || | |
413 tokenizer.token().substr(0, 1) == "#") { | |
414 // Skip comment lines. | |
415 } else if (tokenizer.token().substr(0, 9) == "MimeType=") { | |
416 // Skip MimeType lines, they are only relevant for a web browser | |
417 // shortcut, not a web application shortcut. | |
418 } else if (tokenizer.token().substr(0, 15) == "StartupWMClass=") { | |
419 // Skip StartupWMClass; it will certainly be wrong since we emit a | |
420 // different one based on the app name below. | |
421 } else if (tokenizer.token().substr(0, 5) == "Icon=" && | |
422 !icon_name.empty()) { | |
423 output_buffer += StringPrintf("Icon=%s\n", icon_name.c_str()); | |
424 } else { | |
425 output_buffer += tokenizer.token() + "\n"; | |
426 } | 394 } |
427 } | 395 } |
396 g_strfreev(groups); | |
397 | |
398 // Remove keys that we won't need. | |
399 for (const char** current_key = kDesktopKeysToDelete; *current_key; | |
400 ++current_key) { | |
401 g_key_file_remove_key(key_file, kDesktopEntry, *current_key, NULL); | |
402 } | |
403 | |
404 // Set the "Name" key. | |
405 std::string final_title = UTF16ToUTF8(title); | |
406 // Make sure no endline characters can slip in and possibly introduce | |
407 // additional lines (like Exec, which makes it a security risk). Also | |
408 // use the URL as a default when the title is empty. | |
409 if (final_title.empty() || | |
410 final_title.find("\n") != std::string::npos || | |
411 final_title.find("\r") != std::string::npos) { | |
412 final_title = url.spec(); | |
Evan Martin
2011/05/11 21:29:06
Is it possible that url.spec() has these bad chara
Elliot Glaysher
2011/05/11 21:52:29
No, but it would if we called invalid_spec(). On d
| |
413 } | |
414 g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str()); | |
415 | |
416 // Set the "Exec" key. | |
417 char* exec_c_string = g_key_file_get_string(key_file, kDesktopEntry, "Exec", | |
418 NULL); | |
419 if (exec_c_string) { | |
420 std::string exec_string(exec_c_string); | |
421 g_free(exec_c_string); | |
422 StringTokenizer exec_tokenizer(exec_string, " "); | |
423 | |
424 std::string final_path; | |
425 while (exec_tokenizer.GetNext() && exec_tokenizer.token() != "%U") { | |
426 if (!final_path.empty()) | |
427 final_path += " "; | |
428 final_path += exec_tokenizer.token(); | |
429 } | |
430 CommandLine cmd_line = | |
431 ShellIntegration::CommandLineArgsForLauncher(url, extension_id); | |
432 const CommandLine::SwitchMap& switch_map = cmd_line.GetSwitches(); | |
433 for (CommandLine::SwitchMap::const_iterator i = switch_map.begin(); | |
434 i != switch_map.end(); ++i) { | |
435 if (i->second.empty()) { | |
436 final_path += " --" + i->first; | |
437 } else { | |
438 final_path += " " + QuoteArgForDesktopFileExec("--" + i->first + | |
439 "=" + i->second); | |
440 } | |
441 } | |
442 | |
443 g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str()); | |
Elliot Glaysher
2011/05/11 01:07:27
This level of quoting is done by the library. (We
Evan Martin
2011/05/11 21:29:06
Are the unit tests correct? Better to be correct
Elliot Glaysher
2011/05/11 21:52:29
Yes. It's just a weird artifact of where gkeyparse
| |
444 } | |
445 | |
446 // Set the "Icon" key. | |
447 if (!icon_name.empty()) | |
448 g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str()); | |
428 | 449 |
429 #if defined(TOOLKIT_USES_GTK) | 450 #if defined(TOOLKIT_USES_GTK) |
430 std::string wmclass = web_app::GetWMClassFromAppName(app_name); | 451 std::string wmclass = web_app::GetWMClassFromAppName(app_name); |
431 if (!wmclass.empty()) { | 452 g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass", |
432 output_buffer += StringPrintf("StartupWMClass=%s\n", wmclass.c_str()); | 453 wmclass.c_str()); |
433 } | |
434 #endif | 454 #endif |
435 | 455 |
456 // Although not required by the spec, Nautilus on Ubuntu Karmic creates its | |
457 // launchers with an xdg-open shebang. Follow that convention. | |
458 std::string output_buffer("#!/usr/bin/env xdg-open"); | |
Evan Martin
2011/05/11 21:29:06
did you intend to use your kShebang constant?
Elliot Glaysher
2011/05/11 21:52:29
No, because that constant has a newline. Updated t
| |
459 length = 0; | |
460 gchar* data_dump = g_key_file_to_data(key_file, &length, NULL); | |
461 if (data_dump) { | |
462 output_buffer += data_dump; | |
463 g_free(data_dump); | |
464 } | |
465 | |
466 g_key_file_free(key_file); | |
436 return output_buffer; | 467 return output_buffer; |
437 } | 468 } |
438 | 469 |
439 // static | 470 // static |
440 void ShellIntegration::CreateDesktopShortcut( | 471 void ShellIntegration::CreateDesktopShortcut( |
441 const ShortcutInfo& shortcut_info, const std::string& shortcut_template) { | 472 const ShortcutInfo& shortcut_info, const std::string& shortcut_template) { |
442 // TODO(phajdan.jr): Report errors from this function, possibly as infobars. | 473 // TODO(phajdan.jr): Report errors from this function, possibly as infobars. |
443 | 474 |
444 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 475 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
445 | 476 |
(...skipping 12 matching lines...) Expand all Loading... | |
458 shortcut_info.extension_id, | 489 shortcut_info.extension_id, |
459 shortcut_info.title, | 490 shortcut_info.title, |
460 icon_name); | 491 icon_name); |
461 | 492 |
462 if (shortcut_info.create_on_desktop) | 493 if (shortcut_info.create_on_desktop) |
463 CreateShortcutOnDesktop(shortcut_filename, contents); | 494 CreateShortcutOnDesktop(shortcut_filename, contents); |
464 | 495 |
465 if (shortcut_info.create_in_applications_menu) | 496 if (shortcut_info.create_in_applications_menu) |
466 CreateShortcutInApplicationsMenu(shortcut_filename, contents); | 497 CreateShortcutInApplicationsMenu(shortcut_filename, contents); |
467 } | 498 } |
OLD | NEW |