1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2016 Ricardo Mones and the Claws Mail Team
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #ifdef HAVE_CONFIG_H
20 #  include "config.h"
21 #include "claws-features.h"
22 #endif
23 
24 #include <glib.h>
25 #include <curl/curl.h>
26 #include <pthread.h>
27 
28 #include <common/claws.h>
29 #include <prefs_common.h>
30 #include <file-utils.h>
31 
32 #include "libravatar.h"
33 #include "libravatar_prefs.h"
34 #include "libravatar_missing.h"
35 #include "libravatar_image.h"
36 
write_image_data_cb(void * ptr,size_t size,size_t nmemb,void * stream)37 static size_t write_image_data_cb(void *ptr, size_t size, size_t nmemb, void *stream)
38 {
39 	size_t written = claws_fwrite(ptr, size, nmemb, (FILE *)stream);
40 	debug_print("received %"G_GSIZE_FORMAT" bytes from avatar server\n", written);
41 
42 	return written;
43 }
44 
image_pixbuf_from_filename(const gchar * filename)45 static GdkPixbuf *image_pixbuf_from_filename(const gchar *filename)
46 {
47 	GdkPixbuf *picture = NULL;
48 	GError *error = NULL;
49 	gint w, h;
50 
51 	gdk_pixbuf_get_file_info(filename, &w, &h);
52 
53 	if (w != AVATAR_SIZE || h != AVATAR_SIZE)
54 		/* server can provide a different size from the requested in URL */
55 		picture = gdk_pixbuf_new_from_file_at_scale(
56 				filename, AVATAR_SIZE, AVATAR_SIZE, TRUE, &error);
57 	else	/* exact size */
58 		picture = gdk_pixbuf_new_from_file(filename, &error);
59 
60 	if (error != NULL) {
61 		g_warning("failed to load image '%s': %s", filename, error->message);
62 		g_error_free(error);
63 	} else {
64 		if (!picture)
65 			g_warning("failed to load image '%s': no error returned!", filename);
66 	}
67 
68 	return picture;
69 }
70 
pixbuf_from_url(const gchar * url,const gchar * md5,const gchar * filename)71 static GdkPixbuf *pixbuf_from_url(const gchar *url, const gchar *md5, const gchar *filename) {
72 	GdkPixbuf *image = NULL;
73 	FILE *file;
74 	CURL *curl;
75 	CURLcode res;
76 	long filesize;
77 
78 	file = claws_fopen(filename, "wb");
79 	if (file == NULL) {
80 		g_warning("could not open '%s' for writing", filename);
81 		return NULL;
82 	}
83 	curl = curl_easy_init();
84 	if (curl == NULL) {
85 		g_warning("could not initialize curl to get image from URL");
86 		unlink(filename);
87 		claws_fclose(file);
88 		return NULL;
89 	}
90 
91 	curl_easy_setopt(curl, CURLOPT_URL, url);
92 	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_image_data_cb);
93 	/* make sure timeout is less than general IO timeout */
94 	curl_easy_setopt(curl, CURLOPT_TIMEOUT,
95 			(libravatarprefs.timeout == 0
96 				|| libravatarprefs.timeout
97 					> prefs_common_get_prefs()->io_timeout_secs)
98 			? prefs_common_get_prefs()->io_timeout_secs
99 			: libravatarprefs.timeout);
100 	curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
101 
102 	if (libravatarprefs.allow_redirects) {
103 		long maxredirs = (libravatarprefs.default_mode == DEF_MODE_URL)
104 			? libravatarprefs.max_redirects_url
105 			: ((libravatarprefs.default_mode == DEF_MODE_MM)
106 				? libravatarprefs.max_redirects_mm
107 				: libravatarprefs.max_redirects_url);
108 		debug_print("setting max redirects to %ld\n", maxredirs);
109 		curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
110 		curl_easy_setopt(curl, CURLOPT_MAXREDIRS, maxredirs);
111 	}
112 	curl_easy_setopt(curl, CURLOPT_FILE, file);
113 	curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); /* fail on HTTP error */
114 	debug_print("retrieving URL to file: %s -> %s\n", url, filename);
115 	res = curl_easy_perform(curl);
116 	if (res != CURLE_OK) {
117 		debug_print("curl_easy_perfom failed: %s\n", curl_easy_strerror(res));
118 		unlink(filename);
119 		claws_fclose(file);
120 		missing_add_md5(libravatarmisses, md5);
121 	} else {
122 		filesize = ftell(file);
123 		claws_safe_fclose(file);
124 		if (filesize < MIN_PNG_SIZE) {
125 			debug_print("not enough data for an avatar image: %ld bytes\n", filesize);
126 			missing_add_md5(libravatarmisses, md5);
127 		} else {
128 			image = image_pixbuf_from_filename(filename);
129 		}
130 
131 		if (!libravatarprefs.cache_icons || filesize < MIN_PNG_SIZE) {
132 			if (g_unlink(filename) < 0)
133 				g_warning("failed to delete cache file '%s'", filename);
134 		}
135 	}
136 
137 	curl_easy_cleanup(curl);
138 
139 	return image;
140 }
141 
get_image_thread(void * arg)142 static void *get_image_thread(void *arg) {
143 	AvatarImageFetch *ctx = (AvatarImageFetch *)arg;
144 
145 	/* get image */
146 	ctx->pixbuf = pixbuf_from_url(ctx->url, ctx->md5, ctx->filename);
147 	/* done here */
148 	ctx->ready = TRUE;
149 
150 	return arg;
151 }
152 
libravatar_image_fetch(AvatarImageFetch * ctx)153 GdkPixbuf *libravatar_image_fetch(AvatarImageFetch *ctx)
154 {
155 #ifdef USE_PTHREAD
156 	pthread_t pt;
157 #endif
158 
159 	g_return_val_if_fail(ctx != NULL, NULL);
160 
161 #ifdef USE_PTHREAD
162 	if (pthread_create(&pt, NULL, get_image_thread, (void *)ctx) != 0) {
163 		debug_print("synchronous image fetching (couldn't create thread)\n");
164 		get_image_thread(ctx);
165 	} else {
166 		debug_print("waiting for thread completion\n");
167 		/*
168 		while (!ctx->ready ) {
169 			claws_do_idle();
170 		}
171 		*/
172 		pthread_join(pt, NULL);
173 		debug_print("thread completed\n");
174 	}
175 #else
176 	debug_print("synchronous image fetching (pthreads unavailable)\n");
177 	get_image_thread(ctx);
178 #endif
179 	if (ctx->pixbuf == NULL) {
180 		g_warning("could not get image");
181 	}
182 	return ctx->pixbuf;
183 }
184