Files
evolution/mail/e-http-request.c
Tomas Popela 6f29002a24 EHTTPRequest - GFileInfo not valid when machine is under heavy load
When the machine is under heavy load and I'm moving between messages with
inline images (that are already downloaded and are in the cache) I get warnings
that GFileInfo object is not valid. I managed to get these warnings when the task
was already cancelled (I already moved to another message) but for the active task
as well. So try to access the GFileInfo object just when we know that it is valid.
2015-05-06 15:04:13 +02:00

619 lines
16 KiB
C

/*
* 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.
*
* 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 General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "e-http-request.h"
#include <config.h>
#include <string.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 <libemail-engine/libemail-engine.h>
#include <mail/em-utils.h>
#include <shell/e-shell.h>
#include "e-mail-ui-session.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;
};
G_DEFINE_TYPE (EHTTPRequest, e_http_request, SOUP_TYPE_REQUEST)
static gssize
copy_stream_to_stream (GIOStream *file_io_stream,
GMemoryInputStream *output,
GCancellable *cancellable)
{
GInputStream *input_stream;
gchar *buff;
gssize read_len = 0;
gssize total_len = 0;
const gsize buff_size = 4096;
g_seekable_seek (
G_SEEKABLE (file_io_stream), 0,
G_SEEK_SET, cancellable, NULL);
input_stream = g_io_stream_get_input_stream (file_io_stream);
buff = g_malloc (buff_size);
read_len = g_input_stream_read (
input_stream, buff, buff_size, cancellable, NULL);
while (read_len > 0) {
g_memory_input_stream_add_data (
output, buff, read_len, g_free);
total_len += read_len;
buff = g_malloc (buff_size);
read_len = g_input_stream_read (
input_stream, buff, buff_size, cancellable, NULL);
}
/* Free the last unused buffer */
g_free (buff);
return total_len;
}
static void
redirect_handler (SoupMessage *msg,
gpointer user_data)
{
if (SOUP_STATUS_IS_REDIRECTION (msg->status_code)) {
SoupSession *soup_session = user_data;
SoupURI *new_uri;
const gchar *new_loc;
new_loc = soup_message_headers_get_list (
msg->response_headers, "Location");
if (new_loc == NULL)
return;
new_uri = soup_uri_new_with_base (
soup_message_get_uri (msg), new_loc);
if (new_uri == NULL) {
soup_message_set_status_full (
msg,
SOUP_STATUS_MALFORMED,
"Invalid Redirect URL");
return;
}
soup_message_set_uri (msg, new_uri);
soup_session_requeue_message (soup_session, msg);
soup_uri_free (new_uri);
}
}
static void
send_and_handle_redirection (SoupSession *session,
SoupMessage *message,
gchar **new_location)
{
SoupURI *soup_uri;
gchar *old_uri = NULL;
g_return_if_fail (message != NULL);
soup_uri = soup_message_get_uri (message);
if (new_location != NULL)
old_uri = soup_uri_to_string (soup_uri, FALSE);
soup_message_set_flags (message, SOUP_MESSAGE_NO_REDIRECT);
soup_message_add_header_handler (
message, "got_body", "Location",
G_CALLBACK (redirect_handler), session);
soup_message_headers_append (
message->request_headers, "Connection", "close");
soup_session_send_message (session, message);
if (new_location != NULL) {
gchar *new_loc;
new_loc = soup_uri_to_string (soup_uri, FALSE);
if (new_loc && old_uri && !g_str_equal (new_loc, old_uri)) {
*new_location = new_loc;
} else {
g_free (new_loc);
}
}
g_free (old_uri);
}
static void
http_request_cancelled_cb (GCancellable *cancellable,
SoupSession *session)
{
soup_session_abort (session);
}
static void
handle_http_request (GSimpleAsyncResult *res,
GObject *source_object,
GCancellable *cancellable)
{
EHTTPRequestPrivate *priv;
SoupURI *soup_uri;
SoupRequest *soup_request;
SoupSession *soup_session;
gchar *evo_uri, *uri;
gchar *mail_uri = NULL;
GInputStream *stream;
gboolean force_load_images = FALSE;
EImageLoadingPolicy image_policy;
gchar *uri_md5;
EShell *shell;
GSettings *settings;
const gchar *user_cache_dir, *soup_query;
CamelDataCache *cache;
GIOStream *cache_stream;
gint uri_len;
if (g_cancellable_is_cancelled (cancellable))
return;
priv = E_HTTP_REQUEST_GET_PRIVATE (source_object);
soup_request = SOUP_REQUEST (source_object);
soup_session = soup_request_get_session (soup_request);
soup_uri = soup_request_get_uri (soup_request);
/* Remove the __evo-mail query */
soup_query = soup_uri_get_query (soup_uri);
if (soup_query) {
GHashTable *query;
query = soup_form_decode (soup_uri_get_query (soup_uri));
mail_uri = g_hash_table_lookup (query, "__evo-mail");
if (mail_uri)
mail_uri = g_strdup (mail_uri);
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);
if (camel_debug_start ("emformat:requests")) {
printf (
"%s: looking for '%s'\n",
G_STRFUNC, evo_uri ? evo_uri : "[null]");
camel_debug_end ();
}
/* Remove the "evo-" prefix from scheme */
uri_len = (evo_uri != NULL) ? strlen (evo_uri) : 0;
uri = NULL;
if (evo_uri != NULL && (uri_len > 5)) {
/* Remove trailing "?" if there is no URI query */
if (evo_uri[uri_len - 1] == '?') {
uri = g_strndup (evo_uri + 4, uri_len - 5);
} else {
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);
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 != NULL) {
gssize len;
stream = g_memory_input_stream_new ();
len = copy_stream_to_stream (
cache_stream,
G_MEMORY_INPUT_STREAM (stream), cancellable);
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) && (priv->content_length > 0)) {
GFile *file;
GFileInfo *info;
gchar *path;
path = camel_data_cache_get_filename (
cache, "http", uri_md5);
file = g_file_new_for_path (path);
info = g_file_query_info (
file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
0, cancellable, NULL);
if (info) {
priv->content_type = g_strdup (
g_file_info_get_content_type (info));
d (
printf ("'%s' found in cache (%d bytes, %s)\n",
uri, priv->content_length,
priv->content_type));
}
g_clear_object (&info);
g_clear_object (&file);
g_free (path);
/* Set result and quit the thread */
g_simple_async_result_set_op_res_gpointer (
res, stream, g_object_unref);
goto cleanup;
} else {
d (printf ("Failed to load '%s' from cache.\n", uri));
g_object_unref (stream);
}
}
/* If the item is not cached and Evolution is offline
* then quit regardless of any image loading policy. */
shell = e_shell_get_default ();
if (!e_shell_get_online (shell))
goto cleanup;
settings = e_util_ref_settings ("org.gnome.evolution.mail");
image_policy = g_settings_get_enum (settings, "image-loading-policy");
g_object_unref (settings);
/* Item not found in cache, but image loading policy allows us to fetch
* it from the interwebs */
if (!force_load_images && mail_uri != NULL &&
(image_policy == E_IMAGE_LOADING_POLICY_SOMETIMES)) {
CamelObjectBag *registry;
gchar *decoded_uri;
EMailPartList *part_list;
registry = e_mail_part_list_get_registry ();
decoded_uri = soup_uri_decode (mail_uri);
part_list = camel_object_bag_get (registry, decoded_uri);
if (part_list != NULL) {
EShellBackend *shell_backend;
EMailBackend *backend;
EMailSession *session;
CamelInternetAddress *addr;
CamelMimeMessage *message;
gboolean known_address = FALSE;
GError *error = NULL;
shell_backend =
e_shell_get_backend_by_name (shell, "mail");
backend = E_MAIL_BACKEND (shell_backend);
session = e_mail_backend_get_session (backend);
message = e_mail_part_list_get_message (part_list);
addr = camel_mime_message_get_from (message);
e_mail_ui_session_check_known_address_sync (
E_MAIL_UI_SESSION (session),
addr, FALSE, cancellable,
&known_address, &error);
if (error != NULL) {
g_warning ("%s: %s", G_STRFUNC, error->message);
g_error_free (error);
}
if (known_address)
force_load_images = TRUE;
g_object_unref (part_list);
}
g_free (decoded_uri);
}
if ((image_policy == E_IMAGE_LOADING_POLICY_ALWAYS) ||
force_load_images) {
SoupSession *temp_session;
SoupMessage *message;
GIOStream *cache_stream;
GError *error;
GMainContext *context;
gulong cancelled_id = 0;
if (g_cancellable_is_cancelled (cancellable))
goto cleanup;
message = soup_message_new (SOUP_METHOD_GET, uri);
if (!message) {
g_debug ("%s: Skipping invalid URI '%s'", G_STRFUNC, uri);
goto cleanup;
}
context = g_main_context_new ();
g_main_context_push_thread_default (context);
temp_session = soup_session_new_with_options (
SOUP_SESSION_TIMEOUT, 90, NULL);
e_binding_bind_property (
soup_session, "proxy-resolver",
temp_session, "proxy-resolver",
G_BINDING_SYNC_CREATE);
soup_message_headers_append (
message->request_headers,
"User-Agent", "Evolution/" VERSION);
if (cancellable)
cancelled_id = g_cancellable_connect (cancellable, G_CALLBACK (http_request_cancelled_cb), temp_session, NULL);
send_and_handle_redirection (temp_session, message, NULL);
if (cancellable && cancelled_id)
g_cancellable_disconnect (cancellable, cancelled_id);
if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
g_debug ("Failed to request %s (code %d)", uri, message->status_code);
g_object_unref (message);
g_object_unref (temp_session);
g_main_context_unref (context);
goto cleanup;
}
/* Write the response body to cache */
error = NULL;
cache_stream = camel_data_cache_add (
cache, "http", uri_md5, &error);
if (error != NULL) {
g_warning (
"Failed to create cache file for '%s': %s",
uri, error->message);
g_clear_error (&error);
} else {
GOutputStream *output_stream;
output_stream =
g_io_stream_get_output_stream (cache_stream);
g_output_stream_write_all (
output_stream,
message->response_body->data,
message->response_body->length,
NULL, cancellable, &error);
g_io_stream_close (cache_stream, NULL, NULL);
g_object_unref (cache_stream);
if (error != NULL) {
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning (
"Failed to write data to cache stream: %s",
error->message);
g_clear_error (&error);
g_object_unref (message);
g_object_unref (temp_session);
g_main_context_unref (context);
goto cleanup;
}
}
/* Send the response body to WebKit */
stream = g_memory_input_stream_new_from_data (
g_memdup (
message->response_body->data,
message->response_body->length),
message->response_body->length,
(GDestroyNotify) g_free);
priv->content_length = message->response_body->length;
priv->content_type = g_strdup (
soup_message_headers_get_content_type (
message->response_headers, NULL));
g_object_unref (message);
g_object_unref (temp_session);
g_main_context_unref (context);
d (printf ("Received image from %s\n"
"Content-Type: %s\n"
"Content-Length: %d bytes\n"
"URI MD5: %s:\n",
uri, priv->content_type,
priv->content_length, uri_md5));
g_simple_async_result_set_op_res_gpointer (res, stream, g_object_unref);
goto cleanup;
}
cleanup:
g_clear_object (&cache);
g_free (uri);
g_free (uri_md5);
g_free (mail_uri);
}
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;
}
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)
{
GSimpleAsyncResult *simple;
d ({
const gchar *soup_query;
SoupURI *uri;
uri = soup_request_get_uri (request);
soup_query = soup_uri_get_query (uri);
if (soup_query) {
gchar *uri_str;
GHashTable *query;
query = soup_form_decode (soup_uri_get_query (uri));
uri_str = soup_uri_to_string (uri, FALSE);
printf ("received request for %s\n", uri_str);
g_free (uri_str);
g_hash_table_destroy (query);
}
});
simple = g_simple_async_result_new (
G_OBJECT (request), callback,
user_data, http_request_send_async);
g_simple_async_result_set_check_cancellable (simple, cancellable);
e_util_run_simple_async_result_in_thread (
simple, handle_http_request,
cancellable);
g_object_unref (simple);
}
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));
/* 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 ();
else
g_object_ref (stream);
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);
}