evolution-3.6.4/mail/e-http-request.c

No issues found

  1 /*
  2  * e-http-request.c
  3  *
  4  * This program is free software; you can redistribute it and/or
  5  * modify it under the terms of the GNU Lesser General Public
  6  * License as published by the Free Software Foundation; either
  7  * version 2 of the License, or (at your option) version 3.
  8  *
  9  * This program is distributed in the hope that it will be useful,
 10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 12  * Lesser General Public License for more details.
 13  *
 14  * You should have received a copy of the GNU Lesser General Public
 15  * License along with the program; if not, see <http://www.gnu.org/licenses/>
 16  *
 17  */
 18 
 19 #ifdef HAVE_CONFIG_H
 20 #include <config.h>
 21 #endif
 22 
 23 #include "e-http-request.h"
 24 
 25 #define LIBSOUP_USE_UNSTABLE_REQUEST_API
 26 #include <libsoup/soup.h>
 27 #include <libsoup/soup-requester.h>
 28 #include <libsoup/soup-request-http.h>
 29 #include <camel/camel.h>
 30 #include <webkit/webkit.h>
 31 
 32 #include <e-util/e-util.h>
 33 #include <mail/em-utils.h>
 34 #include <libemail-engine/e-mail-enumtypes.h>
 35 
 36 #include <string.h>
 37 
 38 #include <shell/e-shell.h>
 39 
 40 #define d(x)
 41 
 42 #define E_HTTP_REQUEST_GET_PRIVATE(obj) \
 43 	(G_TYPE_INSTANCE_GET_PRIVATE \
 44 	((obj), E_TYPE_HTTP_REQUEST, EHTTPRequestPrivate))
 45 
 46 struct _EHTTPRequestPrivate {
 47 	gchar *content_type;
 48 	gint content_length;
 49 
 50 	EMailPartList *parts_list;
 51 };
 52 
 53 G_DEFINE_TYPE (EHTTPRequest, e_http_request, SOUP_TYPE_REQUEST)
 54 
 55 static gssize
 56 copy_stream_to_stream (CamelStream *input,
 57                        GMemoryInputStream *output,
 58                        GCancellable *cancellable)
 59 {
 60 	gchar *buff;
 61 	gssize read_len = 0;
 62 	gssize total_len = 0;
 63 
 64 	g_seekable_seek (G_SEEKABLE (input), 0, G_SEEK_SET, cancellable, NULL);
 65 
 66 	buff = g_malloc (4096);
 67 	while ((read_len = camel_stream_read (input, buff, 4096, cancellable, NULL)) > 0) {
 68 
 69 		g_memory_input_stream_add_data (output, buff, read_len, g_free);
 70 
 71 		total_len += read_len;
 72 
 73 		buff = g_malloc (4096);
 74 	}
 75 
 76 	/* Free the last unused buffer */
 77 	g_free (buff);
 78 
 79 	return total_len;
 80 }
 81 
 82 static void
 83 redirect_handler (SoupMessage *msg,
 84                   gpointer user_data)
 85 {
 86 	if (SOUP_STATUS_IS_REDIRECTION (msg->status_code)) {
 87 		SoupSession *soup_session = user_data;
 88 		SoupURI *new_uri;
 89 		const gchar *new_loc;
 90 
 91 		new_loc = soup_message_headers_get (msg->response_headers, "Location");
 92 		if (!new_loc)
 93 			return;
 94 
 95 		new_uri = soup_uri_new_with_base (soup_message_get_uri (msg), new_loc);
 96 		if (!new_uri) {
 97 			soup_message_set_status_full (
 98 				msg,
 99 				SOUP_STATUS_MALFORMED,
100 				"Invalid Redirect URL");
101 			return;
102 		}
103 
104 		soup_message_set_uri (msg, new_uri);
105 		soup_session_requeue_message (soup_session, msg);
106 
107 		soup_uri_free (new_uri);
108 	}
109 }
110 
111 static void
112 send_and_handle_redirection (SoupSession *session,
113                              SoupMessage *message,
114                              gchar **new_location)
115 {
116 	gchar *old_uri = NULL;
117 
118 	g_return_if_fail (message != NULL);
119 
120 	if (new_location) {
121 		old_uri = soup_uri_to_string (soup_message_get_uri (message), FALSE);
122 	}
123 
124 	soup_message_set_flags (message, SOUP_MESSAGE_NO_REDIRECT);
125 	soup_message_add_header_handler (
126 		message, "got_body", "Location",
127 		G_CALLBACK (redirect_handler), session);
128 	soup_message_headers_append (message->request_headers, "Connection", "close");
129 	soup_session_send_message (session, message);
130 
131 	if (new_location) {
132 		gchar *new_loc = soup_uri_to_string (soup_message_get_uri (message), FALSE);
133 
134 		if (new_loc && old_uri && !g_str_equal (new_loc, old_uri)) {
135 			*new_location = new_loc;
136 		} else {
137 			g_free (new_loc);
138 		}
139 	}
140 
141 	g_free (old_uri);
142 }
143 
144 static void
145 handle_http_request (GSimpleAsyncResult *res,
146                      GObject *object,
147                      GCancellable *cancellable)
148 {
149 	EHTTPRequest *request = E_HTTP_REQUEST (object);
150 	SoupURI *soup_uri;
151 	gchar *evo_uri, *uri;
152 	gchar *mail_uri;
153 	GInputStream *stream;
154 	gboolean force_load_images = FALSE;
155 	EMailImageLoadingPolicy image_policy;
156 	gchar *uri_md5;
157 	EShell *shell;
158 	EShellSettings *shell_settings;
159 	const gchar *user_cache_dir;
160 	CamelDataCache *cache;
161 	CamelStream *cache_stream;
162 	GHashTable *query;
163 	gint uri_len;
164 
165 	if (g_cancellable_is_cancelled (cancellable)) {
166 		return;
167 	}
168 
169 	/* Remove the __evo-mail query */
170 	soup_uri = soup_request_get_uri (SOUP_REQUEST (request));
171 	g_return_if_fail (soup_uri_get_query (soup_uri) != NULL);
172 
173 	query = soup_form_decode (soup_uri_get_query (soup_uri));
174 	mail_uri = g_hash_table_lookup (query, "__evo-mail");
175 	if (mail_uri)
176 		mail_uri = g_strdup (mail_uri);
177 
178 	g_hash_table_remove (query, "__evo-mail");
179 
180 	/* Remove __evo-load-images if present (and in such case set
181 	 * force_load_images to TRUE) */
182 	force_load_images = g_hash_table_remove (query, "__evo-load-images");
183 
184 	soup_uri_set_query_from_form (soup_uri, query);
185 	g_hash_table_unref (query);
186 
187 	evo_uri = soup_uri_to_string (soup_uri, FALSE);
188 
189 	/* Remove the "evo-" prefix from scheme */
190 	uri_len = strlen (evo_uri);
191 	uri = NULL;
192 	if (evo_uri && (uri_len > 5)) {
193 
194 		/* Remove trailing "?" if there is no URI query */
195 		if (evo_uri[uri_len - 1] == '?') {
196 			uri = g_strndup (evo_uri + 4, uri_len - 5);
197 		} else {
198 			uri = g_strdup (evo_uri + 4);
199 		}
200 		g_free (evo_uri);
201 	}
202 
203 	g_return_if_fail (uri && *uri);
204 
205 	/* Use MD5 hash of the URI as a filname of the resourec cache file.
206 	 * We were previously using the URI as a filename but the URI is
207 	 * sometimes too long for a filename. */
208 	uri_md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, uri, -1);
209 
210 	/* Open Evolution's cache */
211 	user_cache_dir = e_get_user_cache_dir ();
212 	cache = camel_data_cache_new (user_cache_dir, NULL);
213 	if (cache) {
214 		camel_data_cache_set_expire_age (cache, 24 * 60 * 60);
215 		camel_data_cache_set_expire_access (cache, 2 * 60 * 60);
216 	}
217 
218 	/* Found item in cache! */
219 	cache_stream = camel_data_cache_get (cache, "http", uri_md5, NULL);
220 	if (cache_stream) {
221 
222 		gssize len;
223 
224 		stream = g_memory_input_stream_new ();
225 
226 		len = copy_stream_to_stream (
227 			cache_stream,
228 			G_MEMORY_INPUT_STREAM (stream), cancellable);
229 		request->priv->content_length = len;
230 
231 		g_object_unref (cache_stream);
232 
233 		/* When succesfully read some data from cache then
234 		 * get mimetype and return the stream to WebKit.
235 		 * Otherwise try to fetch the resource again from the network. */
236 		if ((len != -1) && (request->priv->content_length > 0)) {
237 			GFile *file;
238 			GFileInfo *info;
239 			gchar *path;
240 
241 			path = camel_data_cache_get_filename (cache, "http", uri_md5);
242 			file = g_file_new_for_path (path);
243 			info = g_file_query_info (
244 				file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
245 				0, cancellable, NULL);
246 
247 			request->priv->content_type = g_strdup (
248 				g_file_info_get_content_type (info));
249 
250 			d (
251 				printf ("'%s' found in cache (%d bytes, %s)\n",
252 				uri, request->priv->content_length,
253 				request->priv->content_type));
254 
255 			g_object_unref (info);
256 			g_object_unref (file);
257 			g_free (path);
258 
259 			/* Set result and quit the thread */
260 			g_simple_async_result_set_op_res_gpointer (res, stream, NULL);
261 
262 			goto cleanup;
263 		} else {
264 			d (printf ("Failed to load '%s' from cache.\n", uri));
265 		}
266 	}
267 
268 	/* If the item is not in the cache and Evolution is in offline mode then
269 	 * quit regardless any image loading policy */
270 	shell = e_shell_get_default ();
271 	if (!e_shell_get_online (shell)) {
272 		goto cleanup;
273 	}
274 
275 	shell_settings = e_shell_get_shell_settings (shell);
276 	image_policy =  e_shell_settings_get_int (shell_settings, "mail-image-loading-policy");
277 
278 	/* Item not found in cache, but image loading policy allows us to fetch
279 	 * it from the interwebs */
280 	if (!force_load_images && mail_uri &&
281 	    (image_policy == E_MAIL_IMAGE_LOADING_POLICY_SOMETIMES)) {
282 		CamelObjectBag *registry;
283 		gchar *decoded_uri;
284 		EMailPartList *part_list;
285 
286 		registry = e_mail_part_list_get_registry ();
287 		decoded_uri = soup_uri_decode (mail_uri);
288 
289 		part_list = camel_object_bag_get (registry, decoded_uri);
290 		if (part_list) {
291 			EShell *shell;
292 			ESourceRegistry *registry;
293 			CamelInternetAddress *addr;
294 
295 			shell = e_shell_get_default ();
296 			registry = e_shell_get_registry (shell);
297 			addr = camel_mime_message_get_from (part_list->message);
298 			force_load_images = em_utils_in_addressbook (
299 					registry, addr, FALSE, cancellable);
300 
301 			g_object_unref (part_list);
302 		}
303 
304 		g_free (decoded_uri);
305 	}
306 
307 	if ((image_policy == E_MAIL_IMAGE_LOADING_POLICY_ALWAYS) ||
308 	    force_load_images) {
309 
310 		SoupSession *session;
311 		SoupMessage *message;
312 		CamelStream *cache_stream;
313 		GError *error;
314 		GMainContext *context;
315 
316 		context = g_main_context_new ();
317 		g_main_context_push_thread_default (context);
318 
319 		session = soup_session_sync_new_with_options (
320 				SOUP_SESSION_TIMEOUT, 90,
321 				NULL);
322 
323 		message = soup_message_new (SOUP_METHOD_GET, uri);
324 		soup_message_headers_append (
325 			message->request_headers, "User-Agent", "Evolution/" VERSION);
326 
327 		send_and_handle_redirection (session, message, NULL);
328 
329 		if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
330 			g_warning ("Failed to request %s (code %d)", uri, message->status_code);
331 			goto cleanup;
332 		}
333 
334 		/* Write the response body to cache */
335 		error = NULL;
336 		cache_stream = camel_data_cache_add (cache, "http", uri_md5, &error);
337 		if (error != NULL) {
338 			g_warning (
339 				"Failed to create cache file for '%s': %s",
340 				uri, error->message);
341 			g_clear_error (&error);
342 		} else {
343 			camel_stream_write (
344 				cache_stream, message->response_body->data,
345 				message->response_body->length, cancellable, &error);
346 			if (error != NULL) {
347 				g_warning (
348 					"Failed to write data to cache stream: %s",
349 					error->message);
350 				g_clear_error (&error);
351 				goto cleanup;
352 			}
353 
354 			camel_stream_close (cache_stream, cancellable, NULL);
355 			g_object_unref (cache_stream);
356 		}
357 
358 		/* Send the response body to WebKit */
359 		stream = g_memory_input_stream_new_from_data (
360 			g_memdup (message->response_body->data, message->response_body->length),
361 			message->response_body->length,
362 			(GDestroyNotify) g_free);
363 
364 		request->priv->content_length = message->response_body->length;
365 		request->priv->content_type =
366 			g_strdup (
367 				soup_message_headers_get_content_type (
368 					message->response_headers, NULL));
369 
370 		g_object_unref (message);
371 		g_object_unref (session);
372 		g_main_context_unref (context);
373 
374 		d (printf ("Received image from %s\n"
375 			"Content-Type: %s\n"
376 			"Content-Length: %d bytes\n"
377 			"URI MD5: %s:\n",
378 			uri, request->priv->content_type,
379 			request->priv->content_length, uri_md5));
380 
381 		g_simple_async_result_set_op_res_gpointer (res, stream, NULL);
382 		goto cleanup;
383 	}
384 
385  cleanup:
386 	if (cache) {
387 		g_object_unref (cache);
388 	}
389 	g_free (uri);
390 	g_free (uri_md5);
391 	g_free (mail_uri);
392 }
393 
394 static void
395 http_request_finalize (GObject *object)
396 {
397 	EHTTPRequest *request = E_HTTP_REQUEST (object);
398 
399 	if (request->priv->content_type) {
400 		g_free (request->priv->content_type);
401 		request->priv->content_type = NULL;
402 	}
403 
404 	if (request->priv->parts_list) {
405 		g_object_unref (request->priv->parts_list);
406 		request->priv->parts_list = NULL;
407 	}
408 
409 	G_OBJECT_CLASS (e_http_request_parent_class)->finalize (object);
410 }
411 
412 static gboolean
413 http_request_check_uri (SoupRequest *request,
414                         SoupURI *uri,
415                         GError **error)
416 {
417 	return ((strcmp (uri->scheme, "evo-http") == 0) ||
418 		(strcmp (uri->scheme, "evo-https") == 0));
419 }
420 
421 static void
422 http_request_send_async (SoupRequest *request,
423                          GCancellable *cancellable,
424                          GAsyncReadyCallback callback,
425                          gpointer user_data)
426 {
427 	EHTTPRequest *ehr;
428 	GSimpleAsyncResult *simple;
429 	gchar *mail_uri;
430 	SoupURI *uri;
431 	const gchar *enc;
432 	CamelObjectBag *registry;
433 	GHashTable *query;
434 
435 	ehr = E_HTTP_REQUEST (request);
436 	uri = soup_request_get_uri (request);
437 	g_return_if_fail (soup_uri_get_query (uri) != NULL);
438 
439 	query = soup_form_decode (soup_uri_get_query (uri));
440 
441 	d ({
442 		gchar *uri_str = soup_uri_to_string (uri, FALSE);
443 		printf ("received request for %s\n", uri_str);
444 		g_free (uri_str);
445 	});
446 
447 	enc = g_hash_table_lookup (query, "__evo-mail");
448 
449 	if (!enc || !*enc) {
450 		g_hash_table_destroy (query);
451 		return;
452 	}
453 
454 	mail_uri = soup_uri_decode (enc);
455 
456 	registry = e_mail_part_list_get_registry ();
457 	ehr->priv->parts_list = camel_object_bag_get (registry, mail_uri);
458 	g_free (mail_uri);
459 
460 	g_return_if_fail (ehr->priv->parts_list);
461 
462 	simple = g_simple_async_result_new (
463 		G_OBJECT (request), callback,
464 		user_data, http_request_send_async);
465 
466 	g_simple_async_result_set_check_cancellable (simple, cancellable);
467 
468 	g_simple_async_result_run_in_thread (
469 		simple, handle_http_request,
470 		G_PRIORITY_DEFAULT, cancellable);
471 
472 	g_object_unref (simple);
473 
474 	g_hash_table_destroy (query);
475 }
476 
477 static GInputStream *
478 http_request_send_finish (SoupRequest *request,
479                           GAsyncResult *result,
480                           GError **error)
481 {
482 	GInputStream *stream;
483 
484 	stream = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
485 
486 	/* Reset the stream before passing it back to webkit */
487 	if (stream && G_IS_SEEKABLE (stream))
488 		g_seekable_seek (G_SEEKABLE (stream), 0, G_SEEK_SET, NULL, NULL);
489 
490 	if (!stream) /* We must always return something */
491 		stream = g_memory_input_stream_new ();
492 
493 	return stream;
494 }
495 
496 static goffset
497 http_request_get_content_length (SoupRequest *request)
498 {
499 	EHTTPRequest *efr = E_HTTP_REQUEST (request);
500 
501 	d (printf ("Content-Length: %d bytes\n", efr->priv->content_length));
502 	return efr->priv->content_length;
503 }
504 
505 static const gchar *
506 http_request_get_content_type (SoupRequest *request)
507 {
508 	EHTTPRequest *efr = E_HTTP_REQUEST (request);
509 
510 	d (printf ("Content-Type: %s\n", efr->priv->content_type));
511 
512 	return efr->priv->content_type;
513 }
514 
515 static const gchar *data_schemes[] = { "evo-http", "evo-https", NULL };
516 
517 static void
518 e_http_request_class_init (EHTTPRequestClass *class)
519 {
520 	GObjectClass *object_class;
521 	SoupRequestClass *request_class;
522 
523 	g_type_class_add_private (class, sizeof (EHTTPRequestPrivate));
524 
525 	object_class = G_OBJECT_CLASS (class);
526 	object_class->finalize = http_request_finalize;
527 
528 	request_class = SOUP_REQUEST_CLASS (class);
529 	request_class->schemes = data_schemes;
530 	request_class->send_async = http_request_send_async;
531 	request_class->send_finish = http_request_send_finish;
532 	request_class->get_content_type = http_request_get_content_type;
533 	request_class->get_content_length = http_request_get_content_length;
534 	request_class->check_uri = http_request_check_uri;
535 }
536 
537 static void
538 e_http_request_init (EHTTPRequest *request)
539 {
540 	request->priv = E_HTTP_REQUEST_GET_PRIVATE (request);
541 }