diff options
Diffstat (limited to 'mail')
-rw-r--r-- | mail/Makefile.am | 2 | ||||
-rw-r--r-- | mail/e-http-request.c | 515 | ||||
-rw-r--r-- | mail/e-http-request.h | 65 | ||||
-rw-r--r-- | mail/e-mail-display.c | 19 | ||||
-rw-r--r-- | mail/e-mail-request.c | 548 |
5 files changed, 603 insertions, 546 deletions
diff --git a/mail/Makefile.am b/mail/Makefile.am index 72ca86708b..7358f359a5 100644 --- a/mail/Makefile.am +++ b/mail/Makefile.am @@ -39,6 +39,7 @@ libevolution_mail_la_CPPFLAGS = \ -DG_LOG_DOMAIN=\"evolution-mail\" mailinclude_HEADERS = \ + e-http-request.h \ e-mail.h \ e-mail-account-manager.h \ e-mail-account-store.h \ @@ -105,6 +106,7 @@ mailinclude_HEADERS += \ endif libevolution_mail_la_SOURCES = \ + e-http-request.c \ e-mail-account-manager.c \ e-mail-account-store.c \ e-mail-account-tree-view.c \ diff --git a/mail/e-http-request.c b/mail/e-http-request.c new file mode 100644 index 0000000000..f56ba8fc43 --- /dev/null +++ b/mail/e-http-request.c @@ -0,0 +1,515 @@ +/* + * e-http-request.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + */ + +#include "e-http-request.h" + +#define LIBSOUP_USE_UNSTABLE_REQUEST_API +#include <libsoup/soup.h> +#include <libsoup/soup-requester.h> +#include <libsoup/soup-request-http.h> +#include <camel/camel.h> +#include <webkit/webkit.h> + +#include <e-util/e-util.h> + +#include <string.h> + +#include "em-format-html.h" + + +#define d(x) + +#define E_HTTP_REQUEST_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_HTTP_REQUEST, EHTTPRequestPrivate)) + +struct _EHTTPRequestPrivate { + gchar *content_type; + gint content_length; + + EMFormatHTML *efh; +}; + +G_DEFINE_TYPE (EHTTPRequest, e_http_request, SOUP_TYPE_REQUEST) + +struct http_request_async_data { + GMainLoop *loop; + GCancellable *cancellable; + CamelDataCache *cache; + gchar *cache_key; + + CamelStream *cache_stream; + gchar *content_type; + goffset content_length; + + gchar *buff; +}; + +static void +http_request_write_to_cache (GInputStream *stream, + GAsyncResult *res, + struct http_request_async_data *data) +{ + GError *error; + gssize len; + + error = NULL; + len = g_input_stream_read_finish (stream, res, &error); + + /* Error while reading data */ + if (len == -1) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_message ("Error while reading input stream: %s", + error ? error->message : "Unknown error"); + g_clear_error (&error); + } + + /* Don't keep broken data in cache */ + camel_data_cache_remove (data->cache, "http", data->cache_key, NULL); + + goto cleanup; + } + + /* EOF */ + if (len == 0) { + goto cleanup; + } + + /* Write chunk to cache and read another block of data. */ + camel_stream_write (data->cache_stream, data->buff, len, + data->cancellable, NULL); + + g_input_stream_read_async (stream, data->buff, 4096, + G_PRIORITY_DEFAULT, data->cancellable, + (GAsyncReadyCallback) http_request_write_to_cache, data); + + return; + + cleanup: + if (data->buff) + g_free (data->buff); + + g_object_unref (stream); + + g_main_loop_quit (data->loop); +} + +static void +http_request_finished (SoupRequest *request, + GAsyncResult *res, + struct http_request_async_data *data) +{ + GError *error; + SoupMessage *message; + GInputStream *stream; + + error = NULL; + stream = soup_request_send_finish (request, res, &error); + /* If there is an error or the operation was canceled, do nothing */ + if (error) { + g_main_loop_quit (data->loop); + g_error_free (error); + return; + } + + if (!stream) { + g_warning("HTTP request failed: %s", error ? error->message: "Unknown error"); + g_clear_error (&error); + + g_main_loop_quit (data->loop); + return; + } + + message = soup_request_http_get_message (SOUP_REQUEST_HTTP (request)); + if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) { + g_warning ("HTTP request failed: HTTP code %d", message->status_code); + g_object_unref (message); + + g_main_loop_quit (data->loop); + return; + } + + g_object_unref (message); + + data->content_length = soup_request_get_content_length (request); + data->content_type = g_strdup (soup_request_get_content_type (request)); + + if (!data->cache_stream || g_cancellable_is_cancelled (data->cancellable)) { + g_main_loop_quit (data->loop); + return; + } + + data->buff = g_malloc (4096); + g_input_stream_read_async (stream, data->buff, 4096, + G_PRIORITY_DEFAULT, data->cancellable, + (GAsyncReadyCallback) http_request_write_to_cache, data); +} + +static gssize +copy_stream_to_stream (CamelStream *input, + GMemoryInputStream *output, + GCancellable *cancellable) +{ + gchar *buff; + gssize read_len = 0; + gssize total_len = 0; + + g_seekable_seek (G_SEEKABLE (input), 0, G_SEEK_SET, cancellable, NULL); + + buff = g_malloc (4096); + while ((read_len = camel_stream_read (input, buff, 4096, cancellable, NULL)) > 0) { + + g_memory_input_stream_add_data (output, buff, read_len, g_free); + + total_len += read_len; + + buff = g_malloc (4096); + } + + /* Free the last unused buffer */ + g_free (buff); + + return total_len; +} + +static void +handle_http_request (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + EHTTPRequest *request = E_HTTP_REQUEST (object); + SoupURI *soup_uri; + gchar *evo_uri, *uri; + GInputStream *stream; + gboolean force_load_images = FALSE; + gchar *uri_md5; + + const gchar *user_cache_dir; + CamelDataCache *cache; + CamelStream *cache_stream; + + GHashTable *query; + + if (g_cancellable_is_cancelled (cancellable)) { + return; + } + + /* Remove the __evo-mail query */ + soup_uri = soup_request_get_uri (SOUP_REQUEST (request)); + query = soup_form_decode (soup_uri->query); + g_hash_table_remove (query, "__evo-mail"); + + /* Remove __evo-load-images if present (and in such case set + * force_load_images to TRUE) */ + force_load_images = g_hash_table_remove (query, "__evo-load-images"); + + soup_uri_set_query_from_form (soup_uri, query); + g_hash_table_unref (query); + + evo_uri = soup_uri_to_string (soup_uri, FALSE); + + /* Remove the "evo-" prefix from scheme */ + if (evo_uri && (strlen (evo_uri) > 5)) { + uri = g_strdup (&evo_uri[4]); + g_free (evo_uri); + } + + g_return_if_fail (uri && *uri); + + /* Use MD5 hash of the URI as a filname of the resourec cache file. + * We were previously using the URI as a filename but the URI is + * sometimes too long for a filename. */ + uri_md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, uri, -1); + + /* Open Evolution's cache */ + user_cache_dir = e_get_user_cache_dir (); + cache = camel_data_cache_new (user_cache_dir, NULL); + if (cache) { + camel_data_cache_set_expire_age (cache, 24 * 60 * 60); + camel_data_cache_set_expire_access (cache, 2 * 60 * 60); + } + + /* Found item in cache! */ + cache_stream = camel_data_cache_get (cache, "http", uri_md5, NULL); + if (cache_stream) { + + gssize len; + + stream = g_memory_input_stream_new (); + + len = copy_stream_to_stream (cache_stream, + G_MEMORY_INPUT_STREAM (stream), cancellable); + request->priv->content_length = len; + + g_object_unref (cache_stream); + + /* When succesfully read some data from cache then + * get mimetype and return the stream to WebKit. + * Otherwise try to fetch the resource again from the network. */ + if ((len != -1) && (request->priv->content_length > 0)) { + GFile *file; + GFileInfo *info; + gchar *path; + + path = camel_data_cache_get_filename (cache, "http", uri_md5, NULL); + file = g_file_new_for_path (path); + info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + 0, cancellable, NULL); + + request->priv->content_type = g_strdup ( + g_file_info_get_content_type (info)); + + d(printf ("'%s' found in cache (%d bytes, %s)\n", + uri, request->priv->content_length, + request->priv->content_type)); + + g_object_unref (info); + g_object_unref (file); + g_free (path); + + /* Set result and quit the thread */ + g_simple_async_result_set_op_res_gpointer (res, stream, NULL); + + goto cleanup; + } else { + d(printf("Failed to load '%s' from cache.\n", uri)); + } + } + + /* Item not found in cache, but image loading policy allows us to fetch + * it from the interwebs */ + if (force_load_images || em_format_html_can_load_images (request->priv->efh)) { + + SoupRequester *requester; + SoupRequest *http_request; + SoupSession *session; + GMainContext *context; + GError *error; + + struct http_request_async_data data = { 0 }; + + context = g_main_context_get_thread_default (); + session = soup_session_async_new_with_options ( + SOUP_SESSION_ASYNC_CONTEXT, context, NULL); + + requester = soup_requester_new (); + soup_session_add_feature (session, SOUP_SESSION_FEATURE (requester)); + + http_request = soup_requester_request (requester, uri, NULL); + + error = NULL; + data.loop = g_main_loop_new (context, TRUE); + data.cancellable = cancellable; + data.cache = cache; + data.cache_key = uri_md5; + data.cache_stream = camel_data_cache_add (cache, "http", uri_md5, &error); + + if (!data.cache_stream) { + g_warning ("Failed to create cache file for '%s': %s", + uri, error ? error->message : "Unknown error"); + g_clear_error (&error); + + /* We rely on existence of the stream. If CamelDataCache + * failed to create a cache file, then store it at least + * temporarily. */ + data.cache_stream = camel_stream_mem_new (); + } + + /* Send the request and waint in mainloop until it's finished + * and copied to cache */ + d(printf(" '%s' not in cache, sending HTTP request\n", uri)); + soup_request_send_async (http_request, cancellable, + (GAsyncReadyCallback) http_request_finished, &data); + + /* Wait for the asynchronous HTTP GET to finish */ + g_main_loop_run (data.loop); + d(printf (" '%s' fetched from internet and (hopefully) stored in" + " cache\n", uri)); + + g_main_loop_unref (data.loop); + + g_object_unref (session); + g_object_unref (http_request); + g_object_unref (requester); + + /* Copy the content of cache stream to GInputStream that can be + * returned to WebKit */ + stream = g_memory_input_stream_new (); + copy_stream_to_stream (data.cache_stream, + G_MEMORY_INPUT_STREAM (stream), cancellable); + + camel_stream_close (data.cache_stream, cancellable, NULL); + g_object_unref (data.cache_stream); + + request->priv->content_length = data.content_length; + request->priv->content_type = data.content_type; + + g_simple_async_result_set_op_res_gpointer (res, stream, NULL); + + goto cleanup; + + } + + cleanup: + g_free (uri); + g_free (uri_md5); +} + +static void +http_request_finalize (GObject *object) +{ + EHTTPRequest *request = E_HTTP_REQUEST (object); + + if (request->priv->content_type) { + g_free (request->priv->content_type); + request->priv->content_type = NULL; + } + + if (request->priv->efh) { + g_object_unref (request->priv->efh); + request->priv->efh = NULL; + } + + G_OBJECT_CLASS (e_http_request_parent_class)->finalize (object); +} + +static gboolean +http_request_check_uri (SoupRequest *request, + SoupURI *uri, + GError **error) +{ + return ((strcmp (uri->scheme, "evo-http") == 0) || + (strcmp (uri->scheme, "evo-https") == 0)); +} + +static void +http_request_send_async (SoupRequest *request, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + EHTTPRequest *ehr; + GSimpleAsyncResult *result; + gchar *mail_uri; + SoupURI *uri; + const gchar *enc; + SoupSession *session; + GHashTable *formatters, *query; + + ehr = E_HTTP_REQUEST (request); + uri = soup_request_get_uri (request); + query = soup_form_decode (uri->query); + + d(printf("received request for %s\n", soup_uri_to_string (uri, FALSE))); + + enc = g_hash_table_lookup (query, "__evo-mail"); + + if (!enc || !*enc) { + g_hash_table_destroy (query); + return; + } + + mail_uri = soup_uri_decode (enc); + + session = webkit_get_default_session (); + formatters = g_object_get_data (G_OBJECT (session), "formatters"); + g_return_if_fail (formatters != NULL); + + ehr->priv->efh = g_hash_table_lookup (formatters, mail_uri); + g_free (mail_uri); + + g_return_if_fail (ehr->priv->efh); + + /* Make sure the formatter lives until we are finished here */ + g_object_ref (ehr->priv->efh); + + result = g_simple_async_result_new (G_OBJECT (request), callback, + user_data, http_request_send_async); + g_simple_async_result_run_in_thread (result, handle_http_request, + G_PRIORITY_DEFAULT, cancellable); + + g_hash_table_destroy (query); +} + +static GInputStream * +http_request_send_finish (SoupRequest *request, + GAsyncResult *result, + GError **error) +{ + GInputStream *stream; + + stream = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result)); + g_object_unref (result); + + /* Reset the stream before passing it back to webkit */ + if (stream && G_IS_SEEKABLE (stream)) + g_seekable_seek (G_SEEKABLE (stream), 0, G_SEEK_SET, NULL, NULL); + + if (!stream) /* We must always return something */ + stream = g_memory_input_stream_new (); + + return stream; +} + +static goffset +http_request_get_content_length (SoupRequest *request) +{ + EHTTPRequest *efr = E_HTTP_REQUEST (request); + + d(printf("Content-Length: %d bytes\n", efr->priv->content_length)); + return efr->priv->content_length; +} + +static const gchar * +http_request_get_content_type (SoupRequest *request) +{ + EHTTPRequest *efr = E_HTTP_REQUEST (request); + + d(printf("Content-Type: %s\n", efr->priv->content_type)); + + return efr->priv->content_type; +} + +static const gchar *data_schemes[] = { "evo-http", "evo-https", NULL }; + +static void +e_http_request_class_init (EHTTPRequestClass *class) +{ + GObjectClass *object_class; + SoupRequestClass *request_class; + + g_type_class_add_private (class, sizeof (EHTTPRequestPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->finalize = http_request_finalize; + + request_class = SOUP_REQUEST_CLASS (class); + request_class->schemes = data_schemes; + request_class->send_async = http_request_send_async; + request_class->send_finish = http_request_send_finish; + request_class->get_content_type = http_request_get_content_type; + request_class->get_content_length = http_request_get_content_length; + request_class->check_uri = http_request_check_uri; +} + +static void +e_http_request_init (EHTTPRequest *request) +{ + request->priv = E_HTTP_REQUEST_GET_PRIVATE (request); +} + diff --git a/mail/e-http-request.h b/mail/e-http-request.h new file mode 100644 index 0000000000..80480878f7 --- /dev/null +++ b/mail/e-http-request.h @@ -0,0 +1,65 @@ +/* + * e-http-request.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + */ + +#ifndef E_HTTP_REQUEST_H +#define E_HTTP_REQUEST_H + +#define LIBSOUP_USE_UNSTABLE_REQUEST_API + +#include <libsoup/soup.h> +#include <libsoup/soup-request.h> + +/* Standard GObject macros */ +#define E_TYPE_HTTP_REQUEST \ + (e_http_request_get_type ()) +#define E_HTTP_REQUEST(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_HTTP_REQUEST, EHTTPRequest)) +#define E_HTTP_REQUEST_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_HTTP_REQUEST, EHTTPRequestClass)) +#define E_IS_HTTP_REQUEST(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_HTTP_REQUEST)) +#define E_IS_HTTP_REQUEST_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_HTTP_REQUEST)) +#define E_HTTP_REQUEST_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_HTTP_REQUEST, EHTTPRequestClass)) + +G_BEGIN_DECLS + +typedef struct _EHTTPRequest EHTTPRequest; +typedef struct _EHTTPRequestClass EHTTPRequestClass; +typedef struct _EHTTPRequestPrivate EHTTPRequestPrivate; + +struct _EHTTPRequest { + SoupRequest parent; + EHTTPRequestPrivate *priv; +}; + +struct _EHTTPRequestClass { + SoupRequestClass parent; +}; + +GType e_http_request_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* E_HTTP_REQUEST_H */ diff --git a/mail/e-mail-display.c b/mail/e-mail-display.c index d1f3137c80..0b1c343646 100644 --- a/mail/e-mail-display.c +++ b/mail/e-mail-display.c @@ -23,7 +23,6 @@ #include <config.h> #endif -#define LIBSOUP_USE_UNSTABLE_REQUEST_API #include "e-mail-display.h" @@ -33,18 +32,18 @@ #include "e-util/e-marshal.h" #include "e-util/e-util.h" #include "e-util/e-plugin-ui.h" +#include "e-util/e-file-request.h" +#include "e-util/e-stock-request.h" #include "mail/em-composer-utils.h" #include "mail/em-utils.h" #include "mail/e-mail-request.h" +#include "mail/e-http-request.h" #include "mail/em-format-html-display.h" #include "mail/e-mail-attachment-bar.h" #include "widgets/misc/e-attachment-button.h" #include <camel/camel.h> -#include <libsoup/soup.h> -#include <libsoup/soup-requester.h> - #include <JavaScriptCore/JavaScript.h> #define d(x) @@ -1288,8 +1287,6 @@ e_mail_display_init (EMailDisplay *display) { GtkUIManager *ui_manager; GError *error = NULL; - SoupSession *session; - SoupSessionFeature *feature; const gchar *user_cache_dir; WebKitWebSettings *settings; WebKitWebFrame *main_frame; @@ -1364,12 +1361,10 @@ e_mail_display_init (EMailDisplay *display) g_error_free (error); } - /* Register our own handler for our own mail:// protocol */ - session = webkit_get_default_session (); - feature = SOUP_SESSION_FEATURE (soup_requester_new ()); - soup_session_feature_add_feature (feature, E_TYPE_MAIL_REQUEST); - soup_session_add_feature (session, feature); - g_object_unref (feature); + e_web_view_install_request_handler (E_WEB_VIEW (display), E_TYPE_MAIL_REQUEST); + e_web_view_install_request_handler (E_WEB_VIEW (display), E_TYPE_HTTP_REQUEST); + e_web_view_install_request_handler (E_WEB_VIEW (display), E_TYPE_FILE_REQUEST); + e_web_view_install_request_handler (E_WEB_VIEW (display), E_TYPE_STOCK_REQUEST); /* cache expiry - 2 hour access, 1 day max */ user_cache_dir = e_get_user_cache_dir (); diff --git a/mail/e-mail-request.c b/mail/e-mail-request.c index dd5204ccd1..8a51b2a226 100644 --- a/mail/e-mail-request.c +++ b/mail/e-mail-request.c @@ -132,463 +132,6 @@ handle_mail_request (GSimpleAsyncResult *res, } static void -handle_file_request (GSimpleAsyncResult *res, - GObject *object, - GCancellable *cancellable) -{ - EMailRequest *request = E_MAIL_REQUEST (object); - SoupURI *uri; - GInputStream *stream; - gchar *contents; - gsize length; - - if (g_cancellable_is_cancelled (cancellable)) - return; - - uri = soup_request_get_uri (SOUP_REQUEST (request)); - - if (g_file_get_contents (uri->path, &contents, &length, NULL)) { - - request->priv->mime_type = g_content_type_guess (uri->path, NULL, 0, NULL); - request->priv->content_length = length; - - stream = g_memory_input_stream_new_from_data ( - contents, length, (GDestroyNotify) g_free); - g_simple_async_result_set_op_res_gpointer (res, stream, NULL); - } -} - -struct http_request_async_data { - GMainLoop *loop; - GCancellable *cancellable; - CamelDataCache *cache; - gchar *cache_key; - - CamelStream *cache_stream; - gchar *content_type; - goffset content_length; - - gchar *buff; -}; - -static void -http_request_write_to_cache (GInputStream *stream, - GAsyncResult *res, - struct http_request_async_data *data) -{ - GError *error; - gssize len; - - error = NULL; - len = g_input_stream_read_finish (stream, res, &error); - - /* Error while reading data */ - if (len == -1) { - if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { - g_message ("Error while reading input stream: %s", - error ? error->message : "Unknown error"); - g_clear_error (&error); - } - - /* Don't keep broken data in cache */ - camel_data_cache_remove (data->cache, "http", data->cache_key, NULL); - - goto cleanup; - } - - /* EOF */ - if (len == 0) { - goto cleanup; - } - - /* Write chunk to cache and read another block of data. */ - camel_stream_write (data->cache_stream, data->buff, len, - data->cancellable, NULL); - - g_input_stream_read_async (stream, data->buff, 4096, - G_PRIORITY_DEFAULT, data->cancellable, - (GAsyncReadyCallback) http_request_write_to_cache, data); - - return; - - cleanup: - if (data->buff) - g_free (data->buff); - - g_object_unref (stream); - - g_main_loop_quit (data->loop); -} - -static void -http_request_finished (SoupRequest *request, - GAsyncResult *res, - struct http_request_async_data *data) -{ - GError *error; - SoupMessage *message; - GInputStream *stream; - - error = NULL; - stream = soup_request_send_finish (request, res, &error); - /* If there is an error or the operation was canceled, do nothing */ - if (error) { - g_main_loop_quit (data->loop); - g_error_free (error); - return; - } - - if (!stream) { - g_warning("HTTP request failed: %s", error ? error->message: "Unknown error"); - g_clear_error (&error); - - g_main_loop_quit (data->loop); - return; - } - - message = soup_request_http_get_message (SOUP_REQUEST_HTTP (request)); - if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) { - g_warning ("HTTP request failed: HTTP code %d", message->status_code); - g_object_unref (message); - - g_main_loop_quit (data->loop); - return; - } - - g_object_unref (message); - - data->content_length = soup_request_get_content_length (request); - data->content_type = g_strdup (soup_request_get_content_type (request)); - - if (!data->cache_stream || g_cancellable_is_cancelled (data->cancellable)) { - g_main_loop_quit (data->loop); - return; - } - - data->buff = g_malloc (4096); - g_input_stream_read_async (stream, data->buff, 4096, - G_PRIORITY_DEFAULT, data->cancellable, - (GAsyncReadyCallback) http_request_write_to_cache, data); -} - -static gssize -copy_stream_to_stream (CamelStream *input, - GMemoryInputStream *output, - GCancellable *cancellable) -{ - gchar *buff; - gssize read_len = 0; - gssize total_len = 0; - - g_seekable_seek (G_SEEKABLE (input), 0, G_SEEK_SET, cancellable, NULL); - - buff = g_malloc (4096); - while ((read_len = camel_stream_read (input, buff, 4096, cancellable, NULL)) > 0) { - - g_memory_input_stream_add_data (output, buff, read_len, g_free); - - total_len += read_len; - - buff = g_malloc (4096); - } - - /* Free the last unused buffer */ - g_free (buff); - - return total_len; -} - -static void -handle_http_request (GSimpleAsyncResult *res, - GObject *object, - GCancellable *cancellable) -{ - EMailRequest *request = E_MAIL_REQUEST (object); - SoupURI *soup_uri; - gchar *evo_uri, *uri; - GInputStream *stream; - gboolean force_load_images = FALSE; - gchar *uri_md5; - - const gchar *user_cache_dir; - CamelDataCache *cache; - CamelStream *cache_stream; - - GHashTable *query; - - if (g_cancellable_is_cancelled (cancellable)) { - return; - } - - /* Remove the __evo-mail query */ - soup_uri = soup_request_get_uri (SOUP_REQUEST (request)); - query = soup_form_decode (soup_uri->query); - g_hash_table_remove (query, "__evo-mail"); - - /* Remove __evo-load-images if present (and in such case set - * force_load_images to TRUE) */ - force_load_images = g_hash_table_remove (query, "__evo-load-images"); - - soup_uri_set_query_from_form (soup_uri, query); - g_hash_table_unref (query); - - evo_uri = soup_uri_to_string (soup_uri, FALSE); - - /* Remove the "evo-" prefix from scheme */ - if (evo_uri && (strlen (evo_uri) > 5)) { - uri = g_strdup (&evo_uri[4]); - g_free (evo_uri); - } - - g_return_if_fail (uri && *uri); - - /* Use MD5 hash of the URI as a filname of the resourec cache file. - * We were previously using the URI as a filename but the URI is - * sometimes too long for a filename. */ - uri_md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, uri, -1); - - /* Open Evolution's cache */ - user_cache_dir = e_get_user_cache_dir (); - cache = camel_data_cache_new (user_cache_dir, NULL); - if (cache) { - camel_data_cache_set_expire_age (cache, 24 * 60 * 60); - camel_data_cache_set_expire_access (cache, 2 * 60 * 60); - } - - /* Found item in cache! */ - cache_stream = camel_data_cache_get (cache, "http", uri_md5, NULL); - if (cache_stream) { - - gssize len; - - stream = g_memory_input_stream_new (); - - len = copy_stream_to_stream (cache_stream, - G_MEMORY_INPUT_STREAM (stream), cancellable); - request->priv->content_length = len; - - g_object_unref (cache_stream); - - /* When succesfully read some data from cache then - * get mimetype and return the stream to WebKit. - * Otherwise try to fetch the resource again from the network. */ - if ((len != -1) && (request->priv->content_length > 0)) { - GFile *file; - GFileInfo *info; - gchar *path; - - path = camel_data_cache_get_filename (cache, "http", uri_md5, NULL); - file = g_file_new_for_path (path); - info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, - 0, cancellable, NULL); - - request->priv->mime_type = g_strdup ( - g_file_info_get_content_type (info)); - - d(printf ("'%s' found in cache (%d bytes, %s)\n", - uri, request->priv->content_length, - request->priv->mime_type)); - - g_object_unref (info); - g_object_unref (file); - g_free (path); - - /* Set result and quit the thread */ - g_simple_async_result_set_op_res_gpointer (res, stream, NULL); - - goto cleanup; - } else { - d(printf("Failed to load '%s' from cache.\n", uri)); - } - } - - /* Item not found in cache, but image loading policy allows us to fetch - * it from the interwebs */ - if (force_load_images || em_format_html_can_load_images (request->priv->efh)) { - - SoupRequester *requester; - SoupRequest *http_request; - SoupSession *session; - GMainContext *context; - GError *error; - - struct http_request_async_data data = { 0 }; - - context = g_main_context_get_thread_default (); - session = soup_session_async_new_with_options ( - SOUP_SESSION_ASYNC_CONTEXT, context, NULL); - - requester = soup_requester_new (); - soup_session_add_feature (session, SOUP_SESSION_FEATURE (requester)); - - http_request = soup_requester_request (requester, uri, NULL); - - error = NULL; - data.loop = g_main_loop_new (context, TRUE); - data.cancellable = cancellable; - data.cache = cache; - data.cache_key = uri_md5; - data.cache_stream = camel_data_cache_add (cache, "http", uri_md5, &error); - - if (!data.cache_stream) { - g_warning ("Failed to create cache file for '%s': %s", - uri, error ? error->message : "Unknown error"); - g_clear_error (&error); - - /* We rely on existence of the stream. If CamelDataCache - * failed to create a cache file, then store it at least - * temporarily. */ - data.cache_stream = camel_stream_mem_new (); - } - - /* Send the request and waint in mainloop until it's finished - * and copied to cache */ - d(printf(" '%s' not in cache, sending HTTP request\n", uri)); - soup_request_send_async (http_request, cancellable, - (GAsyncReadyCallback) http_request_finished, &data); - - /* Wait for the asynchronous HTTP GET to finish */ - g_main_loop_run (data.loop); - d(printf (" '%s' fetched from internet and (hopefully) stored in" - " cache\n", uri)); - - g_main_loop_unref (data.loop); - - g_object_unref (session); - g_object_unref (http_request); - g_object_unref (requester); - - /* Copy the content of cache stream to GInputStream that can be - * returned to WebKit */ - stream = g_memory_input_stream_new (); - copy_stream_to_stream (data.cache_stream, - G_MEMORY_INPUT_STREAM (stream), cancellable); - - camel_stream_close (data.cache_stream, cancellable, NULL); - g_object_unref (data.cache_stream); - - request->priv->content_length = data.content_length; - request->priv->mime_type = data.content_type; - - g_simple_async_result_set_op_res_gpointer (res, stream, NULL); - - goto cleanup; - - } - - cleanup: - g_free (uri); - g_free (uri_md5); -} - -static void -handle_stock_request (GSimpleAsyncResult *res, - GObject *object, - GCancellable *cancellable) -{ - EMailRequest *request; - SoupURI *uri; - GtkIconTheme *icon_theme; - GtkIconInfo *icon_info; - const gchar *file; - gchar *a_size; - gssize size; - gchar *buffer; - gsize buff_len; - GtkStyleContext *context; - GtkWidgetPath *path; - GtkIconSet *set; - - request = E_MAIL_REQUEST (object); - uri = soup_request_get_uri (SOUP_REQUEST (object)); - - if (request->priv->uri_query) { - a_size = g_hash_table_lookup (request->priv->uri_query, "size"); - } else { - a_size = NULL; - } - - if (!a_size) { - size = GTK_ICON_SIZE_BUTTON; - } else { - size = atoi (a_size); - } - - /* Try style context first */ - context = gtk_style_context_new (); - path = gtk_widget_path_new (); - gtk_widget_path_append_type (path, GTK_TYPE_WINDOW); - gtk_widget_path_append_type (path, GTK_TYPE_BUTTON); - gtk_style_context_set_path (context, path); - - set = gtk_style_context_lookup_icon_set (context, uri->host); - if (!set) { - /* Fallback to icon theme */ - icon_theme = gtk_icon_theme_get_default (); - icon_info = gtk_icon_theme_lookup_icon ( - icon_theme, uri->host, size, - GTK_ICON_LOOKUP_USE_BUILTIN); - if (!icon_info) { - gtk_widget_path_free (path); - g_object_unref (context); - return; - } - - file = gtk_icon_info_get_filename (icon_info); - buffer = NULL; - if (file) { - if (g_file_get_contents (file, &buffer, &buff_len, NULL)) { - - request->priv->mime_type = - g_content_type_guess (file, NULL, 0, NULL); - request->priv->content_length = buff_len; - } - - } else { - GdkPixbuf *pixbuf; - - pixbuf = gtk_icon_info_get_builtin_pixbuf (icon_info); - if (pixbuf) { - gdk_pixbuf_save_to_buffer ( - pixbuf, &buffer, - &buff_len, "png", NULL, NULL); - - request->priv->mime_type = g_strdup("image/png"); - request->priv->content_length = buff_len; - - g_object_unref (pixbuf); - } - } - - gtk_icon_info_free (icon_info); - - } else { - GdkPixbuf *pixbuf; - - pixbuf = gtk_icon_set_render_icon_pixbuf (set, context, size); - gdk_pixbuf_save_to_buffer ( - pixbuf, &buffer, - &buff_len, "png", NULL, NULL); - - request->priv->mime_type = g_strdup("image/png"); - request->priv->content_length = buff_len; - - g_object_unref (pixbuf); - } - - if (buffer) { - GInputStream *stream; - stream = g_memory_input_stream_new_from_data ( - buffer, buff_len, (GDestroyNotify) g_free); - g_simple_async_result_set_op_res_gpointer (res, stream, NULL); - } - - gtk_widget_path_free (path); - g_object_unref (context); - -} - -static void mail_request_finalize (GObject *object) { EMailRequest *request = E_MAIL_REQUEST (object); @@ -626,11 +169,7 @@ mail_request_check_uri (SoupRequest *request, SoupURI *uri, GError **error) { - return ((strcmp (uri->scheme, "mail") == 0) || - (strcmp (uri->scheme, "evo-file") == 0) || - (strcmp (uri->scheme, "evo-http") == 0) || - (strcmp (uri->scheme, "evo-https") == 0) || - (strcmp (uri->scheme, "gtk-stock") == 0)); + return (strcmp (uri->scheme, "mail") == 0); } static void @@ -644,26 +183,13 @@ mail_request_send_async (SoupRequest *request, GSimpleAsyncResult *result; SoupURI *uri; GHashTable *formatters; + gchar *uri_str; session = soup_request_get_session (request); uri = soup_request_get_uri (request); d(printf("received request for %s\n", soup_uri_to_string (uri, FALSE))); - /* WebKit won't allow us to load data through local file:// protocol - * when using "remote" mail:// protocol, so we have evo-file:// - * which WebKit thinks it's remote, but in fact it behaves like - * oridnary file:// */ - if (g_strcmp0 (uri->scheme, "evo-file") == 0) { - - result = g_simple_async_result_new (G_OBJECT (request), callback, - user_data, mail_request_send_async); - g_simple_async_result_run_in_thread (result, handle_file_request, - G_PRIORITY_DEFAULT, cancellable); - - return; - } - if (uri->query) { emr->priv->uri_query = soup_form_decode (uri->query); } else { @@ -673,66 +199,20 @@ mail_request_send_async (SoupRequest *request, formatters = g_object_get_data (G_OBJECT (session), "formatters"); g_return_if_fail (formatters != NULL); - /* Get HTML content of given PURI part */ - if (g_strcmp0 (uri->scheme, "mail") == 0) { - gchar *uri_str; - - uri_str = g_strdup_printf ( - "%s://%s%s", uri->scheme, uri->host, uri->path); - emr->priv->efh = g_hash_table_lookup (formatters, uri_str); - g_free (uri_str); - - g_return_if_fail (emr->priv->efh); - - /* Make sure the formatter lives until we are finished here */ - g_object_ref (emr->priv->efh); - - result = g_simple_async_result_new (G_OBJECT (request), callback, - user_data, mail_request_send_async); - g_simple_async_result_run_in_thread (result, handle_mail_request, - G_PRIORITY_DEFAULT, cancellable); - - return; - - /* For http and https requests we have this evo-http(s) protocol. - * We first try to lookup the data in local cache and when not found, - * we send standard http(s) request to fetch them. But only when image - * loading policy allows us. */ - } else if ((g_strcmp0 (uri->scheme, "evo-http") == 0) || - (g_strcmp0 (uri->scheme, "evo-https") == 0)) { + uri_str = g_strdup_printf ( + "%s://%s%s", uri->scheme, uri->host, uri->path); + emr->priv->efh = g_hash_table_lookup (formatters, uri_str); + g_free (uri_str); - gchar *mail_uri; - const gchar *enc = g_hash_table_lookup (emr->priv->uri_query, - "__evo-mail"); + g_return_if_fail (emr->priv->efh); - g_return_if_fail (enc && *enc); + /* Make sure the formatter lives until we are finished here */ + g_object_ref (emr->priv->efh); - mail_uri = soup_uri_decode (enc); - - emr->priv->efh = g_hash_table_lookup (formatters, mail_uri); - g_free (mail_uri); - - g_return_if_fail (emr->priv->efh); - - /* Make sure the formatter lives until we are finished here */ - g_object_ref (emr->priv->efh); - - result = g_simple_async_result_new (G_OBJECT (request), callback, - user_data, mail_request_send_async); - g_simple_async_result_run_in_thread (result, handle_http_request, - G_PRIORITY_DEFAULT, cancellable); - - return; - - } else if ((g_strcmp0 (uri->scheme, "gtk-stock") == 0)) { - - result = g_simple_async_result_new (G_OBJECT (request), callback, - user_data, mail_request_send_async); - g_simple_async_result_run_in_thread (result, handle_stock_request, - G_PRIORITY_DEFAULT, cancellable); - - return; - } + result = g_simple_async_result_new (G_OBJECT (request), callback, + user_data, mail_request_send_async); + g_simple_async_result_run_in_thread (result, handle_mail_request, + G_PRIORITY_DEFAULT, cancellable); } static GInputStream * @@ -804,7 +284,7 @@ mail_request_get_content_type (SoupRequest *request) return emr->priv->ret_mime_type; } -static const char *data_schemes[] = { "mail", "evo-file", "evo-http", "evo-https", "gtk-stock", NULL }; +static const char *data_schemes[] = { "mail", NULL }; static void e_mail_request_class_init (EMailRequestClass *class) |