diff options
author | erg@google.com <erg@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-11 22:10:20 +0000 |
---|---|---|
committer | erg@google.com <erg@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-11 22:10:20 +0000 |
commit | 0a96c3fca43621cba2890f1c9ae0b3f6f5ad78ec (patch) | |
tree | 80733835bdf11ebed52f8b8104aeae271383ef6c | |
parent | 41999c790950781a3598bad03b6179d685e9d82a (diff) | |
download | chromium_src-0a96c3fca43621cba2890f1c9ae0b3f6f5ad78ec.zip chromium_src-0a96c3fca43621cba2890f1c9ae0b3f6f5ad78ec.tar.gz chromium_src-0a96c3fca43621cba2890f1c9ae0b3f6f5ad78ec.tar.bz2 |
GTK: Use glib's desktop file parser instead of our hand rolled one.
We need to support .desktop files that are much more complicated than the
simple ones we've used so far. Rip out the our hand rolled parser and use
glib's to manage multi-section desktop files.
We need to be able to build new .desktop files off desktop files that have
multiple groups. Change the unit test so the "real world example" has the
X-Ayatana-Desktop-Shortcuts keys (plus related sections).
BUG=none
TEST=none
Review URL: http://codereview.chromium.org/7000018
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@85059 0039d316-1c4b-4281-b951-d872f2087c98
-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" }, |