summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorphajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-12 21:34:49 +0000
committerphajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-12 21:34:49 +0000
commitb96aa938b7f86b67f7d4c167e713f5c23fae01d6 (patch)
tree92c58342b82e416a5ffd29a7c283fdcfe9c0c82b
parent4778bf3be094a4faac5e622a5f18be308908e53c (diff)
downloadchromium_src-b96aa938b7f86b67f7d4c167e713f5c23fae01d6.zip
chromium_src-b96aa938b7f86b67f7d4c167e713f5c23fae01d6.tar.gz
chromium_src-b96aa938b7f86b67f7d4c167e713f5c23fae01d6.tar.bz2
First step to create application shortcuts on Linux.
Create a working desktop shortcut. For now it displays no UI, but the backend works. TEST=none http://crbug.com/17251 Review URL: http://codereview.chromium.org/164280 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@23226 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/browser.cc2
-rw-r--r--chrome/browser/gtk/standard_menus.cc8
-rw-r--r--chrome/browser/shell_integration.cc2
-rw-r--r--chrome/browser/shell_integration.h20
-rw-r--r--chrome/browser/shell_integration_linux.cc136
-rw-r--r--chrome/browser/shell_integration_unittest.cc94
-rw-r--r--chrome/browser/tab_contents/tab_contents.cc6
-rw-r--r--chrome/chrome.gyp1
8 files changed, 261 insertions, 8 deletions
diff --git a/chrome/browser/browser.cc b/chrome/browser/browser.cc
index 562f3b0..160be20 100644
--- a/chrome/browser/browser.cc
+++ b/chrome/browser/browser.cc
@@ -1114,7 +1114,7 @@ void Browser::OpenFile() {
void Browser::OpenCreateShortcutsDialog() {
UserMetrics::RecordAction(L"CreateShortcut", profile_);
-#if defined(OS_WIN)
+#if defined(OS_WIN) || defined(OS_LINUX)
GetSelectedTabContents()->CreateShortcut();
#else
NOTIMPLEMENTED();
diff --git a/chrome/browser/gtk/standard_menus.cc b/chrome/browser/gtk/standard_menus.cc
index 72a66ac..bf1ef7db 100644
--- a/chrome/browser/gtk/standard_menus.cc
+++ b/chrome/browser/gtk/standard_menus.cc
@@ -37,12 +37,8 @@ struct MenuCreateMaterial developer_menu_materials[] = {
};
struct MenuCreateMaterial standard_page_menu_materials[] = {
- // The Create App Shortcuts menu item hasn't been implemented yet.
- // Remove it until it is.
- // http://code.google.com/p/chromium/issues/detail?id=17251
- // { MENU_NORMAL, IDC_CREATE_SHORTCUTS, IDS_CREATE_SHORTCUTS },
- // { MENU_SEPARATOR },
-
+ { MENU_NORMAL, IDC_CREATE_SHORTCUTS, IDS_CREATE_SHORTCUTS },
+ { MENU_SEPARATOR },
{ MENU_NORMAL, IDC_CUT, IDS_CUT, 0, NULL, GDK_x, GDK_CONTROL_MASK, true },
{ MENU_NORMAL, IDC_COPY, IDS_COPY, 0, NULL, GDK_c, GDK_CONTROL_MASK, true },
{ MENU_NORMAL, IDC_PASTE, IDS_PASTE, 0, NULL, GDK_v, GDK_CONTROL_MASK, true },
diff --git a/chrome/browser/shell_integration.cc b/chrome/browser/shell_integration.cc
index e982ab8..219ce77 100644
--- a/chrome/browser/shell_integration.cc
+++ b/chrome/browser/shell_integration.cc
@@ -5,8 +5,10 @@
#include "chrome/browser/shell_integration.h"
#include "base/message_loop.h"
+#include "base/path_service.h"
#include "base/thread.h"
#include "chrome/browser/browser_process.h"
+#include "chrome/common/chrome_paths.h"
///////////////////////////////////////////////////////////////////////////////
// ShellIntegration::DefaultBrowserWorker
diff --git a/chrome/browser/shell_integration.h b/chrome/browser/shell_integration.h
index 4059266..9abfadd 100644
--- a/chrome/browser/shell_integration.h
+++ b/chrome/browser/shell_integration.h
@@ -7,8 +7,12 @@
#include <string>
+#include "base/basictypes.h"
#include "base/ref_counted.h"
+#include "base/string16.h"
+class FilePath;
+class GURL;
class MessageLoop;
class ShellIntegration {
@@ -27,6 +31,22 @@ class ShellIntegration {
// user. This method is very fast so it can be invoked in the UI thread.
static bool IsFirefoxDefaultBrowser();
+#if defined(OS_LINUX)
+ // Returns filename for .desktop file based on |url|, sanitized for security.
+ static FilePath GetDesktopShortcutFilename(const GURL& url);
+
+ // Returns contents for .desktop file based on |template_contents|, |url|
+ // and |title|. The |template_contents| should be contents of .desktop file
+ // used to launch Chrome.
+ static std::string GetDesktopFileContents(
+ const std::string& template_contents, const GURL& url,
+ const string16& title);
+
+ // Creates a desktop shortcut for |url| with |title|. It is not guaranteed
+ // to exist immediately after returning from this function, because actual
+ // file operation is done on the file thread.
+ static void CreateDesktopShortcut(const GURL& url, const string16& title);
+#endif // defined(OS_LINUX)
// The current default browser UI state
enum DefaultBrowserUIState {
diff --git a/chrome/browser/shell_integration_linux.cc b/chrome/browser/shell_integration_linux.cc
index fc5e053..48abd49 100644
--- a/chrome/browser/shell_integration_linux.cc
+++ b/chrome/browser/shell_integration_linux.cc
@@ -12,9 +12,22 @@
#include <vector>
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/message_loop.h"
+#include "base/path_service.h"
#include "base/process_util.h"
+#include "base/string_tokenizer.h"
+#include "base/string_util.h"
+#include "base/task.h"
+#include "base/thread.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/common/chrome_paths.h"
+#include "googleurl/src/gurl.h"
-static const char* GetDesktopName() {
+namespace {
+
+const char* GetDesktopName() {
#if defined(GOOGLE_CHROME_BUILD)
return "google-chrome.desktop";
#else // CHROMIUM_BUILD
@@ -31,6 +44,72 @@ static const char* GetDesktopName() {
#endif
}
+bool GetDesktopShortcutTemplate(std::string* output) {
+ std::vector<std::string> search_paths;
+
+ const char* xdg_data_home = getenv("XDG_DATA_HOME");
+ if (xdg_data_home)
+ search_paths.push_back(xdg_data_home);
+
+ const char* xdg_data_dirs = getenv("XDG_DATA_DIRS");
+ if (xdg_data_dirs) {
+ StringTokenizer tokenizer(xdg_data_dirs, ":");
+ while (tokenizer.GetNext()) {
+ search_paths.push_back(tokenizer.token());
+ }
+ }
+
+ std::string template_filename(GetDesktopName());
+ for (std::vector<std::string>::const_iterator i = search_paths.begin();
+ i != search_paths.end(); ++i) {
+ FilePath path = FilePath(*i).Append(template_filename);
+ if (file_util::PathExists(path))
+ return file_util::ReadFileToString(path, output);
+ }
+
+ return false;
+}
+
+class CreateDesktopShortcutTask : public Task {
+ public:
+ CreateDesktopShortcutTask(const GURL& url, const string16& title)
+ : url_(url),
+ title_(title) {
+ }
+
+ virtual void Run() {
+ // TODO(phajdan.jr): Report errors from this function, possibly as infobars.
+ FilePath desktop_path;
+ if (!PathService::Get(chrome::DIR_USER_DESKTOP, &desktop_path))
+ return;
+ desktop_path =
+ desktop_path.Append(ShellIntegration::GetDesktopShortcutFilename(url_));
+
+ if (file_util::PathExists(desktop_path))
+ return;
+
+ std::string template_contents;
+ if (!GetDesktopShortcutTemplate(&template_contents))
+ return;
+
+ std::string contents = ShellIntegration::GetDesktopFileContents(
+ template_contents, url_, title_);
+ int bytes_written = file_util::WriteFile(desktop_path, contents.data(),
+ contents.length());
+ if (bytes_written != static_cast<int>(contents.length())) {
+ file_util::Delete(desktop_path, false);
+ }
+ }
+
+ private:
+ const GURL url_; // URL of the web application.
+ const string16 title_; // Title displayed to the user.
+
+ DISALLOW_COPY_AND_ASSIGN(CreateDesktopShortcutTask);
+};
+
+} // namespace
+
// We delegate the difficult of setting the default browser in Linux desktop
// environments to a new xdg utility, xdg-settings. We'll have to include a copy
// of it for this to work, obviously, but that's actually the suggested approach
@@ -98,3 +177,58 @@ bool ShellIntegration::IsFirefoxDefaultBrowser() {
base::GetAppOutput(CommandLine(argv), &browser);
return browser.find("irefox") != std::string::npos;
}
+
+FilePath ShellIntegration::GetDesktopShortcutFilename(const GURL& url) {
+ std::wstring filename = UTF8ToWide(url.spec()) + L".desktop";
+ file_util::ReplaceIllegalCharacters(&filename, '_');
+
+ // Return BaseName to be absolutely sure we're not vulnerable to a directory
+ // traversal attack.
+ return FilePath::FromWStringHack(filename).BaseName();
+}
+
+std::string ShellIntegration::GetDesktopFileContents(
+ const std::string& template_contents, const GURL& url,
+ const string16& title) {
+ // See http://standards.freedesktop.org/desktop-entry-spec/latest/
+ std::string output_buffer;
+ StringTokenizer tokenizer(template_contents, "\n");
+ while (tokenizer.GetNext()) {
+ // TODO(phajdan.jr): Add the icon.
+
+ 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()) {
+ if (exec_tokenizer.token() != "%U")
+ final_path += exec_tokenizer.token() + " ";
+ }
+ std::string app_switch(StringPrintf("\"--app=%s\"",
+ url.spec().c_str()));
+ ReplaceSubstringsAfterOffset(&app_switch, 0, "%", "%%");
+ output_buffer += std::string("Exec=") + final_path + app_switch + "\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();
+ output_buffer += StringPrintf("Name=%s\n", final_title.c_str());
+ } else if (tokenizer.token().substr(0, 8) == "Comment=") {
+ // Skip the line.
+ } else {
+ output_buffer += tokenizer.token() + "\n";
+ }
+ }
+ return output_buffer;
+}
+
+void ShellIntegration::CreateDesktopShortcut(const GURL& url,
+ const string16& title) {
+ g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE,
+ new CreateDesktopShortcutTask(url, title));
+}
diff --git a/chrome/browser/shell_integration_unittest.cc b/chrome/browser/shell_integration_unittest.cc
new file mode 100644
index 0000000..fe01fc1
--- /dev/null
+++ b/chrome/browser/shell_integration_unittest.cc
@@ -0,0 +1,94 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/shell_integration.h"
+
+#include "base/file_path.h"
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#define FPL FILE_PATH_LITERAL
+
+#if defined(OS_LINUX)
+TEST(ShellIntegrationTest, GetDesktopShortcutFilename) {
+ const struct {
+ const FilePath::CharType* path;
+ const char* url;
+ } test_cases[] = {
+ { FPL("http___foo_.desktop"), "http://foo" },
+ { FPL("http___foo_bar_.desktop"), "http://foo/bar/" },
+ { FPL("http___foo_bar_a=b&c=d.desktop"), "http://foo/bar?a=b&c=d" },
+
+ // Now we're starting to be more evil...
+ { FPL("http___foo_.desktop"), "http://foo/bar/baz/../../../../../" },
+ { FPL("http___foo_.desktop"), "http://foo/bar/././../baz/././../" },
+ { FPL("http___.._.desktop"), "http://../../../../" },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) {
+ EXPECT_EQ(test_cases[i].path, ShellIntegration::GetDesktopShortcutFilename(
+ GURL(test_cases[i].url)).value()) << " while testing " <<
+ test_cases[i].url;
+ }
+}
+
+TEST(ShellIntegrationTest, GetDesktopFileContents) {
+ const struct {
+ const char* url;
+ const char* title;
+ const char* template_contents;
+ const char* expected_output;
+ } test_cases[] = {
+ // Dumb case.
+ { "ignored", "ignored", "", "" },
+
+ // Real-world case.
+ { "http://gmail.com",
+ "GMail",
+
+ "[Desktop Entry]\n"
+ "Version=1.0\n"
+ "Encoding=UTF-8\n"
+ "Name=Google Chrome\n"
+ "Comment=The web browser from Google\n"
+ "Exec=/opt/google/chrome/google-chrome %U\n"
+ "Terminal=false\n"
+ "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",
+
+ "[Desktop Entry]\n"
+ "Version=1.0\n"
+ "Encoding=UTF-8\n"
+ "Name=GMail\n"
+ "Exec=/opt/google/chrome/google-chrome \"--app=http://gmail.com/\"\n"
+ "Terminal=false\n"
+ "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"
+ },
+
+ // Now we're starting to be more evil...
+ { "http://evil.com/evil --join-the-b0tnet",
+ "Ownz0red\nExec=rm -rf /",
+
+ "Name=Google Chrome\n"
+ "Exec=/opt/google/chrome/google-chrome %U\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"
+ },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) {
+ EXPECT_EQ(test_cases[i].expected_output,
+ ShellIntegration::GetDesktopFileContents(
+ test_cases[i].template_contents,
+ GURL(test_cases[i].url),
+ ASCIIToUTF16(test_cases[i].title)));
+ }
+}
+#endif // defined(OS_LINUX)
diff --git a/chrome/browser/tab_contents/tab_contents.cc b/chrome/browser/tab_contents/tab_contents.cc
index f6e6687..36a5d7e 100644
--- a/chrome/browser/tab_contents/tab_contents.cc
+++ b/chrome/browser/tab_contents/tab_contents.cc
@@ -45,6 +45,7 @@
#include "chrome/browser/thumbnail_store.h"
#include "chrome/browser/search_engines/template_url_fetcher.h"
#include "chrome/browser/search_engines/template_url_model.h"
+#include "chrome/browser/shell_integration.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/page_action.h"
@@ -743,6 +744,10 @@ void TabContents::CreateShortcut() {
if (!entry)
return;
+#if defined(OS_LINUX)
+ // TODO(phajdan.jr): Finish creating shortcuts (UI etc).
+ ShellIntegration::CreateDesktopShortcut(GetURL(), GetTitle());
+#else
// We only allow one pending install request. By resetting the page id we
// effectively cancel the pending install request.
pending_install_.page_id = entry->page_id();
@@ -760,6 +765,7 @@ void TabContents::CreateShortcut() {
// Request the application info. When done OnDidGetApplicationInfo is invoked
// and we'll create the shortcut.
render_view_host()->GetApplicationInfo(pending_install_.page_id);
+#endif
}
void TabContents::ShowPageInfo(const GURL& url,
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index 4411daef..053792a 100644
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -3916,6 +3916,7 @@
'browser/sessions/session_service_test_helper.h',
'browser/sessions/session_service_unittest.cc',
'browser/sessions/tab_restore_service_unittest.cc',
+ 'browser/shell_integration_unittest.cc',
'browser/spellcheck_unittest.cc',
'browser/ssl/ssl_host_state_unittest.cc',
'browser/sync/glue/bookmark_model_worker_unittest.cc',