Subversion Repositories Kolibri OS

Rev

Rev 4364 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
3584 sourcerer 1
/*
2
 * Copyright 2010 Vincent Sanders 
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
/* file: URL handling. Based on the data fetcher by Rob Kendrick */
20
 
21
#include 
22
#include 
23
#include 
24
#include 
25
#include 
26
#include 
27
#include 
28
#include 
29
#include 
30
#include 
31
#include 
32
#include 
33
#include 
34
#include 
35
#include 
36
 
37
#include "utils/config.h"
38
 
39
#ifdef HAVE_MMAP
40
#include 
41
#endif
42
 
43
#include 
44
 
45
#include "content/dirlist.h"
46
#include "content/fetch.h"
47
#include "content/fetchers/file.h"
48
#include "content/urldb.h"
49
#include "desktop/netsurf.h"
50
#include "desktop/options.h"
51
#include "utils/errors.h"
52
#include "utils/log.h"
53
#include "utils/messages.h"
54
#include "utils/url.h"
55
#include "utils/utils.h"
56
#include "utils/ring.h"
57
 
58
/* Maximum size of read buffer */
59
#define FETCH_FILE_MAX_BUF_SIZE (1024 * 1024)
60
 
61
/** Context for a fetch */
62
struct fetch_file_context {
63
	struct fetch_file_context *r_next, *r_prev;
64
 
65
	struct fetch *fetchh; /**< Handle for this fetch */
66
 
67
	bool aborted; /**< Flag indicating fetch has been aborted */
68
	bool locked; /**< Flag indicating entry is already entered */
69
 
70
	nsurl *url; /**< The full url the fetch refers to */
71
	char *path; /**< The actual path to be used with open() */
5043 ashmew2 72
 
3584 sourcerer 73
	time_t file_etag; /**< Request etag for file (previous st.m_time) */
74
};
75
 
76
static struct fetch_file_context *ring = NULL;
77
 
78
/** issue fetch callbacks with locking */
79
static inline bool fetch_file_send_callback(const fetch_msg *msg,
80
		struct fetch_file_context *ctx)
81
{
82
	ctx->locked = true;
83
	fetch_send_callback(msg, ctx->fetchh);
84
	ctx->locked = false;
85
 
86
	return ctx->aborted;
87
}
88
 
89
static bool fetch_file_send_header(struct fetch_file_context *ctx,
90
		const char *fmt, ...)
91
{
92
	fetch_msg msg;
93
	char header[64];
94
	va_list ap;
95
 
96
	va_start(ap, fmt);
97
 
98
	vsnprintf(header, sizeof header, fmt, ap);
99
 
100
	va_end(ap);
101
 
102
	msg.type = FETCH_HEADER;
103
	msg.data.header_or_data.buf = (const uint8_t *) header;
104
	msg.data.header_or_data.len = strlen(header);
105
	fetch_file_send_callback(&msg, ctx);
106
 
107
	return ctx->aborted;
108
}
109
 
110
/** callback to initialise the file fetcher. */
111
static bool fetch_file_initialise(lwc_string *scheme)
112
{
113
	return true;
114
}
115
 
116
/** callback to initialise the file fetcher. */
117
static void fetch_file_finalise(lwc_string *scheme)
118
{
119
}
120
 
121
static bool fetch_file_can_fetch(const nsurl *url)
122
{
123
	return true;
124
}
125
 
126
/** callback to set up a file fetch context. */
127
static void *
128
fetch_file_setup(struct fetch *fetchh,
129
		 nsurl *url,
130
		 bool only_2xx,
131
		 bool downgrade_tls,
132
		 const char *post_urlenc,
133
		 const struct fetch_multipart_data *post_multipart,
134
		 const char **headers)
