/* * Copyright © 2005 Jorn Baayen * Copyright © 2005 Christian Persch * * Based on the work of: * * Copyright © 2004 Bastien Nocera * Copyright © 2002 David A. Schleef * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * $Id$ */ #include #include "config.h" #include #include #include #include #include #include "ephy-stock-icons.h" #include #include #include #include "../../embed/mozilla/EphyUtils.h" /* NOTE: For simplicity, we use the Epiphany domain for translations, * instead of setting up another one for just a few strings. So we * don't need gi18n-lib.h here. */ #include #define DESKTOP_FILE_MIME_TYPE "application/x-desktop" #define URL_FILE_MIME_TYPE_1 "text/x-uri" #define URL_FILE_MIME_TYPE_2 "application/x-mswinurl" static const char kDesktopEntry[] = "Desktop Entry"; static const char kInternetShortcut[] = "InternetShortcut"; static const char kInternetShortcutW[] = "InternetShortcut.W"; typedef enum { FORMAT_DESKTOP_FILE, FORMAT_URL_FILE } FileFormat; typedef struct { NPP instance; guint format : 2; guint handled : 1; } Plugin; static NPNetscapeFuncs mozilla_functions; static char * parse_desktop_file (const char *filename) { GKeyFile *keyfile = g_key_file_new (); if (!g_key_file_load_from_file (keyfile, filename, (GKeyFileFlags) 0, NULL)) { /* Not a valid key file */ g_key_file_free (keyfile); return NULL; } char *group = g_key_file_get_start_group (keyfile); if (!group || strcmp (group, kDesktopEntry) != 0) { /* Not a valid Desktop file */ g_free (group); g_key_file_free (keyfile); return NULL; } g_free (group); char *encoding = g_key_file_get_string (keyfile, kDesktopEntry, "Encoding", NULL); if (!encoding || strcmp (encoding, "UTF-8") != 0) { /* Not a properly encoded desktop file */ g_free (encoding); g_key_file_free (keyfile); return NULL; } g_free (encoding); char *type = g_key_file_get_string (keyfile, kDesktopEntry, "Type", NULL); if (!type || strcmp (type, "Link") != 0) { /* Not a "Link" file */ g_free (type); g_key_file_free (keyfile); return NULL; } g_free (type); char *url = g_key_file_get_string (keyfile, kDesktopEntry, "URL", NULL); if (!url || !url[0]) { /* Not a valid URL */ g_free (url); g_key_file_free (keyfile); return NULL; } g_key_file_free (keyfile); return url; } static char * parse_url_file (const char *filename) { char *contents = NULL; gsize len = 0; if (!g_file_get_contents (filename, &contents, &len, NULL)) { return NULL; } /* URL files are encoded in MS-ANSI, so convert to UTF-8 first */ gsize bytes_read, bytes_written; char *converted = g_convert (contents, len, "UTF-8", "MS-ANSI", &bytes_read, &bytes_written, NULL); g_free (contents); if (converted == NULL) { return NULL; } /* Now load as keyfile */ GKeyFile *keyfile = g_key_file_new (); if (!g_key_file_load_from_data (keyfile, converted, strlen (converted), (GKeyFileFlags) 0, NULL)) { /* Not a valid key file */ g_free (converted); g_key_file_free (keyfile); return NULL; } g_free (converted); /* First try the [InternetShortcut.W] section */ if (g_key_file_has_group (keyfile, kInternetShortcutW)) { char *entry = g_key_file_get_string (keyfile, kInternetShortcutW, "URL", NULL); if (!entry || !entry[0]) { g_free (entry); g_key_file_free (keyfile); return NULL; } /* The URL is encoded in UTF-7 */ char *url = g_convert (entry, strlen (entry), "UTF-8", "UTF-7", &bytes_read, &bytes_written, NULL); g_free (entry); if (!url || !url[0]) { g_free (url); g_key_file_free (keyfile); return NULL; } g_key_file_free (keyfile); return url; } /* No [InternetShortcut.W] section, fallback to [InternetShortcut] */ if (g_key_file_has_group (keyfile, kInternetShortcut)) { char *url = g_key_file_get_string (keyfile, kInternetShortcut, "URL", NULL); if (!url || !url[0]) { g_free (url); g_key_file_free (keyfile); return NULL; } return url; } g_key_file_free (keyfile); return NULL; } static void show_error_dialog (NPP instance, const char *primary_text, const char *secondary_text) { GtkWidget *dialog, *parent; nsCOMPtr domWin; mozilla_functions.getvalue (instance, NPNVDOMWindow, static_cast(getter_AddRefs (domWin))); parent = EphyUtils::FindGtkParent (domWin); dialog = gtk_message_dialog_new (GTK_WINDOW (parent), (GtkDialogFlags) (GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, primary_text, NULL); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), secondary_text); gtk_window_set_icon_name (GTK_WINDOW (dialog), EPHY_STOCK_EPHY); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL); if (parent && GTK_WINDOW (parent)->group) { gtk_window_group_add_window (GTK_WINDOW (parent)->group, GTK_WINDOW (dialog)); } gtk_widget_show (dialog); } /* Check for unsafe protocols. See: * http://www.mozilla.org/projects/security/components/reviewguide.html */ #define JAVASCRIPT_PROTOCOL "javascript:" #define DATA_PROTOCOL "data:" static gboolean is_safe_url (char *url) { /* FIXME: when we allow non-file: .desktop files, add a security check here, * like the one HTPP protocol handler does on redirect. */ url = g_strstrip (url); return (g_ascii_strncasecmp (url, JAVASCRIPT_PROTOCOL, strlen (JAVASCRIPT_PROTOCOL)) != 0 && g_ascii_strncasecmp (url, DATA_PROTOCOL, strlen (DATA_PROTOCOL)) != 0); } static NPError plugin_new_instance (NPMIMEType mime_type, NPP instance, guint16 mode, gint16 argc, char **argn, char **argv, NPSavedData *saved) { if (instance == NULL) return NPERR_INVALID_INSTANCE_ERROR; /* We don't do embedded */ if (mode != NP_FULL) return NPERR_INVALID_PARAM; /* FIXME: Until I've thought more about security when loading .desktop * files from non-file: locations, only allow local desktop files. */ PRBool isSafe = PR_FALSE; for (gint16 i = 0; i < argc; ++i) { if (argn[i] && g_ascii_strcasecmp (argn[i], "src") == 0) { isSafe = argv[i] && g_ascii_strncasecmp (argv[i], "file:/", strlen ("file:/")) == 0; break; } } if (!isSafe) return NPERR_INVALID_URL; instance->pdata = mozilla_functions.memalloc (sizeof (Plugin)); Plugin *plugin = (Plugin *) instance->pdata; if (plugin == NULL) return NPERR_OUT_OF_MEMORY_ERROR; memset (plugin, 0, sizeof (Plugin)); plugin->instance = instance; mozilla_functions.setvalue (plugin->instance, NPPVpluginWindowBool, GINT_TO_POINTER (FALSE)); return NPERR_NO_ERROR; } static NPError plugin_destroy_instance (NPP instance, NPSavedData **save) { if (instance == NULL) return NPERR_INVALID_INSTANCE_ERROR; Plugin *plugin = (Plugin *) instance->pdata; if (plugin == NULL) return NPERR_NO_ERROR; mozilla_functions.memfree (instance->pdata); instance->pdata = NULL; return NPERR_NO_ERROR; } static NPError plugin_new_stream (NPP instance, NPMIMEType type, NPStream *stream_ptr, NPBool seekable, guint16 *stype) { if (instance == NULL || type == NULL) return NPERR_INVALID_INSTANCE_ERROR; Plugin *plugin = (Plugin *) instance->pdata; if (plugin == NULL) return NPERR_INVALID_INSTANCE_ERROR; if (type && strcmp (type, DESKTOP_FILE_MIME_TYPE) == 0) { plugin->format = FORMAT_DESKTOP_FILE; } else if (type && (strcmp (type, URL_FILE_MIME_TYPE_1) == 0 || strcmp (type, URL_FILE_MIME_TYPE_2) == 0)) { plugin->format = FORMAT_URL_FILE; } else { return NPERR_INVALID_PARAM; } *stype = NP_ASFILEONLY; plugin->handled = FALSE; return NPERR_NO_ERROR; } static NPError plugin_stream_as_file (NPP instance, NPStream* stream, const char *filename) { if (instance == NULL) return NPERR_INVALID_INSTANCE_ERROR; Plugin *plugin = (Plugin *) instance->pdata; if (plugin == NULL) return NPERR_INVALID_INSTANCE_ERROR; if (!filename) return NPERR_INVALID_PARAM; char *url = NULL; if (plugin->format == FORMAT_DESKTOP_FILE) { url = parse_desktop_file (filename); } else if (plugin->format == FORMAT_URL_FILE) { url = parse_url_file (filename); } if (!url) { return NPERR_GENERIC_ERROR; } plugin->handled = TRUE; if (is_safe_url (url)) { mozilla_functions.geturl (instance, url, "_top"); } else { /* Load blank page, so that further drags to the embed work */ mozilla_functions.geturl (instance, "about:blank", "_top"); show_error_dialog (instance, _("Unsafe protocol."), _("The address has not been " "loaded, because it refers to an " "unsafe protocol and thereby presents " "a security risk to your system.")); } g_free (url); return NPERR_NO_ERROR; } static NPError plugin_destroy_stream (NPP instance, NPStream *stream, NPError reason) { if (instance == NULL) return NPERR_INVALID_INSTANCE_ERROR; Plugin *plugin = (Plugin *) instance->pdata; if (plugin == NULL) return NPERR_INVALID_INSTANCE_ERROR; if (!plugin->handled) { /* Load blank page, so that further drags to the embed work */ mozilla_functions.geturl (instance, "about:blank", "_top"); show_error_dialog (instance, _("No address found."), _("No web address could be found in this file.")); } return NPERR_NO_ERROR; } static int32 plugin_write_ready (NPP instance, NPStream *stream) { /* Ready to read 8 KB - should do. Can always get more. */ return 8192; } static int32 plugin_write (NPP instance, NPStream *stream, int32 offset, int32 len, gpointer buffer) { /* FIXME */ return len; } static NPError plugin_get_value (NPP instance, NPPVariable variable, gpointer value) { NPError err = NPERR_NO_ERROR; switch (variable) { case NPPVpluginNameString: /* Translators: "Desktop File" refers to .desktop files containing a link */ *((char **) value) = _("Epiphany Desktop File Plugin"); break; case NPPVpluginDescriptionString: *((char **) value) = _("This plugin handles “.desktop” and “.url” files containing web links."); break; case NPPVpluginNeedsXEmbed: *((NPBool *) value) = PR_FALSE; break; default: err = NPERR_INVALID_PARAM; break; } return err; } NPError NP_GetValue (void *future, NPPVariable variable, gpointer value) { return plugin_get_value (NULL, variable, value); } char * NP_GetMIMEDescription (void) { return DESKTOP_FILE_MIME_TYPE ":desktop:desktop link file;" URL_FILE_MIME_TYPE_1 ":url:URL file;" URL_FILE_MIME_TYPE_2 "::URL file;"; } NPError NP_Initialize (NPNetscapeFuncs *moz_funcs, NPPluginFuncs *plugin_funcs) { if (moz_funcs == NULL || plugin_funcs == NULL) return NPERR_INVALID_FUNCTABLE_ERROR; if ((moz_funcs->version >> 8) > NP_VERSION_MAJOR) return NPERR_INCOMPATIBLE_VERSION_ERROR; if (moz_funcs->size < sizeof (NPNetscapeFuncs)) return NPERR_INVALID_FUNCTABLE_ERROR; if (plugin_funcs->size < sizeof (NPPluginFuncs)) return NPERR_INVALID_FUNCTABLE_ERROR; /* * Copy all of the fields of the Mozilla function table into our * copy so we can call back into Mozilla later. Note that we need * to copy the fields one by one, rather than assigning the whole * structure, because the Mozilla function table could actually be * bigger than what we expect. */ mozilla_functions.size = moz_funcs->size; mozilla_functions.version = moz_funcs->version; mozilla_functions.geturl = moz_funcs->geturl; mozilla_functions.posturl = moz_funcs->posturl; mozilla_functions.requestread = moz_funcs->requestread; mozilla_functions.newstream = moz_funcs->newstream; mozilla_functions.write = moz_funcs->write; mozilla_functions.destroystream = moz_funcs->destroystream; mozilla_functions.status = moz_funcs->status; mozilla_functions.uagent = moz_funcs->uagent; mozilla_functions.memalloc = moz_funcs->memalloc; mozilla_functions.memfree = moz_funcs->memfree; mozilla_functions.memflush = moz_funcs->memflush; mozilla_functions.reloadplugins = moz_funcs->reloadplugins; mozilla_functions.getJavaEnv = moz_funcs->getJavaEnv; mozilla_functions.getJavaPeer = moz_funcs->getJavaPeer; mozilla_functions.geturlnotify = moz_funcs->geturlnotify; mozilla_functions.posturlnotify = moz_funcs->posturlnotify; mozilla_functions.getvalue = moz_funcs->getvalue; mozilla_functions.setvalue = moz_funcs->setvalue; mozilla_functions.invalidaterect = moz_funcs->invalidaterect; mozilla_functions.invalidateregion = moz_funcs->invalidateregion; mozilla_functions.forceredraw = moz_funcs->forceredraw; mozilla_functions.geturl = moz_funcs->geturl; /* * Set up a plugin function table that Mozilla will use to call * into us. Mozilla needs to know about our version and size and * have a UniversalProcPointer for every function we implement. */ plugin_funcs->size = sizeof (NPPluginFuncs); plugin_funcs->version = (NP_VERSION_MAJOR << 8) + NP_VERSION_MINOR; plugin_funcs->newp = NewNPP_NewProc (plugin_new_instance); plugin_funcs->destroy = NewNPP_DestroyProc (plugin_destroy_instance); plugin_funcs->setwindow = NewNPP_SetWindowProc (NULL); plugin_funcs->newstream = NewNPP_NewStreamProc (plugin_new_stream); plugin_funcs->destroystream = NewNPP_DestroyStreamProc (plugin_destroy_stream); plugin_funcs->asfile = NewNPP_StreamAsFileProc (plugin_stream_as_file); plugin_funcs->writeready = NewNPP_WriteReadyProc (plugin_write_ready); plugin_funcs->write = NewNPP_WriteProc (plugin_write); plugin_funcs->print = NewNPP_PrintProc (NULL); plugin_funcs->event = NewNPP_HandleEventProc (NULL); plugin_funcs->urlnotify = NewNPP_URLNotifyProc (NULL); plugin_funcs->javaClass = NULL; plugin_funcs->getvalue = NewNPP_GetValueProc (plugin_get_value); plugin_funcs->setvalue = NewNPP_SetValueProc (NULL); return NPERR_NO_ERROR; } NPError NP_Shutdown (void) { return NPERR_NO_ERROR; }