1 /*
2  * Claws Mail -- A GTK+ based, lightweight, and fast e-mail client
3  * Copyright(C) 2019 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  * 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
12  * GNU General Public License for more details.
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write tothe Free Software
15  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16  */
17 
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #include "claws-features.h"
21 #endif
22 
23 #include "common/utils.h"
24 
25 #include "container_linux.h"
26 #include "http.h"
27 #include "lh_prefs.h"
28 
lh_get_image(const litehtml::tchar_t * url)29 static GdkPixbuf *lh_get_image(const litehtml::tchar_t* url)
30 {
31 	GError *error = NULL;
32 	GdkPixbuf *pixbuf = NULL;
33 	http* http_loader = NULL;
34 
35 	http_loader = new http();
36 	GInputStream *image = http_loader->load_url(url, &error);
37 
38 	if (error || !image) {
39 		if (error) {
40 			g_warning("lh_get_image: Could not create pixbuf %s",
41 					error->message);
42 			g_clear_error(&error);
43 		}
44 		goto theend;
45 	}
46 
47 	pixbuf = gdk_pixbuf_new_from_stream(image, NULL, &error);
48 	if (error) {
49 		g_warning("lh_get_image: Could not create pixbuf %s",
50 				error->message);
51 		pixbuf = NULL;
52 		g_clear_error(&error);
53 	}
54 
55 theend:
56 	if (http_loader) {
57 		delete http_loader;
58 	}
59 
60 	return pixbuf;
61 }
62 
63 struct FetchCtx {
64 	container_linux *container;
65 	gchar *url;
66 };
67 
get_image_threaded(GTask * task,gpointer source,gpointer task_data,GCancellable * cancellable)68 static void get_image_threaded(GTask *task, gpointer source, gpointer task_data, GCancellable *cancellable)
69 {
70 	struct FetchCtx *ctx = (struct FetchCtx *)task_data;
71 	GdkPixbuf *pixbuf = lh_get_image(ctx->url);
72 
73 	g_task_return_pointer(task, pixbuf, NULL);
74 }
75 
get_image_callback(GObject * source,GAsyncResult * res,gpointer user_data)76 static void get_image_callback(GObject *source, GAsyncResult *res, gpointer user_data)
77 {
78 	GdkPixbuf *pixbuf;
79 	struct FetchCtx *ctx = (struct FetchCtx *)user_data;
80 
81 	pixbuf = GDK_PIXBUF(g_task_propagate_pointer(G_TASK(res), NULL));
82 
83 	if (pixbuf != NULL) {
84 		ctx->container->add_image_to_cache(ctx->url, pixbuf);
85 		ctx->container->redraw(true);
86 	}
87 
88 	g_free(ctx->url);
89 	g_free(ctx);
90 }
91 
load_image(const litehtml::tchar_t * src,const litehtml::tchar_t * baseurl,bool redraw_on_ready)92 void container_linux::load_image( const litehtml::tchar_t* src, const litehtml::tchar_t* baseurl, bool redraw_on_ready )
93 {
94 	litehtml::tstring url;
95 	make_url(src, baseurl, url);
96 	bool found = false;
97 
98 	lock_images_cache();
99 
100 	for (auto ii = m_images.cbegin(); ii != m_images.cend(); ++ii) {
101 		const image *i = &(*ii);
102 
103 		if (!strcmp(i->first.c_str(), url.c_str())) {
104 			found = true;
105 			break;
106 		}
107 	}
108 
109 	unlock_images_cache();
110 
111 	if (!found) {
112 		struct FetchCtx *ctx;
113 
114 		/* Attached images can be loaded into cache right here. */
115 		if (!strncmp(src, "cid:", 4)) {
116 			GdkPixbuf *pixbuf = get_local_image(src);
117 
118 			if (pixbuf != NULL)
119 				add_image_to_cache(src, pixbuf);
120 
121 			return;
122 		}
123 
124 		if (!lh_prefs_get()->enable_remote_content) {
125 			debug_print("blocking download of image from '%s'\n", src);
126 			return;
127 		}
128 
129 		debug_print("allowing download of image from '%s'\n", src);
130 
131 		ctx = g_new(struct FetchCtx, 1);
132 		ctx->url = g_strdup(url.c_str());
133 		ctx->container = this;
134 
135 		GTask *task = g_task_new(this, NULL, get_image_callback, ctx);
136 		g_task_set_task_data(task, ctx, NULL);
137 		g_task_run_in_thread(task, get_image_threaded);
138 	} else {
139 		debug_print("found image in cache: '%s'\n", url.c_str());
140 	}
141 }
142 
get_image_size(const litehtml::tchar_t * src,const litehtml::tchar_t * baseurl,litehtml::size & sz)143 void container_linux::get_image_size( const litehtml::tchar_t* src, const litehtml::tchar_t* baseurl, litehtml::size& sz )
144 {
145 	litehtml::tstring url;
146 	make_url(src, baseurl, url);
147 	bool found = false;
148 	const image *img = NULL;
149 
150 	lock_images_cache();
151 
152 	for (auto ii = m_images.cbegin(); ii != m_images.cend(); ++ii) {
153 		const image *i = &(*ii);
154 		if (i->first == url) {
155 			img = i;
156 			found = true;
157 			break;
158 		}
159 	}
160 
161 	if(img != NULL)
162 	{
163 		sz.width	= gdk_pixbuf_get_width(img->second);
164 		sz.height	= gdk_pixbuf_get_height(img->second);
165 	} else
166 	{
167 		sz.width	= 0;
168 		sz.height	= 0;
169 	}
170 
171 	unlock_images_cache();
172 }
173 
add_image_to_cache(const gchar * url,GdkPixbuf * image)174 void container_linux::add_image_to_cache(const gchar *url, GdkPixbuf *image)
175 {
176 	g_return_if_fail(url != NULL);
177 	g_return_if_fail(image != NULL);
178 
179 	debug_print("adding image to cache: '%s'\n", url);
180 	lock_images_cache();
181 	m_images.push_back(std::make_pair(url, image));
182 	unlock_images_cache();
183 }
lock_images_cache(void)184 void container_linux::lock_images_cache(void)
185 {
186 	g_rec_mutex_lock(&m_images_lock);
187 }
188 
unlock_images_cache(void)189 void container_linux::unlock_images_cache(void)
190 {
191 	g_rec_mutex_unlock(&m_images_lock);
192 }
193 
clear_images()194 void container_linux::clear_images()
195 {
196 	lock_images_cache();
197 
198 	for(auto i = m_images.begin(); i != m_images.end(); ++i) {
199 		image *img = &(*i);
200 
201 		if (img->second) {
202 			g_object_unref(img->second);
203 		}
204 	}
205 
206 	m_images.clear();
207 
208 	unlock_images_cache();
209 }
210 
clear_images(gint desired_size)211 gint container_linux::clear_images(gint desired_size)
212 {
213 	gint size = 0;
214 	gint num = 0;
215 
216 	lock_images_cache();
217 
218 	/* First, remove all local images - the ones with "cid:"
219 	 * URL. We will remove their list elements later. */
220 	for (auto i = m_images.rbegin(); i != m_images.rend(); ++i) {
221 		image *img = &(*i);
222 
223 		if (!strncmp(img->first.c_str(), "cid:", 4)) {
224 			g_object_unref(img->second);
225 			img->second = NULL;
226 			num++;
227 		}
228 	}
229 
230 	/* Now tally up size of all the stored GdkPixbufs and
231 	 * deallocate those which make the total size be above
232 	 * the desired_size limit. We will remove their list
233 	 * elements later. */
234 	for (auto i = m_images.rbegin(); i != m_images.rend(); ++i) {
235 		image *img = &(*i);
236 		gint cursize;
237 
238 		if (img->second == NULL)
239 			continue;
240 
241 		cursize = gdk_pixbuf_get_byte_length(img->second);
242 
243 		if (size + cursize > desired_size) {
244 			g_object_unref(img->second);
245 			img->second = NULL;
246 			num++;
247 		} else {
248 			size += cursize;
249 		}
250 	}
251 
252 	/* Remove elements whose GdkPixbuf pointers point to NULL. */
253 	m_images.remove_if([&](image _img) -> bool {
254 			if (_img.second == NULL)
255 				return true;
256 			return false;
257 			});
258 
259 	unlock_images_cache();
260 
261 	return num;
262 }
263