135
{
136
	struct fetch_file_context *ctx;
137
	int i;
138
 
139
	ctx = calloc(1, sizeof(*ctx));
140
	if (ctx == NULL)
141
		return NULL;
142
 
143
	ctx->path = url_to_path(nsurl_access(url));
144
	if (ctx->path == NULL) {
145
		free(ctx);
146
		return NULL;
147
	}
148
 
149
	ctx->url = nsurl_ref(url);
150
 
151
	/* Scan request headers looking for If-None-Match */
152
	for (i = 0; headers[i] != NULL; i++) {
153
		if (strncasecmp(headers[i], "If-None-Match:",
154
				SLEN("If-None-Match:")) == 0) {
155
			/* If-None-Match: "12345678" */
156
			const char *d = headers[i] + SLEN("If-None-Match:");
157
 
158
			/* Scan to first digit, if any */
159
			while (*d != '\0' && (*d < '0' || '9' < *d))
160
				d++;
161
 
162
			/* Convert to time_t */
163
			if (*d != '\0')
164
				ctx->file_etag = atoi(d);
165
		}
166
	}
167
 
168
	ctx->fetchh = fetchh;
169
 
170
	RING_INSERT(ring, ctx);
171
	return ctx;
172
}
173
 
174
/** callback to free a file fetch */
175
static void fetch_file_free(void *ctx)
176
{
177
	struct fetch_file_context *c = ctx;
178
	nsurl_unref(c->url);
179
	free(c->path);
180
	RING_REMOVE(ring, c);
181
	free(ctx);
182
}
183
 
184
/** callback to start a file fetch */
185
static bool fetch_file_start(void *ctx)
186
{
187
	return true;
188
}
189
 
190
/** callback to abort a file fetch */
191
static void fetch_file_abort(void *ctx)
192
{
193
	struct fetch_file_context *c = ctx;
194
 
195
	/* To avoid the poll loop having to deal with the fetch context
196
	 * disappearing from under it, we simply flag the abort here.
197
	 * The poll loop itself will perform the appropriate cleanup.
198
	 */
199
	c->aborted = true;
200
}
201
 
202
static int fetch_file_errno_to_http_code(int error_no)
203
{
204
	switch (error_no) {
205
	case ENAMETOOLONG:
206
		return 400;
207
	case EACCES:
208
		return 403;
209
	case ENOENT:
210
		return 404;
211
	default:
212
		break;
213
	}
214
 
215
	return 500;
216
}
217
 
218
static void fetch_file_process_error(struct fetch_file_context *ctx, int code)
219
{
220
	fetch_msg msg;
221
	char buffer[1024];
222
	const char *title;
223
	char key[8];
224
 
225
	/* content is going to return error code */
226
	fetch_set_http_code(ctx->fetchh, code);
227
 
228
	/* content type */
229
	if (fetch_file_send_header(ctx, "Content-Type: text/html"))
230
		goto fetch_file_process_error_aborted;
231
 
232
	snprintf(key, sizeof key, "HTTP%03d", code);
233
	title = messages_get(key);
234
 
235
	snprintf(buffer, sizeof buffer, "%s"
5043 ashmew2 236
		 "

%s

"
3584 sourcerer 237
			"

Error %d while fetching file %s

",
238
			title, title, code, nsurl_access(ctx->url));
239
 
240
	msg.type = FETCH_DATA;
241
	msg.data.header_or_data.buf = (const uint8_t *) buffer;
242
	msg.data.header_or_data.len = strlen(buffer);
243
	if (fetch_file_send_callback(&msg, ctx))
244
		goto fetch_file_process_error_aborted;
245
 
246
	msg.type = FETCH_FINISHED;
247
	fetch_file_send_callback(&msg, ctx);
248
 
249
fetch_file_process_error_aborted:
250
	return;
251
}
252
 
253
 
254
/** Process object as a regular file */
255
static void fetch_file_process_plain(struct fetch_file_context *ctx,
256
				     struct stat *fdstat)
