summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/shell_integration_linux.cc177
-rw-r--r--chrome/browser/shell_integration_unittest.cc25
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"
},