Subversion Repositories Kolibri OS

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
3584 sourcerer 1
/*
2
 * Copyright 2008 Rob Kendrick 
3
 *
4
 * This file is part of NetSurf.
5
 *
6
 * NetSurf is free software; you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; version 2 of the License.
9
 *
10
 * NetSurf 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 .
17
 */
18
 
19
/* data: URL handling.  See http://tools.ietf.org/html/rfc2397 */
20
 
21
#include 
22
#include 
23
#include 
24
#include 
25
#include 
26
#include 
27
 
28
#include 		/* for URL unescaping functions */
29
 
30
#include 
31
 
32
#include "utils/config.h"
33
#include "content/fetch.h"
34
#include "content/fetchers/data.h"
35
#include "content/urldb.h"
36
#include "desktop/netsurf.h"
37
#include "desktop/options.h"
38
#include "utils/log.h"
39
#include "utils/messages.h"
40
#include "utils/url.h"
41
#include "utils/utils.h"
42
#include "utils/ring.h"
43
#include "utils/base64.h"
44
 
45
struct fetch_data_context {
46
	struct fetch *parent_fetch;
47
	char *url;
48
	char *mimetype;
49
	char *data;
50
	size_t datalen;
51
	bool base64;
52
 
53
	bool aborted;
54
	bool locked;
55
 
56
	struct fetch_data_context *r_next, *r_prev;
57
};
58
 
59
static struct fetch_data_context *ring = NULL;
60
 
61
static CURL *curl;
62
 
63
static bool fetch_data_initialise(lwc_string *scheme)
64
{
65
	LOG(("fetch_data_initialise called for %s", lwc_string_data(scheme)));
66
	if ( (curl = curl_easy_init()) == NULL)
67
		return false;
68
	else
69
		return true;
70
}
71
 
72
static void fetch_data_finalise(lwc_string *scheme)
73
{
74
	LOG(("fetch_data_finalise called for %s", lwc_string_data(scheme)));
75
	curl_easy_cleanup(curl);
76
}
77
 
78
static bool fetch_data_can_fetch(const nsurl *url)
79
{
80
	return true;
81
}
82
 
83
static void *fetch_data_setup(struct fetch *parent_fetch, nsurl *url,
84
		 bool only_2xx, bool downgrade_tls, const char *post_urlenc,
85
		 const struct fetch_multipart_data *post_multipart,
86
		 const char **headers)
87
{
88
	struct fetch_data_context *ctx = calloc(1, sizeof(*ctx));
89
 
90
	if (ctx == NULL)
91
		return NULL;
92
 
93
	ctx->parent_fetch = parent_fetch;
94
 
95
	/* TODO: keep as nsurl to avoid copy */
96
	ctx->url = malloc(nsurl_length(url) + 1);
97
	if (ctx->url == NULL) {
98
		free(ctx);
99
		return NULL;
100
	}
101
	memcpy(ctx->url, nsurl_access(url), nsurl_length(url) + 1);
102
 
103
	RING_INSERT(ring, ctx);
104
 
105
	return ctx;
106
}
107
 
108
static bool fetch_data_start(void *ctx)
109
{
110
	return true;
111
}
112
 
113
static void fetch_data_free(void *ctx)
114
{
115
	struct fetch_data_context *c = ctx;
116
 
117
	free(c->url);
118
	free(c->data);
119
	free(c->mimetype);
120
	RING_REMOVE(ring, c);
121
	free(ctx);
122
}
123
 
124
static void fetch_data_abort(void *ctx)
125
{
126
	struct fetch_data_context *c = ctx;
127
 
128
	/* To avoid the poll loop having to deal with the fetch context
129
	 * disappearing from under it, we simply flag the abort here.
130
	 * The poll loop itself will perform the appropriate cleanup.
131
	 */
132
	c->aborted = true;
133
}
134
 
135
static void fetch_data_send_callback(const fetch_msg *msg,
136
		struct fetch_data_context *c)
137
{
138
	c->locked = true;
139
	fetch_send_callback(msg, c->parent_fetch);
140
	c->locked = false;
141
}
142
 