257
{
258
#ifdef HAVE_MMAP
259
	fetch_msg msg;
260
	char *buf = NULL;
261
	size_t buf_size;
262
 
263
	int fd; /**< The file descriptor of the object */
264
 
265
	/* Check if we can just return not modified */
266
	if (ctx->file_etag != 0 && ctx->file_etag == fdstat->st_mtime) {
267
		fetch_set_http_code(ctx->fetchh, 304);
268
		msg.type = FETCH_NOTMODIFIED;
269
		fetch_file_send_callback(&msg, ctx);
270
		return;
271
	}
272
 
273
	fd = open(ctx->path, O_RDONLY);
274
	if (fd < 0) {
275
		/* process errors as appropriate */
276
		fetch_file_process_error(ctx,
277
				fetch_file_errno_to_http_code(errno));
278
		return;
279
	}
280
 
281
	/* set buffer size */
282
	buf_size = fdstat->st_size;
283
 
284
	/* allocate the buffer storage */
285
	if (buf_size > 0) {
286
		buf = mmap(NULL, buf_size, PROT_READ, MAP_SHARED, fd, 0);
287
		if (buf == MAP_FAILED) {
288
			msg.type = FETCH_ERROR;
289
			msg.data.error = "Unable to map memory for file data buffer";
290
			fetch_file_send_callback(&msg, ctx);
291
			close(fd);
292
			return;
293
		}
294
	}
295
 
296
	/* fetch is going to be successful */
297
	fetch_set_http_code(ctx->fetchh, 200);
298
 
299
	/* Any callback can result in the fetch being aborted.
300
	 * Therefore, we _must_ check for this after _every_ call to
301
	 * fetch_file_send_callback().
302
	 */
303
 
304
	/* content type */
305
	if (fetch_file_send_header(ctx, "Content-Type: %s",
306
			fetch_filetype(ctx->path)))
307
		goto fetch_file_process_aborted;
308
 
309
	/* content length */
310
	if (fetch_file_send_header(ctx, "Content-Length: %"SSIZET_FMT, fdstat->st_size))
311
		goto fetch_file_process_aborted;
312
 
313
	/* create etag */
314
	if (fetch_file_send_header(ctx, "ETag: \"%10" PRId64 "\"",
315
			(int64_t) fdstat->st_mtime))
316
		goto fetch_file_process_aborted;
317
 
318
 
319
	msg.type = FETCH_DATA;
320
	msg.data.header_or_data.buf = (const uint8_t *) buf;
321
	msg.data.header_or_data.len = buf_size;
322
	fetch_file_send_callback(&msg, ctx);
323
 
324
	if (ctx->aborted == false) {
325
		msg.type = FETCH_FINISHED;
326
		fetch_file_send_callback(&msg, ctx);
327
	}
328
 
329
fetch_file_process_aborted:
330
 
331
	if (buf != NULL)
332
		munmap(buf, buf_size);
333
	close(fd);
334
#else
335
	fetch_msg msg;
336
	char *buf;
337
	size_t buf_size;
338
 
339
	ssize_t tot_read = 0;
340
	ssize_t res;
341
 
342
	FILE *infile;
343
 
344
	/* Check if we can just return not modified */
345
	if (ctx->file_etag != 0 && ctx->file_etag == fdstat->st_mtime) {
346
		fetch_set_http_code(ctx->fetchh, 304);
347
		msg.type = FETCH_NOTMODIFIED;
348
		fetch_file_send_callback(&msg, ctx);
349
		return;
350
	}
351
 
352
	infile = fopen(ctx->path, "rb");
353
	if (infile == NULL) {
354
		/* process errors as appropriate */
355
		fetch_file_process_error(ctx,
356
				fetch_file_errno_to_http_code(errno));
357
		return;
358
	}
359
 
360
	/* set buffer size */
361
	buf_size = fdstat->st_size;
362
	if (buf_size > FETCH_FILE_MAX_BUF_SIZE)
363
		buf_size = FETCH_FILE_MAX_BUF_SIZE;
364
 
365
	/* allocate the buffer storage */
366
	buf = malloc(buf_size);
367
	if (buf == NULL) {
368
		msg.type = FETCH_ERROR;
369
		msg.data.error =
370
			"Unable to allocate memory for file data buffer";
371
		fetch_file_send_callback(&msg, ctx);
372
		fclose(infile);
373
		return;
374
	}
375
 
376
	/* fetch is going to be successful */
377
	fetch_set_http_code(ctx->fetchh, 200);
378
 
379
	/* Any callback can result in the fetch being aborted.
380
	 * Therefore, we _must_ check for this after _every_ call to
381
	 * fetch_file_send_callback().
382
	 */
383
 
384
	/* content type */
385
	if (fetch_file_send_header(ctx, "Content-Type: %s",
386
			fetch_filetype(ctx->path)))
387
		goto fetch_file_process_aborted;
388
 
389
	/* content length */
390
	if (fetch_file_send_header(ctx, "Content-Length: %"SSIZET_FMT, fdstat->st_size))
391
		goto fetch_file_process_aborted;
392
 
393
	/* create etag */
394
	if (fetch_file_send_header(ctx, "ETag: \"%10" PRId64 "\"",
395
			(int64_t) fdstat->st_mtime))
396
		goto fetch_file_process_aborted;
397
 
398
	/* main data loop */
399
	while (tot_read < fdstat->st_size) {
400
		res = fread(buf, 1, buf_size, infile);
401
		if (res == 0) {
402
			if (feof(infile)) {
403
				msg.type = FETCH_ERROR;
404
				msg.data.error = "Unexpected EOF reading file";
405
				fetch_file_send_callback(&msg, ctx);
406
				goto fetch_file_process_aborted;
407
			} else {
408
				msg.type = FETCH_ERROR;
409
				msg.data.error = "Error reading file";
410
				fetch_file_send_callback(&msg, ctx);
411
				goto fetch_file_process_aborted;
412
			}
413
		}
414
		tot_read += res;
415
 
416
		msg.type = FETCH_DATA;
417
		msg.data.header_or_data.buf = (const uint8_t *) buf;
418
		msg.data.header_or_data.len = res;
419
		if (fetch_file_send_callback(&msg, ctx))
420
			break;
421
	}
422
 
423
	if (ctx->aborted == false) {
424
		msg.type = FETCH_FINISHED;
425
		fetch_file_send_callback(&msg, ctx);
426
	}
427
 
428
fetch_file_process_aborted:
429
 
430
	fclose(infile);
431
	free(buf);
432
#endif
433
	return;
434
}
435
 
