1 /*
2  * Copyright 2008 Rob Kendrick <rjek@netsurf-browser.org>
3  * Copyright 2013 John-Mark Bell <jmb@netsurf-browser.org>
4  *
5  * This file is part of NetSurf.
6  *
7  * NetSurf is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; version 2 of the License.
10  *
11  * NetSurf is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 /**
21  * \file
22  * HTML fetcher for CSS objects
23  */
24 
25 #include <assert.h>
26 #include <stdbool.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <dom/dom.h>
30 #include <libwapcaplet/libwapcaplet.h>
31 
32 #include "netsurf/inttypes.h"
33 #include "utils/config.h"
34 #include "utils/corestrings.h"
35 #include "utils/log.h"
36 #include "utils/ring.h"
37 #include "utils/nsurl.h"
38 #include "utils/utils.h"
39 #include "content/fetch.h"
40 #include "content/fetchers.h"
41 
42 #include "html/private.h"
43 
44 typedef struct html_css_fetcher_item {
45 	uint32_t key;
46 	dom_string *data;
47 	nsurl *base_url;
48 
49 	struct html_css_fetcher_item *r_next, *r_prev;
50 } html_css_fetcher_item;
51 
52 typedef struct html_css_fetcher_context {
53 	struct fetch *parent_fetch;
54 
55 	nsurl *url;
56 	html_css_fetcher_item *item;
57 
58 	bool aborted;
59 	bool locked;
60 
61 	struct html_css_fetcher_context *r_next, *r_prev;
62 } html_css_fetcher_context;
63 
64 static uint32_t current_key = 0;
65 static html_css_fetcher_item *items = NULL;
66 static html_css_fetcher_context *ring = NULL;
67 
html_css_fetcher_initialise(lwc_string * scheme)68 static bool html_css_fetcher_initialise(lwc_string *scheme)
69 {
70 	NSLOG(netsurf, INFO, "html_css_fetcher_initialise called for %s",
71 	      lwc_string_data(scheme));
72 	return true;
73 }
74 
html_css_fetcher_finalise(lwc_string * scheme)75 static void html_css_fetcher_finalise(lwc_string *scheme)
76 {
77 	NSLOG(netsurf, INFO, "html_css_fetcher_finalise called for %s",
78 	      lwc_string_data(scheme));
79 }
80 
html_css_fetcher_can_fetch(const nsurl * url)81 static bool html_css_fetcher_can_fetch(const nsurl *url)
82 {
83 	return true;
84 }
85 
html_css_fetcher_setup(struct fetch * parent_fetch,nsurl * url,bool only_2xx,bool downgrade_tls,const char * post_urlenc,const struct fetch_multipart_data * post_multipart,const char ** headers)86 static void *html_css_fetcher_setup(struct fetch *parent_fetch, nsurl *url,
87 		 bool only_2xx, bool downgrade_tls, const char *post_urlenc,
88 		 const struct fetch_multipart_data *post_multipart,
89 		 const char **headers)
90 {
91 	html_css_fetcher_context *ctx;
92 	lwc_string *path;
93 	uint32_t key;
94 	html_css_fetcher_item *item, *found = NULL;
95 
96 	/* format of a x-ns-css URL is:
97 	 *   x-ns-url:<key>
98 	 * Where key is an unsigned 32bit integer
99 	 */
100 
101 	path = nsurl_get_component(url, NSURL_PATH);
102 	/* The path must exist */
103 	if (path == NULL) {
104 		return NULL;
105 	}
106 
107 	key = strtoul(lwc_string_data(path), NULL, 10);
108 
109 	lwc_string_unref(path);
110 
111 	/* There must be at least one item */
112 	if (items == NULL) {
113 		return NULL;
114 	}
115 
116 	item = items;
117 	do {
118 		if (item->key == key) {
119 			found = item;
120 			break;
121 		}
122 
123 		item = item->r_next;
124 	} while (item != items);
125 
126 	/* We must have found the item */
127 	if (found == NULL) {
128 		return NULL;
129 	}
130 
131 	ctx = calloc(1, sizeof(*ctx));
132 	if (ctx == NULL)
133 		return NULL;
134 
135 	ctx->parent_fetch = parent_fetch;
136 	ctx->url = nsurl_ref(url);
137 	ctx->item = found;
138 
139 	RING_INSERT(ring, ctx);
140 
141 	return ctx;
142 }
143 
html_css_fetcher_start(void * ctx)144 static bool html_css_fetcher_start(void *ctx)
145 {
146 	return true;
147 }
148 
html_css_fetcher_free(void * ctx)149 static void html_css_fetcher_free(void *ctx)
150 {
151 	html_css_fetcher_context *c = ctx;
152 
153 	nsurl_unref(c->url);
154 	if (c->item != NULL) {
155 		nsurl_unref(c->item->base_url);
156 		dom_string_unref(c->item->data);
157 		RING_REMOVE(items, c->item);
158 		free(c->item);
159 	}
160 	RING_REMOVE(ring, c);
161 	free(ctx);
162 }
163 
html_css_fetcher_abort(void * ctx)164 static void html_css_fetcher_abort(void *ctx)
165 {
166 	html_css_fetcher_context *c = ctx;
167 
168 	/* To avoid the poll loop having to deal with the fetch context
169 	 * disappearing from under it, we simply flag the abort here.
170 	 * The poll loop itself will perform the appropriate cleanup.
171 	 */
172 	c->aborted = true;
173 }
174 
html_css_fetcher_send_callback(const fetch_msg * msg,html_css_fetcher_context * c)175 static void html_css_fetcher_send_callback(const fetch_msg *msg,
176 		html_css_fetcher_context *c)
177 {
178 	c->locked = true;
179 	fetch_send_callback(msg, c->parent_fetch);
180 	c->locked = false;
181 }
182 
html_css_fetcher_poll(lwc_string * scheme)183 static void html_css_fetcher_poll(lwc_string *scheme)
184 {
185 	fetch_msg msg;
186 	html_css_fetcher_context *c, *next;
187 
188 	if (ring == NULL) return;
189 
190 	/* Iterate over ring, processing each pending fetch */
191 	c = ring;
192 	do {
193 		/* Ignore fetches that have been flagged as locked.
194 		 * This allows safe re-entrant calls to this function.
195 		 * Re-entrancy can occur if, as a result of a callback,
196 		 * the interested party causes fetch_poll() to be called
197 		 * again.
198 		 */
199 		if (c->locked == true) {
200 			next = c->r_next;
201 			continue;
202 		}
203 
204 		/* Only process non-aborted fetches */
205 		if (c->aborted) {
206 			/* Nothing to do */
207 			assert(c->locked == false);
208 		} else if (c->item != NULL) {
209 			char header[4096];
210 
211 			fetch_set_http_code(c->parent_fetch, 200);
212 
213 			/* Any callback can result in the fetch being aborted.
214 			 * Therefore, we _must_ check for this after _every_
215 			 * call to html_css_fetcher_send_callback().
216 			 */
217 			snprintf(header, sizeof header,
218 				"Content-Type: text/css; charset=utf-8");
219 			msg.type = FETCH_HEADER;
220 			msg.data.header_or_data.buf = (const uint8_t *) header;
221 			msg.data.header_or_data.len = strlen(header);
222 			html_css_fetcher_send_callback(&msg, c);
223 
224 			if (c->aborted == false) {
225 				snprintf(header, sizeof header,
226 					"Content-Length: %"PRIsizet,
227 					dom_string_byte_length(c->item->data));
228 				msg.type = FETCH_HEADER;
229 				msg.data.header_or_data.buf =
230 						(const uint8_t *) header;
231 				msg.data.header_or_data.len = strlen(header);
232 				html_css_fetcher_send_callback(&msg, c);
233 			}
234 
235 			if (c->aborted == false) {
236 				snprintf(header, sizeof header,
237 					"X-NS-Base: %.*s",
238 					(int) nsurl_length(c->item->base_url),
239 					nsurl_access(c->item->base_url));
240 				msg.type = FETCH_HEADER;
241 				msg.data.header_or_data.buf =
242 						(const uint8_t *) header;
243 				msg.data.header_or_data.len = strlen(header);
244 				html_css_fetcher_send_callback(&msg, c);
245 			}
246 
247 			if (c->aborted == false) {
248 				msg.type = FETCH_DATA;
249 				msg.data.header_or_data.buf =
250 						(const uint8_t *)
251 						dom_string_data(c->item->data);
252 				msg.data.header_or_data.len =
253 					dom_string_byte_length(c->item->data);
254 				html_css_fetcher_send_callback(&msg, c);
255 			}
256 
257 			if (c->aborted == false) {
258 				msg.type = FETCH_FINISHED;
259 				html_css_fetcher_send_callback(&msg, c);
260 			}
261 		} else {
262 			NSLOG(netsurf, INFO, "Processing of %s failed!",
263 			      nsurl_access(c->url));
264 
265 			/* Ensure that we're unlocked here. If we aren't,
266 			 * then html_css_fetcher_process() is broken.
267 			 */
268 			assert(c->locked == false);
269 		}
270 
271 		/* Compute next fetch item at the last possible moment as
272 		 * processing this item may have added to the ring.
273 		 */
274 		next = c->r_next;
275 
276 		fetch_remove_from_queues(c->parent_fetch);
277 		fetch_free(c->parent_fetch);
278 
279 		/* Advance to next ring entry, exiting if we've reached
280 		 * the start of the ring or the ring has become empty
281 		 */
282 	} while ( (c = next) != ring && ring != NULL);
283 }
284 
285 /* exported interface documented in html_internal.h */
html_css_fetcher_register(void)286 nserror html_css_fetcher_register(void)
287 {
288 	const struct fetcher_operation_table html_css_fetcher_ops = {
289 		.initialise = html_css_fetcher_initialise,
290 		.acceptable = html_css_fetcher_can_fetch,
291 		.setup = html_css_fetcher_setup,
292 		.start = html_css_fetcher_start,
293 		.abort = html_css_fetcher_abort,
294 		.free = html_css_fetcher_free,
295 		.poll = html_css_fetcher_poll,
296 		.finalise = html_css_fetcher_finalise
297 	};
298 
299 	return fetcher_add(lwc_string_ref(corestring_lwc_x_ns_css),
300 			&html_css_fetcher_ops);
301 }
302 
303 /* exported interface documented in html_internal.h */
304 nserror
html_css_fetcher_add_item(dom_string * data,nsurl * base_url,uint32_t * key)305 html_css_fetcher_add_item(dom_string *data, nsurl *base_url, uint32_t *key)
306 {
307 	html_css_fetcher_item *item = malloc(sizeof(*item));
308 
309 	if (item == NULL) {
310 		return NSERROR_NOMEM;
311 	}
312 
313 	*key = item->key = current_key++;
314 	item->data = dom_string_ref(data);
315 	item->base_url = nsurl_ref(base_url);
316 
317 	RING_INSERT(items, item);
318 
319 	return NSERROR_OK;
320 }
321