diff options
-rw-r--r-- | chrome/browser/shell_integration_linux.cc | 177 | ||||
-rw-r--r-- | chrome/browser/shell_integration_unittest.cc | 25 |
2 files changed, 128 insertions, 74 deletions
diff --git a/chrome/browser/shell_integration_linux.cc b/chrome/browser/shell_integration_linux.cc index 8a69fee..0fe55c1 100644 --- a/chrome/browser/shell_integration_linux.cc +++ b/chrome/browser/shell_integration_linux.cc @@ -5,6 +5,7 @@ #include "chrome/browser/shell_integration.h" #include <fcntl.h> +#include <glib.h> #include <stdlib.h> #include <sys/stat.h> #include <sys/types.h> @@ -23,7 +24,6 @@ #include "base/message_loop.h" #include "base/path_service.h" #include "base/process_util.h" -#include "base/stringprintf.h" #include "base/string_number_conversions.h" #include "base/string_tokenizer.h" #include "base/task.h" @@ -204,23 +204,20 @@ std::string QuoteArgForDesktopFileExec(const std::string& arg) { return quoted; } -// Escape a string if needed for the right side of a Key=Value -// construct in a desktop file. (Note that for Exec= lines this -// should be used in conjunction with QuoteArgForDesktopFileExec, -// possibly escaping a backslash twice.) -std::string EscapeStringForDesktopFile(const std::string& arg) { - // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s03.html - if (arg.find('\\') == std::string::npos) - return arg; +// Remove keys from the [Desktop Entry] that would be wrong if copied verbatim +// into the new .desktop file. +const char* kDesktopKeysToDelete[] = { + "GenericName", + "Comment", + "MimeType", + "X-Ayatana-Desktop-Shortcuts", + "StartupWMClass", + NULL +}; - std::string escaped; - for (size_t i = 0; i < arg.size(); ++i) { - if (arg[i] == '\\') - escaped += '\\'; - escaped += arg[i]; - } - return escaped; -} +const char* kDesktopEntry = "Desktop Entry"; + +const char* kXdgOpenShebang = "#!/usr/bin/env xdg-open"; } // namespace @@ -368,71 +365,105 @@ std::string ShellIntegration::GetDesktopFileContents( const std::string& extension_id, const string16& title, const std::string& icon_name) { + if (template_contents.empty()) + return std::string(kXdgOpenShebang) + "\n"; + // See http://standards.freedesktop.org/desktop-entry-spec/latest/ - // Although not required by the spec, Nautilus on Ubuntu Karmic creates its - // launchers with an xdg-open shebang. Follow that convention. - std::string output_buffer("#!/usr/bin/env xdg-open\n"); - StringTokenizer tokenizer(template_contents, "\n"); - while (tokenizer.GetNext()) { - if (tokenizer.token().substr(0, 5) == "Exec=") { - std::string exec_path = tokenizer.token().substr(5); - StringTokenizer exec_tokenizer(exec_path, " "); - std::string final_path; - while (exec_tokenizer.GetNext() && exec_tokenizer.token() != "%U") { - if (!final_path.empty()) - final_path += " "; - final_path += exec_tokenizer.token(); - } - CommandLine cmd_line = - ShellIntegration::CommandLineArgsForLauncher(url, extension_id); - const CommandLine::SwitchMap& switch_map = cmd_line.GetSwitches(); - for (CommandLine::SwitchMap::const_iterator i = switch_map.begin(); - i != switch_map.end(); ++i) { - if (i->second.empty()) { - final_path += " --" + i->first; - } else { - final_path += " " + QuoteArgForDesktopFileExec("--" + i->first + - "=" + i->second); - } - } - output_buffer += std::string("Exec=") + - EscapeStringForDesktopFile(final_path) + "\n"; - } else if (tokenizer.token().substr(0, 5) == "Name=") { - std::string final_title = UTF16ToUTF8(title); - // Make sure no endline characters can slip in and possibly introduce - // additional lines (like Exec, which makes it a security risk). Also - // use the URL as a default when the title is empty. - if (final_title.empty() || - final_title.find("\n") != std::string::npos || - final_title.find("\r") != std::string::npos) { - final_title = url.spec(); + // http://developer.gnome.org/glib/unstable/glib-Key-value-file-parser.html + GKeyFile* key_file = g_key_file_new(); + GError* err = NULL; + // Loading the data will strip translations and comments from the desktop + // file (which we want to do!) + if (!g_key_file_load_from_data( + key_file, + template_contents.c_str(), + template_contents.size(), + G_KEY_FILE_NONE, + &err)) { + NOTREACHED() << "Unable to read desktop file template:" << err->message; + g_error_free(err); + return std::string(kXdgOpenShebang) + "\n"; + } + + // Remove all sections except for the Desktop Entry + gsize length = 0; + gchar** groups = g_key_file_get_groups(key_file, &length); + for (gsize i = 0; i < length; ++i) { + if (strcmp(groups[i], kDesktopEntry) != 0) { + g_key_file_remove_group(key_file, groups[i], NULL); + } + } + g_strfreev(groups); + + // Remove keys that we won't need. + for (const char** current_key = kDesktopKeysToDelete; *current_key; + ++current_key) { + g_key_file_remove_key(key_file, kDesktopEntry, *current_key, NULL); + } + + // Set the "Name" key. + std::string final_title = UTF16ToUTF8(title); + // Make sure no endline characters can slip in and possibly introduce + // additional lines (like Exec, which makes it a security risk). Also + // use the URL as a default when the title is empty. + if (final_title.empty() || + final_title.find("\n") != std::string::npos || + final_title.find("\r") != std::string::npos) { + final_title = url.spec(); + } + g_key_file_set_string(key_file, kDesktopEntry, "Name", final_title.c_str()); + + // Set the "Exec" key. + char* exec_c_string = g_key_file_get_string(key_file, kDesktopEntry, "Exec", + NULL); + if (exec_c_string) { + std::string exec_string(exec_c_string); + g_free(exec_c_string); + StringTokenizer exec_tokenizer(exec_string, " "); + + std::string final_path; + while (exec_tokenizer.GetNext() && exec_tokenizer.token() != "%U") { + if (!final_path.empty()) + final_path += " "; + final_path += exec_tokenizer.token(); + } + CommandLine cmd_line = + ShellIntegration::CommandLineArgsForLauncher(url, extension_id); + const CommandLine::SwitchMap& switch_map = cmd_line.GetSwitches(); + for (CommandLine::SwitchMap::const_iterator i = switch_map.begin(); + i != switch_map.end(); ++i) { + if (i->second.empty()) { + final_path += " --" + i->first; + } else { + final_path += " " + QuoteArgForDesktopFileExec("--" + i->first + + "=" + i->second); } - output_buffer += base::StringPrintf("Name=%s\n", final_title.c_str()); - } else if (tokenizer.token().substr(0, 11) == "GenericName" || - tokenizer.token().substr(0, 7) == "Comment" || - tokenizer.token().substr(0, 1) == "#") { - // Skip comment lines. - } else if (tokenizer.token().substr(0, 9) == "MimeType=") { - // Skip MimeType lines, they are only relevant for a web browser - // shortcut, not a web application shortcut. - } else if (tokenizer.token().substr(0, 15) == "StartupWMClass=") { - // Skip StartupWMClass; it will certainly be wrong since we emit a - // different one based on the app name below. - } else if (tokenizer.token().substr(0, 5) == "Icon=" && - !icon_name.empty()) { - output_buffer += base::StringPrintf("Icon=%s\n", icon_name.c_str()); - } else { - output_buffer += tokenizer.token() + "\n"; } + + g_key_file_set_string(key_file, kDesktopEntry, "Exec", final_path.c_str()); } + // Set the "Icon" key. + if (!icon_name.empty()) + g_key_file_set_string(key_file, kDesktopEntry, "Icon", icon_name.c_str()); + #if defined(TOOLKIT_USES_GTK) std::string wmclass = web_app::GetWMClassFromAppName(app_name); - if (!wmclass.empty()) { - output_buffer += base::StringPrintf("StartupWMClass=%s\n", wmclass.c_str()); - } + g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass", + wmclass.c_str()); #endif + // Although not required by the spec, Nautilus on Ubuntu Karmic creates its + // launchers with an xdg-open shebang. Follow that convention. + std::string output_buffer = kXdgOpenShebang; + length = 0; + gchar* data_dump = g_key_file_to_data(key_file, &length, NULL); + if (data_dump) { + output_buffer += data_dump; + g_free(data_dump); + } + + g_key_file_free(key_file); return output_buffer; } diff --git a/chrome/browser/shell_integration_unittest.cc b/chrome/browser/shell_integration_unittest.cc index 2c4aa8b..63d1304 100644 --- a/chrome/browser/shell_integration_unittest.cc +++ b/chrome/browser/shell_integration_unittest.cc @@ -185,7 +185,13 @@ TEST(ShellIntegrationTest, GetDesktopFileContents) { "Icon=/opt/google/chrome/product_logo_48.png\n" "Type=Application\n" "Categories=Application;Network;WebBrowser;\n" - "MimeType=text/html;text/xml;application/xhtml_xml;\n", + "MimeType=text/html;text/xml;application/xhtml_xml;\n" + "X-Ayatana-Desktop-Shortcuts=NewWindow;\n" + "\n" + "[NewWindow Shortcut Group]\n" + "Name=Open New Window\n" + "Exec=/opt/google/chrome/google-chrome\n" + "TargetEnvironment=Unity\n", "#!/usr/bin/env xdg-open\n" "[Desktop Entry]\n" @@ -206,12 +212,15 @@ TEST(ShellIntegrationTest, GetDesktopFileContents) { "chrome-http__gmail.com", "#!/some/shebang\n" + "[Desktop Entry]\n" "Name=Google Chrome\n" "Exec=/opt/google/chrome/google-chrome %U\n", "#!/usr/bin/env xdg-open\n" + "[Desktop Entry]\n" "Name=GMail\n" "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n" + "Icon=chrome-http__gmail.com\n" "StartupWMClass=gmail.com\n" }, @@ -220,13 +229,16 @@ TEST(ShellIntegrationTest, GetDesktopFileContents) { "GMail", "chrome-http__gmail.com", + "[Desktop Entry]\n" "Name=Google Chrome\n" "Exec=/opt/google/chrome/google-chrome %U\n" "Comment[pl]=Jakis komentarz.\n", "#!/usr/bin/env xdg-open\n" + "[Desktop Entry]\n" "Name=GMail\n" "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n" + "Icon=chrome-http__gmail.com\n" "StartupWMClass=gmail.com\n" }, @@ -235,12 +247,14 @@ TEST(ShellIntegrationTest, GetDesktopFileContents) { "GMail", "", + "[Desktop Entry]\n" "Name=Google Chrome\n" "Exec=/opt/google/chrome/google-chrome %U\n" "Comment[pl]=Jakis komentarz.\n" "Icon=/opt/google/chrome/product_logo_48.png\n", "#!/usr/bin/env xdg-open\n" + "[Desktop Entry]\n" "Name=GMail\n" "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n" "Icon=/opt/google/chrome/product_logo_48.png\n" @@ -252,23 +266,28 @@ TEST(ShellIntegrationTest, GetDesktopFileContents) { "Ownz0red\nExec=rm -rf /", "chrome-http__evil.com_evil", + "[Desktop Entry]\n" "Name=Google Chrome\n" "Exec=/opt/google/chrome/google-chrome %U\n", "#!/usr/bin/env xdg-open\n" + "[Desktop Entry]\n" "Name=http://evil.com/evil%20--join-the-b0tnet\n" "Exec=/opt/google/chrome/google-chrome " "--app=http://evil.com/evil%20--join-the-b0tnet\n" + "Icon=chrome-http__evil.com_evil\n" "StartupWMClass=evil.com__evil%20--join-the-b0tnet\n" }, { "http://evil.com/evil; rm -rf /; \"; rm -rf $HOME >ownz0red", "Innocent Title", "chrome-http__evil.com_evil", + "[Desktop Entry]\n" "Name=Google Chrome\n" "Exec=/opt/google/chrome/google-chrome %U\n", "#!/usr/bin/env xdg-open\n" + "[Desktop Entry]\n" "Name=Innocent Title\n" "Exec=/opt/google/chrome/google-chrome " "\"--app=http://evil.com/evil;%20rm%20-rf%20/;%20%22;%20rm%20" @@ -276,6 +295,7 @@ TEST(ShellIntegrationTest, GetDesktopFileContents) { // the \ is escaped as \\ as all strings in a Desktop file should // be; finally, \\ becomes \\\\ when represented in a C++ string! "-rf%20\\\\$HOME%20%3Eownz0red\"\n" + "Icon=chrome-http__evil.com_evil\n" "StartupWMClass=evil.com__evil;%20rm%20-rf%20_;%20%22;%20" "rm%20-rf%20$HOME%20%3Eownz0red\n" }, @@ -283,14 +303,17 @@ TEST(ShellIntegrationTest, GetDesktopFileContents) { "Innocent Title", "chrome-http__evil.com_evil", + "[Desktop Entry]\n" "Name=Google Chrome\n" "Exec=/opt/google/chrome/google-chrome %U\n", "#!/usr/bin/env xdg-open\n" + "[Desktop Entry]\n" "Name=Innocent Title\n" "Exec=/opt/google/chrome/google-chrome " "--app=http://evil.com/evil%20%7C%20cat%20%60echo%20ownz0red" "%60%20%3E/dev/null\n" + "Icon=chrome-http__evil.com_evil\n" "StartupWMClass=evil.com__evil%20%7C%20cat%20%60echo%20ownz0red" "%60%20%3E_dev_null\n" }, |