436
static char *gen_nice_title(char *path)
437
{
438
	char *nice_path, *cnv, *tmp;
439
	char *title;
440
	int title_length;
441
 
442
	/* Convert path for display */
443
	nice_path = malloc(strlen(path) * SLEN("&") + 1);
444
	if (nice_path == NULL) {
445
		return NULL;
446
	}
447
 
448
	/* Escape special HTML characters */
449
	for (cnv = nice_path, tmp = path; *tmp != '\0'; tmp++) {
450
		if (*tmp == '<') {
451
			*cnv++ = '&';
452
			*cnv++ = 'l';
453
			*cnv++ = 't';
454
			*cnv++ = ';';
455
		} else if (*tmp == '>') {
456
			*cnv++ = '&';
457
			*cnv++ = 'g';
458
			*cnv++ = 't';
459
			*cnv++ = ';';
460
		} else if (*tmp == '&') {
461
			*cnv++ = '&';
462
			*cnv++ = 'a';
463
			*cnv++ = 'm';
464
			*cnv++ = 'p';
465
			*cnv++ = ';';
466
		} else {
467
			*cnv++ = *tmp;
468
		}
469
	}
470
	*cnv = '\0';
471
 
472
	/* Construct a localised title string */
473
	title_length = (cnv - nice_path) + strlen(messages_get("FileIndex"));
474
	title = malloc(title_length + 1);
475
 
476
	if (title == NULL) {
477
		free(nice_path);
478
		return NULL;
479
	}
480
 
481
	/* Set title to localised "Index of " */
482
	snprintf(title, title_length, messages_get("FileIndex"), nice_path);
483
 
484
	free(nice_path);
485
 
486
	return title;
487
}
488
 
489
 
490
static void fetch_file_process_dir(struct fetch_file_context *ctx,
491
				   struct stat *fdstat)