143
static bool fetch_data_process(struct fetch_data_context *c)
144
{
145
	fetch_msg msg;
146
	char *params;
147
	char *comma;
148
	char *unescaped;
149
        int templen;
150
 
151
	/* format of a data: URL is:
152
	 *   data:[][;base64],
153
	 * The mimetype is optional.  If it is missing, the , before the
154
	 * data must still be there.
155
	 */
156
 
157
	LOG(("url: %.140s", c->url));
158
 
159
	if (strlen(c->url) < 6) {
160
		/* 6 is the minimum possible length (data:,) */
161
		msg.type = FETCH_ERROR;
162
		msg.data.error = "Malformed data: URL";
163
		fetch_data_send_callback(&msg, c);
164
		return false;
165
	}
166
 
167
	/* skip the data: part */
168
	params = c->url + SLEN("data:");
169
 
170
	/* find the comma */
171
	if ( (comma = strchr(params, ',')) == NULL) {
172
		msg.type = FETCH_ERROR;
173
		msg.data.error = "Malformed data: URL";
174
		fetch_data_send_callback(&msg, c);
175
		return false;
176
	}
177
 
178
	if (params[0] == ',') {
179
		/* there is no mimetype here, assume text/plain */
180
		c->mimetype = strdup("text/plain;charset=US-ASCII");
181
	} else {
182
		/* make a copy of everything between data: and the comma */
183
		c->mimetype = strndup(params, comma - params);
184
	}
185
 
186
	if (c->mimetype == NULL) {
187
		msg.type = FETCH_ERROR;
188
		msg.data.error =
189
			"Unable to allocate memory for mimetype in data: URL";
190
		fetch_data_send_callback(&msg, c);
191
		return false;
192
	}
193
 
194
	if (strcmp(c->mimetype + strlen(c->mimetype) - 7, ";base64") == 0) {
195
		c->base64 = true;
196
		c->mimetype[strlen(c->mimetype) - 7] = '\0';
197
	} else {
198
		c->base64 = false;
199
	}
200
 
201
	/* we URL unescape the data first, just incase some insane page
202
	 * decides to nest URL and base64 encoding.  Like, say, Acid2.
203
	 */
204
        templen = c->datalen;
205
        unescaped = curl_easy_unescape(curl, comma + 1, 0, &templen);
206
        c->datalen = templen;
207
        if (unescaped == NULL) {
208
		msg.type = FETCH_ERROR;
209
		msg.data.error = "Unable to URL decode data: URL";
210
		fetch_data_send_callback(&msg, c);
211
		return false;
212
	}
213
 
214
	if (c->base64) {
215
		c->data = malloc(c->datalen); /* safe: always gets smaller */
216
		if (base64_decode(unescaped, c->datalen, c->data,
217
				&(c->datalen)) == false) {
218
			msg.type = FETCH_ERROR;
219
			msg.data.error = "Unable to Base64 decode data: URL";
220
			fetch_data_send_callback(&msg, c);
221
			curl_free(unescaped);
222
			return false;
223
		}
224
	} else {
225
		c->data = malloc(c->datalen);
226
		if (c->data == NULL) {
227
			msg.type = FETCH_ERROR;
228
			msg.data.error =
229
				"Unable to allocate memory for data: URL";
230
			fetch_data_send_callback(&msg, c);
231
			curl_free(unescaped);
232
			return false;
233
		}
234
		memcpy(c->data, unescaped, c->datalen);
235
	}
236
 
237
	curl_free(unescaped);
238
 
239
	return true;
240
}
241
 
242
static void fetch_data_poll(lwc_string *scheme)
243
{
244
	fetch_msg msg;
245
	struct fetch_data_context *c, *next;
246
 
247
	if (ring == NULL) return;
248
 
249
	/* Iterate over ring, processing each pending fetch */
250
	c = ring;
251
	do {
252
		/* Ignore fetches that have been flagged as locked.
253
		 * This allows safe re-entrant calls to this function.
254
		 * Re-entrancy can occur if, as a result of a callback,
255
		 * the interested party causes fetch_poll() to be called
256
		 * again.
257
		 */
258
		if (c->locked == true) {
259
			next = c->r_next;
260
			continue;
261
		}
262
 
263
		/* Only process non-aborted fetches */
264
		if (c->aborted == false && fetch_data_process(c) == true) {
265
			char header[64];
266
 
267
			fetch_set_http_code(c->parent_fetch, 200);
268
			LOG(("setting data: MIME type to %s, length to %zd",
269
					c->mimetype, c->datalen));
270
			/* Any callback can result in the fetch being aborted.
271
			 * Therefore, we _must_ check for this after _every_
272
			 * call to fetch_data_send_callback().
273
			 */
274
			snprintf(header, sizeof header, "Content-Type: %s",
275
					c->mimetype);
276
			msg.type = FETCH_HEADER;
277
			msg.data.header_or_data.buf = (const uint8_t *) header;
278
			msg.data.header_or_data.len = strlen(header);
279
			fetch_data_send_callback(&msg, c);
280
 
281
			if (c->aborted == false) {
282
				snprintf(header, sizeof header,
283
					"Content-Length: %"SSIZET_FMT,
284
					c->datalen);
285
				msg.type = FETCH_HEADER;
286
				msg.data.header_or_data.buf =
287
						(const uint8_t *) header;
288
				msg.data.header_or_data.len = strlen(header);
289
				fetch_data_send_callback(&msg, c);
290
			}
291
 
292
			if (c->aborted == false) {
293
				msg.type = FETCH_DATA;
294
				msg.data.header_or_data.buf =
295
						(const uint8_t *) c->data;
296
				msg.data.header_or_data.len = c->datalen;
297
				fetch_data_send_callback(&msg, c);
298
			}
299
 
300
			if (c->aborted == false) {
301
				msg.type = FETCH_FINISHED;
302
				fetch_data_send_callback(&msg, c);
303
			}
304
		} else {
305
			LOG(("Processing of %s failed!", c->url));
306
 
307
			/* Ensure that we're unlocked here. If we aren't,
308
			 * then fetch_data_process() is broken.
309
			 */
310
			assert(c->locked == false);
311
		}
312
 
313
		/* Compute next fetch item at the last possible moment as
314
		 * processing this item may have added to the ring.
315
		 */
316
		next = c->r_next;
317
 
318
		fetch_remove_from_queues(c->parent_fetch);
319
		fetch_free(c->parent_fetch);
320
 
321
		/* Advance to next ring entry, exiting if we've reached
322
		 * the start of the ring or the ring has become empty
323
		 */
324
	} while ( (c = next) != ring && ring != NULL);
325
}
326
 
327
void fetch_data_register(void)
328
{
329
	lwc_string *scheme;
330
 
331
	if (lwc_intern_string("data", SLEN("data"), &scheme) != lwc_error_ok) {
332
		die("Failed to initialise the fetch module "
333
				"(couldn't intern \"data\").");
334
	}
335
 
336
	fetch_add_fetcher(scheme,
337
		fetch_data_initialise,
338
		fetch_data_can_fetch,
339
		fetch_data_setup,
340
		fetch_data_start,
341
		fetch_data_abort,
342
		fetch_data_free,
343
		fetch_data_poll,
344
		fetch_data_finalise);
345
}