492
{
493
	fetch_msg msg;
494
	char buffer[1024]; /* Output buffer */
495
	bool even = false; /* formatting flag */
496
	char *title; /* pretty printed title */
497
	nserror err; /* result from url routines */
498
	nsurl *up; /* url of parent */
499
	char *path; /* url for list entries */
500
 
501
	DIR *scandir; /* handle for enumerating the directory */
502
	struct dirent* ent; /* leaf directory entry */
503
	struct stat ent_stat; /* stat result of leaf entry */
504
	char datebuf[64]; /* buffer for date text */
505
	char timebuf[64]; /* buffer for time text */
506
	char urlpath[PATH_MAX]; /* buffer for leaf entry path */
507
 
508
	scandir = opendir(ctx->path);
509
	if (scandir == NULL) {
510
		fetch_file_process_error(ctx,
511
			fetch_file_errno_to_http_code(errno));
512
		return;
513
	}
514
 
515
	/* fetch is going to be successful */
516
	fetch_set_http_code(ctx->fetchh, 200);
517
 
518
	/* force no-cache */
519
	if (fetch_file_send_header(ctx, "Cache-Control: no-cache"))
520
		goto fetch_file_process_dir_aborted;
521
 
522
	/* content type */
523
	if (fetch_file_send_header(ctx, "Content-Type: text/html"))
524
		goto fetch_file_process_dir_aborted;
525
 
526
	msg.type = FETCH_DATA;
527
	msg.data.header_or_data.buf = (const uint8_t *) buffer;
528
 
529
	/* directory listing top */
530
	dirlist_generate_top(buffer, sizeof buffer);
531
	msg.data.header_or_data.len = strlen(buffer);
532
	if (fetch_file_send_callback(&msg, ctx))
533
		goto fetch_file_process_dir_aborted;
534
 
535
	/* directory listing title */
536
	title = gen_nice_title(ctx->path);
537
	dirlist_generate_title(title, buffer, sizeof buffer);
538
	free(title);
539
	msg.data.header_or_data.len = strlen(buffer);
540
	if (fetch_file_send_callback(&msg, ctx))
541
		goto fetch_file_process_dir_aborted;
542
 
543
	/* Print parent directory link */
544
	err = nsurl_parent(ctx->url, &up);
545
	if (err == NSERROR_OK) {
546
		if (nsurl_compare(ctx->url, up, NSURL_COMPLETE) == false) {
547
			/* different URL; have parent */
548
			dirlist_generate_parent_link(nsurl_access(up),
549
					buffer, sizeof buffer);
550
 
551
			msg.data.header_or_data.len = strlen(buffer);
552
			fetch_file_send_callback(&msg, ctx);
553
		}
554
		nsurl_unref(up);
555
 
556
		if (ctx->aborted)
557
			goto fetch_file_process_dir_aborted;
558
 
559
	}
560
 
561
	/* directory list headings */
562
	dirlist_generate_headings(buffer, sizeof buffer);
563
	msg.data.header_or_data.len = strlen(buffer);
564
	if (fetch_file_send_callback(&msg, ctx))
565
		goto fetch_file_process_dir_aborted;
566
 
567
	while ((ent = readdir(scandir)) != NULL) {
568
 
569
		if (ent->d_name[0] == '.')
570
			continue;
571
 
572
		strncpy(urlpath, ctx->path, sizeof urlpath);
573
		if (path_add_part(urlpath, sizeof urlpath,
574
				ent->d_name) == false)
575
			continue;
576
 
577
		if (stat(urlpath, &ent_stat) != 0) {
578
			ent_stat.st_mode = 0;
579
			datebuf[0] = 0;
580
			timebuf[0] = 0;
581
		} else {
582
			/* Get date in output format */
583
			if (strftime((char *)&datebuf, sizeof datebuf,
584
				     "%a %d %b %Y",
585
				     localtime(&ent_stat.st_mtime)) == 0) {
586
				strncpy(datebuf, "-", sizeof datebuf);
587
			}
588
 
589
			/* Get time in output format */
590
			if (strftime((char *)&timebuf, sizeof timebuf,
591
				     "%H:%M",
592
				     localtime(&ent_stat.st_mtime)) == 0) {
593
				strncpy(timebuf, "-", sizeof timebuf);
594
			}
595
		}
596
 
597
		if((path = path_to_url(urlpath)) == NULL)
598
			continue;
599
 
600
		if (S_ISREG(ent_stat.st_mode)) {
601
			/* regular file */
602
			dirlist_generate_row(even,
603
					     false,
604
					     path,
605
					     ent->d_name,
606
					     fetch_filetype(urlpath),
607
					     ent_stat.st_size,
608
					     datebuf, timebuf,
609
					     buffer, sizeof(buffer));
610
		} else if (S_ISDIR(ent_stat.st_mode)) {
611
			/* directory */
612
			dirlist_generate_row(even,
613
					     true,
614
					     path,
615
					     ent->d_name,
616
					     messages_get("FileDirectory"),
617
					     -1,
618
					     datebuf, timebuf,
619
					     buffer, sizeof(buffer));
620
		} else {
621
			/* something else */
622
			dirlist_generate_row(even,
623
					     false,
624
					     path,
625
					     ent->d_name,
626
					     "",
627
					     -1,
628
					     datebuf, timebuf,
629
					     buffer, sizeof(buffer));
630
		}
631
 
632
		free(path);
633
 
634
		msg.data.header_or_data.len = strlen(buffer);
635
		if (fetch_file_send_callback(&msg, ctx))
636
			goto fetch_file_process_dir_aborted;
637
 
638
		even = !even;
639
	}
640
 
641
	/* directory listing bottom */
642
	dirlist_generate_bottom(buffer, sizeof buffer);
643
	msg.data.header_or_data.len = strlen(buffer);
644
	if (fetch_file_send_callback(&msg, ctx))
645
		goto fetch_file_process_dir_aborted;
646
 
647
	msg.type = FETCH_FINISHED;
648
	fetch_file_send_callback(&msg, ctx);
649
 
650
fetch_file_process_dir_aborted:
651
 
652
	closedir(scandir);
653
}
654
 
655
 
656
/* process a file fetch */
657
static void fetch_file_process(struct fetch_file_context *ctx)
658
{
659
	struct stat fdstat; /**< The objects stat */
660
 
661
	if (stat(ctx->path, &fdstat) != 0) {
662
		/* process errors as appropriate */
663
		fetch_file_process_error(ctx,
664
				fetch_file_errno_to_http_code(errno));
665
		return;
666
	}
667
 
668
	if (S_ISDIR(fdstat.st_mode)) {
669
		/* directory listing */
670
		fetch_file_process_dir(ctx, &fdstat);
671
		return;
672
	} else if (S_ISREG(fdstat.st_mode)) {
673
		/* regular file */
674
		fetch_file_process_plain(ctx, &fdstat);
675
		return;
676
	} else {
677
		/* unhandled type of file */
678
		fetch_file_process_error(ctx, 501);
679
	}
680
 
681
	return;
682
}
683
 
684
/** callback to poll for additional file fetch contents */
685
static void fetch_file_poll(lwc_string *scheme)
686
{
687
	struct fetch_file_context *c, *next;
688
 
689
	if (ring == NULL) return;
690
 
691
	/* Iterate over ring, processing each pending fetch */
692
	c = ring;
693
	do {
694
		/* Ignore fetches that have been flagged as locked.
695
		 * This allows safe re-entrant calls to this function.
696
		 * Re-entrancy can occur if, as a result of a callback,
697
		 * the interested party causes fetch_poll() to be called
698
		 * again.
699
		 */
700
		if (c->locked == true) {
701
			next = c->r_next;
702
			continue;
703
		}
704
 
705
		/* Only process non-aborted fetches */
706
		if (c->aborted == false) {
707
			/* file fetches can be processed in one go */
708
			fetch_file_process(c);
709
		}
710
 
711
		/* Compute next fetch item at the last possible moment as
712
		 * processing this item may have added to the ring.
713
		 */
714
		next = c->r_next;
715
 
716
		fetch_remove_from_queues(c->fetchh);
717
		fetch_free(c->fetchh);
718
 
719
		/* Advance to next ring entry, exiting if we've reached
720
		 * the start of the ring or the ring has become empty
721
		 */
722
	} while ( (c = next) != ring && ring != NULL);
723
}
724
 
725
void fetch_file_register(void)
726
{
727
	lwc_string *scheme;
728
 
729
	if (lwc_intern_string("file", SLEN("file"), &scheme) != lwc_error_ok) {
730
		die("Failed to initialise the fetch module "
731
				"(couldn't intern \"file\").");
732
	}
733
 
734
	fetch_add_fetcher(scheme,
735
		fetch_file_initialise,
736
		fetch_file_can_fetch,
737
		fetch_file_setup,
738
		fetch_file_start,
739
		fetch_file_abort,
740
		fetch_file_free,
741
		fetch_file_poll,
742
		fetch_file_finalise);
743
}