1 /*
2  * Copyright 2010 Vincent Sanders <vince@netsurf-browser.org>
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 <http://www.gnu.org/licenses/>.
17  */
18 
19 /**
20  * \file
21  *
22  * file scheme URL handling. Based on the data fetcher by Rob Kendrick
23  *
24  * output dates and directory ordering are affected by the current locale
25  */
26 
27 #include "utils/config.h"
28 
29 #include <stdlib.h>
30 #include <ctype.h>
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <fcntl.h>
34 #include <unistd.h>
35 #include <errno.h>
36 #include <stdbool.h>
37 #include <string.h>
38 #include <strings.h>
39 #include <time.h>
40 #include <stdio.h>
41 #include <stdarg.h>
42 #ifdef HAVE_MMAP
43 #include <sys/mman.h>
44 #endif
45 #include <libwapcaplet/libwapcaplet.h>
46 
47 #include "netsurf/inttypes.h"
48 #include "utils/nsurl.h"
49 #include "utils/dirent.h"
50 #include "utils/corestrings.h"
51 #include "utils/messages.h"
52 #include "utils/utils.h"
53 #include "utils/log.h"
54 #include "utils/time.h"
55 #include "utils/ring.h"
56 #include "utils/file.h"
57 #include "netsurf/fetch.h"
58 #include "desktop/gui_internal.h"
59 
60 #include "content/dirlist.h"
61 #include "content/fetch.h"
62 #include "content/fetchers.h"
63 #include "content/fetchers/file.h"
64 
65 /* Maximum size of read buffer */
66 #define FETCH_FILE_MAX_BUF_SIZE (1024 * 1024)
67 
68 /** Context for a fetch */
69 struct fetch_file_context {
70 	struct fetch_file_context *r_next, *r_prev;
71 
72 	struct fetch *fetchh; /**< Handle for this fetch */
73 
74 	bool aborted; /**< Flag indicating fetch has been aborted */
75 	bool locked; /**< Flag indicating entry is already entered */
76 
77 	nsurl *url; /**< The full url the fetch refers to */
78 	char *path; /**< The actual path to be used with open() */
79 
80 	time_t file_etag; /**< Request etag for file (previous st.m_time) */
81 };
82 
83 static struct fetch_file_context *ring = NULL;
84 
85 /** issue fetch callbacks with locking */
fetch_file_send_callback(const fetch_msg * msg,struct fetch_file_context * ctx)86 static inline bool fetch_file_send_callback(const fetch_msg *msg,
87 		struct fetch_file_context *ctx)
88 {
89 	ctx->locked = true;
90 	fetch_send_callback(msg, ctx->fetchh);
91 	ctx->locked = false;
92 
93 	return ctx->aborted;
94 }
95 
fetch_file_send_header(struct fetch_file_context * ctx,const char * fmt,...)96 static bool fetch_file_send_header(struct fetch_file_context *ctx,
97 		const char *fmt, ...)
98 {
99 	fetch_msg msg;
100 	char header[64];
101 	va_list ap;
102 	int len;
103 
104 	va_start(ap, fmt);
105 	len = vsnprintf(header, sizeof header, fmt, ap);
106 	va_end(ap);
107 
108 	if (len >= (int)sizeof(header) || len < 0) {
109 		return false;
110 	}
111 
112 	msg.type = FETCH_HEADER;
113 	msg.data.header_or_data.buf = (const uint8_t *) header;
114 	msg.data.header_or_data.len = len;
115 
116 	return fetch_file_send_callback(&msg, ctx);
117 }
118 
119 /** callback to initialise the file fetcher. */
fetch_file_initialise(lwc_string * scheme)120 static bool fetch_file_initialise(lwc_string *scheme)
121 {
122 	return true;
123 }
124 
125 /** callback to initialise the file fetcher. */
fetch_file_finalise(lwc_string * scheme)126 static void fetch_file_finalise(lwc_string *scheme)
127 {
128 }
129 
fetch_file_can_fetch(const nsurl * url)130 static bool fetch_file_can_fetch(const nsurl *url)
131 {
132 	return true;
133 }
134 
135 /** callback to set up a file fetch context. */
136 static void *
fetch_file_setup(struct fetch * fetchh,nsurl * url,bool only_2xx,bool downgrade_tls,const char * post_urlenc,const struct fetch_multipart_data * post_multipart,const char ** headers)137 fetch_file_setup(struct fetch *fetchh,
138 		 nsurl *url,
139 		 bool only_2xx,
140 		 bool downgrade_tls,
141 		 const char *post_urlenc,
142 		 const struct fetch_multipart_data *post_multipart,
143 		 const char **headers)
144 {
145 	struct fetch_file_context *ctx;
146 	int i;
147 	nserror ret;
148 
149 	ctx = calloc(1, sizeof(*ctx));
150 	if (ctx == NULL)
151 		return NULL;
152 
153 	ret = guit->file->nsurl_to_path(url, &ctx->path);
154 	if (ret != NSERROR_OK) {
155 		free(ctx);
156 		return NULL;
157 	}
158 
159 	ctx->url = nsurl_ref(url);
160 
161 	/* Scan request headers looking for If-None-Match */
162 	for (i = 0; headers[i] != NULL; i++) {
163 		if (strncasecmp(headers[i], "If-None-Match:",
164 				SLEN("If-None-Match:")) != 0) {
165 			continue;
166 		}
167 
168 		/* If-None-Match: "12345678" */
169 		const char *d = headers[i] + SLEN("If-None-Match:");
170 
171 		/* Scan to first digit, if any */
172 		while (*d != '\0' && (*d < '0' || '9' < *d))
173 			d++;
174 
175 		/* Convert to time_t */
176 		if (*d != '\0') {
177 			ret = nsc_snptimet(d, strlen(d), &ctx->file_etag);
178 			if (ret != NSERROR_OK) {
179 				NSLOG(fetch, WARNING,
180 						"Bad If-None-Match value");
181 			}
182 		}
183 	}
184 
185 	ctx->fetchh = fetchh;
186 
187 	RING_INSERT(ring, ctx);
188 
189 	return ctx;
190 }
191 
192 /** callback to free a file fetch */
fetch_file_free(void * ctx)193 static void fetch_file_free(void *ctx)
194 {
195 	struct fetch_file_context *c = ctx;
196 	nsurl_unref(c->url);
197 	free(c->path);
198 	free(ctx);
199 }
200 
201 /** callback to start a file fetch */
fetch_file_start(void * ctx)202 static bool fetch_file_start(void *ctx)
203 {
204 	return true;
205 }
206 
207 /** callback to abort a file fetch */
fetch_file_abort(void * ctx)208 static void fetch_file_abort(void *ctx)
209 {
210 	struct fetch_file_context *c = ctx;
211 
212 	/* To avoid the poll loop having to deal with the fetch context
213 	 * disappearing from under it, we simply flag the abort here.
214 	 * The poll loop itself will perform the appropriate cleanup.
215 	 */
216 	c->aborted = true;
217 }
218 
fetch_file_errno_to_http_code(int error_no)219 static int fetch_file_errno_to_http_code(int error_no)
220 {
221 	switch (error_no) {
222 	case ENAMETOOLONG:
223 		return 400;
224 	case EACCES:
225 		return 403;
226 	case ENOENT:
227 		return 404;
228 	default:
229 		break;
230 	}
231 
232 	return 500;
233 }
234 
fetch_file_process_error(struct fetch_file_context * ctx,int code)235 static void fetch_file_process_error(struct fetch_file_context *ctx, int code)
236 {
237 	fetch_msg msg;
238 	char buffer[1024];
239 	const char *title;
240 	char key[8];
241 
242 	/* content is going to return error code */
243 	fetch_set_http_code(ctx->fetchh, code);
244 
245 	/* content type */
246 	if (fetch_file_send_header(ctx, "Content-Type: text/html"))
247 		goto fetch_file_process_error_aborted;
248 
249 	snprintf(key, sizeof key, "HTTP%03d", code);
250 	title = messages_get(key);
251 
252 	snprintf(buffer, sizeof buffer, "<html><head><title>%s</title></head>"
253 			"<body><h1>%s</h1>"
254 			"<p>Error %d while fetching file %s</p></body></html>",
255 			title, title, code, nsurl_access(ctx->url));
256 
257 	msg.type = FETCH_DATA;
258 	msg.data.header_or_data.buf = (const uint8_t *) buffer;
259 	msg.data.header_or_data.len = strlen(buffer);
260 	if (fetch_file_send_callback(&msg, ctx))
261 		goto fetch_file_process_error_aborted;
262 
263 	msg.type = FETCH_FINISHED;
264 	fetch_file_send_callback(&msg, ctx);
265 
266 fetch_file_process_error_aborted:
267 	return;
268 }
269 
270 
271 /** Process object as a regular file */
fetch_file_process_plain(struct fetch_file_context * ctx,struct stat * fdstat)272 static void fetch_file_process_plain(struct fetch_file_context *ctx,
273 				     struct stat *fdstat)
274 {
275 #ifdef HAVE_MMAP
276 	fetch_msg msg;
277 	char *buf = NULL;
278 	size_t buf_size;
279 
280 	int fd; /**< The file descriptor of the object */
281 
282 	/* Check if we can just return not modified */
283 	if (ctx->file_etag != 0 && ctx->file_etag == fdstat->st_mtime) {
284 		fetch_set_http_code(ctx->fetchh, 304);
285 		msg.type = FETCH_NOTMODIFIED;
286 		fetch_file_send_callback(&msg, ctx);
287 		return;
288 	}
289 
290 	fd = open(ctx->path, O_RDONLY);
291 	if (fd < 0) {
292 		/* process errors as appropriate */
293 		fetch_file_process_error(ctx,
294 				fetch_file_errno_to_http_code(errno));
295 		return;
296 	}
297 
298 	/* set buffer size */
299 	buf_size = fdstat->st_size;
300 
301 	/* allocate the buffer storage */
302 	if (buf_size > 0) {
303 		buf = mmap(NULL, buf_size, PROT_READ, MAP_SHARED, fd, 0);
304 		if (buf == MAP_FAILED) {
305 			msg.type = FETCH_ERROR;
306 			msg.data.error = "Unable to map memory for file data buffer";
307 			fetch_file_send_callback(&msg, ctx);
308 			close(fd);
309 			return;
310 		}
311 	}
312 
313 	/* fetch is going to be successful */
314 	fetch_set_http_code(ctx->fetchh, 200);
315 
316 	/* Any callback can result in the fetch being aborted.
317 	 * Therefore, we _must_ check for this after _every_ call to
318 	 * fetch_file_send_callback().
319 	 */
320 
321 	/* content type */
322 	if (fetch_file_send_header(ctx, "Content-Type: %s",
323 				   guit->fetch->filetype(ctx->path))) {
324 		goto fetch_file_process_aborted;
325 	}
326 
327 	/* content length */
328 	if (fetch_file_send_header(ctx, "Content-Length: %" PRIsizet,
329 				   fdstat->st_size)) {
330 		goto fetch_file_process_aborted;
331 	}
332 
333 	/* create etag */
334 	if (fetch_file_send_header(ctx, "ETag: \"%10" PRId64 "\"",
335 				   (int64_t) fdstat->st_mtime)) {
336 		goto fetch_file_process_aborted;
337 	}
338 
339 	msg.type = FETCH_DATA;
340 	msg.data.header_or_data.buf = (const uint8_t *) buf;
341 	msg.data.header_or_data.len = buf_size;
342 	fetch_file_send_callback(&msg, ctx);
343 
344 	if (ctx->aborted == false) {
345 		msg.type = FETCH_FINISHED;
346 		fetch_file_send_callback(&msg, ctx);
347 	}
348 
349 fetch_file_process_aborted:
350 
351 	if (buf != NULL)
352 		munmap(buf, buf_size);
353 	close(fd);
354 #else
355 	fetch_msg msg;
356 	char *buf;
357 	size_t buf_size;
358 
359 	ssize_t tot_read = 0;
360 	ssize_t res;
361 
362 	FILE *infile;
363 
364 	/* Check if we can just return not modified */
365 	if (ctx->file_etag != 0 && ctx->file_etag == fdstat->st_mtime) {
366 		fetch_set_http_code(ctx->fetchh, 304);
367 		msg.type = FETCH_NOTMODIFIED;
368 		fetch_file_send_callback(&msg, ctx);
369 		return;
370 	}
371 
372 	infile = fopen(ctx->path, "rb");
373 	if (infile == NULL) {
374 		/* process errors as appropriate */
375 		fetch_file_process_error(ctx,
376 				fetch_file_errno_to_http_code(errno));
377 		return;
378 	}
379 
380 	/* set buffer size */
381 	buf_size = fdstat->st_size;
382 	if (buf_size > FETCH_FILE_MAX_BUF_SIZE)
383 		buf_size = FETCH_FILE_MAX_BUF_SIZE;
384 
385 	/* allocate the buffer storage */
386 	buf = malloc(buf_size);
387 	if (buf == NULL) {
388 		msg.type = FETCH_ERROR;
389 		msg.data.error =
390 			"Unable to allocate memory for file data buffer";
391 		fetch_file_send_callback(&msg, ctx);
392 		fclose(infile);
393 		return;
394 	}
395 
396 	/* fetch is going to be successful */
397 	fetch_set_http_code(ctx->fetchh, 200);
398 
399 	/* Any callback can result in the fetch being aborted.
400 	 * Therefore, we _must_ check for this after _every_ call to
401 	 * fetch_file_send_callback().
402 	 */
403 
404 	/* content type */
405 	if (fetch_file_send_header(ctx, "Content-Type: %s",
406 				   guit->fetch->filetype(ctx->path))) {
407 		goto fetch_file_process_aborted;
408 	}
409 
410 	/* content length */
411 	if (fetch_file_send_header(ctx, "Content-Length: %" PRIsizet,
412 				   fdstat->st_size)) {
413 		goto fetch_file_process_aborted;
414 	}
415 
416 	/* create etag */
417 	if (fetch_file_send_header(ctx, "ETag: \"%10" PRId64 "\"",
418 				   (int64_t) fdstat->st_mtime)) {
419 		goto fetch_file_process_aborted;
420 	}
421 
422 	/* main data loop */
423 	while (tot_read < fdstat->st_size) {
424 		res = fread(buf, 1, buf_size, infile);
425 		if (res == 0) {
426 			if (feof(infile)) {
427 				msg.type = FETCH_ERROR;
428 				msg.data.error = "Unexpected EOF reading file";
429 				fetch_file_send_callback(&msg, ctx);
430 				goto fetch_file_process_aborted;
431 			} else {
432 				msg.type = FETCH_ERROR;
433 				msg.data.error = "Error reading file";
434 				fetch_file_send_callback(&msg, ctx);
435 				goto fetch_file_process_aborted;
436 			}
437 		}
438 		tot_read += res;
439 
440 		msg.type = FETCH_DATA;
441 		msg.data.header_or_data.buf = (const uint8_t *) buf;
442 		msg.data.header_or_data.len = res;
443 		if (fetch_file_send_callback(&msg, ctx))
444 			break;
445 	}
446 
447 	if (ctx->aborted == false) {
448 		msg.type = FETCH_FINISHED;
449 		fetch_file_send_callback(&msg, ctx);
450 	}
451 
452 fetch_file_process_aborted:
453 
454 	fclose(infile);
455 	free(buf);
456 #endif
457 	return;
458 }
459 
gen_nice_title(char * path)460 static char *gen_nice_title(char *path)
461 {
462 	char *nice_path, *cnv, *tmp;
463 	char *title;
464 	int title_length;
465 
466 	/* Convert path for display */
467 	nice_path = malloc(strlen(path) * SLEN("&amp;") + 1);
468 	if (nice_path == NULL) {
469 		return NULL;
470 	}
471 
472 	/* Escape special HTML characters */
473 	for (cnv = nice_path, tmp = path; *tmp != '\0'; tmp++) {
474 		if (*tmp == '<') {
475 			*cnv++ = '&';
476 			*cnv++ = 'l';
477 			*cnv++ = 't';
478 			*cnv++ = ';';
479 		} else if (*tmp == '>') {
480 			*cnv++ = '&';
481 			*cnv++ = 'g';
482 			*cnv++ = 't';
483 			*cnv++ = ';';
484 		} else if (*tmp == '&') {
485 			*cnv++ = '&';
486 			*cnv++ = 'a';
487 			*cnv++ = 'm';
488 			*cnv++ = 'p';
489 			*cnv++ = ';';
490 		} else {
491 			*cnv++ = *tmp;
492 		}
493 	}
494 	*cnv = '\0';
495 
496 	/* Construct a localised title string */
497 	title_length = (cnv - nice_path) + strlen(messages_get("FileIndex"));
498 	title = malloc(title_length + 1);
499 
500 	if (title == NULL) {
501 		free(nice_path);
502 		return NULL;
503 	}
504 
505 	/* Set title to localised "Index of <nice_path>" */
506 	snprintf(title, title_length, messages_get("FileIndex"), nice_path);
507 
508 	free(nice_path);
509 
510 	return title;
511 }
512 
513 /**
514  * Generate an output row of the directory listing.
515  *
516  * \param ctx The file fetching context.
517  * \param ent current directory entry.
518  * \param even is the row an even row.
519  * \param buffer The output buffer.
520  * \param buffer_len The space available in the output buffer.
521  * \return NSERROR_OK or error code on faliure.
522  */
523 static nserror
process_dir_ent(struct fetch_file_context * ctx,struct dirent * ent,bool even,char * buffer,size_t buffer_len)524 process_dir_ent(struct fetch_file_context *ctx,
525 		 struct dirent *ent,
526 		 bool even,
527 		 char *buffer,
528 		 size_t buffer_len)
529 {
530 	nserror ret;
531 	char *urlpath = NULL; /* buffer for leaf entry path */
532 	struct stat ent_stat; /* stat result of leaf entry */
533 	char datebuf[64]; /* buffer for date text */
534 	char timebuf[64]; /* buffer for time text */
535 	nsurl *url;
536 
537 	/* skip hidden files */
538 	if (ent->d_name[0] == '.') {
539 		return NSERROR_BAD_PARAMETER;
540 	}
541 
542 	ret = netsurf_mkpath(&urlpath, NULL, 2, ctx->path, ent->d_name);
543 	if (ret != NSERROR_OK) {
544 		return ret;
545 	}
546 
547 	if (stat(urlpath, &ent_stat) != 0) {
548 		ent_stat.st_mode = 0;
549 		datebuf[0] = 0;
550 		timebuf[0] = 0;
551 	} else {
552 		/* Get date in output format. a (day of week) and b
553 		 * (month) are both affected by the locale
554 		 */
555 		if (strftime((char *)&datebuf, sizeof datebuf, "%a %d %b %Y",
556 			     localtime(&ent_stat.st_mtime)) == 0) {
557 			datebuf[0] = '-';
558 			datebuf[1] = 0;
559 		}
560 
561 		/* Get time in output format */
562 		if (strftime((char *)&timebuf, sizeof timebuf, "%H:%M",
563 			     localtime(&ent_stat.st_mtime)) == 0) {
564 			timebuf[0] = '-';
565 			timebuf[1] = 0;
566 		}
567 	}
568 
569 	ret = guit->file->path_to_nsurl(urlpath, &url);
570 	if (ret != NSERROR_OK) {
571 		free(urlpath);
572 		return ret;
573 	}
574 
575 	if (S_ISREG(ent_stat.st_mode)) {
576 		/* regular file */
577 		dirlist_generate_row(even,
578 				     false,
579 				     url,
580 				     ent->d_name,
581 				     guit->fetch->filetype(urlpath),
582 				     ent_stat.st_size,
583 				     datebuf, timebuf,
584 				     buffer, buffer_len);
585 	} else if (S_ISDIR(ent_stat.st_mode)) {
586 		/* directory */
587 		dirlist_generate_row(even,
588 				     true,
589 				     url,
590 				     ent->d_name,
591 				     messages_get("FileDirectory"),
592 				     -1,
593 				     datebuf, timebuf,
594 				     buffer, buffer_len);
595 	} else {
596 		/* something else */
597 		dirlist_generate_row(even,
598 				     false,
599 				     url,
600 				     ent->d_name,
601 				     "",
602 				     -1,
603 				     datebuf, timebuf,
604 				     buffer, buffer_len);
605 	}
606 
607 	nsurl_unref(url);
608 	free(urlpath);
609 
610 	return NSERROR_OK;
611 }
612 
613 /**
614  * Comparison function for sorting directories.
615  *
616  * Correctly orders non zero-padded numerical parts.
617  * ie. produces "file1, file2, file10" rather than "file1, file10, file2".
618  *
619  * \param d1 first directory entry
620  * \param d2 second directory entry
621  */
dir_sort_alpha(const struct dirent ** d1,const struct dirent ** d2)622 static int dir_sort_alpha(const struct dirent **d1, const struct dirent **d2)
623 {
624 	const char *s1 = (*d1)->d_name;
625 	const char *s2 = (*d2)->d_name;
626 
627 	while (*s1 != '\0' && *s2 != '\0') {
628 		if ((*s1 >= '0' && *s1 <= '9') &&
629 				(*s2 >= '0' && *s2 <= '9')) {
630 			int n1 = 0,  n2 = 0;
631 			while (*s1 >= '0' && *s1 <= '9') {
632 				n1 = n1 * 10 + (*s1) - '0';
633 				s1++;
634 			}
635 			while (*s2 >= '0' && *s2 <= '9') {
636 				n2 = n2 * 10 + (*s2) - '0';
637 				s2++;
638 			}
639 			if (n1 != n2) {
640 				return n1 - n2;
641 			}
642 			if (*s1 == '\0' || *s2 == '\0')
643 				break;
644 		}
645 		if (tolower(*s1) != tolower(*s2))
646 			break;
647 
648 		s1++;
649 		s2++;
650 	}
651 
652 	return tolower(*s1) - tolower(*s2);
653 }
654 
fetch_file_process_dir(struct fetch_file_context * ctx,struct stat * fdstat)655 static void fetch_file_process_dir(struct fetch_file_context *ctx,
656 				   struct stat *fdstat)
657 {
658 	fetch_msg msg;
659 	char buffer[1024]; /* Output buffer */
660 	bool even = false; /* formatting flag */
661 	char *title; /* pretty printed title */
662 	nserror err; /* result from url routines */
663 	nsurl *up; /* url of parent */
664 
665 	struct dirent **listing = NULL; /* directory entry listing */
666 	int i; /* directory entry index */
667 	int n; /* number of directory entries */
668 
669 	n = scandir(ctx->path, &listing, 0, dir_sort_alpha);
670 	if (n < 0) {
671 		fetch_file_process_error(ctx,
672 			fetch_file_errno_to_http_code(errno));
673 		return;
674 	}
675 
676 	/* fetch is going to be successful */
677 	fetch_set_http_code(ctx->fetchh, 200);
678 
679 	/* force no-cache */
680 	if (fetch_file_send_header(ctx, "Cache-Control: no-cache"))
681 		goto fetch_file_process_dir_aborted;
682 
683 	/* content type */
684 	if (fetch_file_send_header(ctx, "Content-Type: text/html"))
685 		goto fetch_file_process_dir_aborted;
686 
687 	msg.type = FETCH_DATA;
688 	msg.data.header_or_data.buf = (const uint8_t *) buffer;
689 
690 	/* directory listing top */
691 	dirlist_generate_top(buffer, sizeof buffer);
692 	msg.data.header_or_data.len = strlen(buffer);
693 	if (fetch_file_send_callback(&msg, ctx))
694 		goto fetch_file_process_dir_aborted;
695 
696 	/* directory listing title */
697 	title = gen_nice_title(ctx->path);
698 	dirlist_generate_title(title, buffer, sizeof buffer);
699 	free(title);
700 	msg.data.header_or_data.len = strlen(buffer);
701 	if (fetch_file_send_callback(&msg, ctx))
702 		goto fetch_file_process_dir_aborted;
703 
704 	/* Print parent directory link */
705 	err = nsurl_parent(ctx->url, &up);
706 	if (err == NSERROR_OK) {
707 		if (nsurl_compare(ctx->url, up, NSURL_COMPLETE) == false) {
708 			/* different URL; have parent */
709 			dirlist_generate_parent_link(nsurl_access(up),
710 					buffer, sizeof buffer);
711 
712 			msg.data.header_or_data.len = strlen(buffer);
713 			fetch_file_send_callback(&msg, ctx);
714 		}
715 		nsurl_unref(up);
716 
717 		if (ctx->aborted)
718 			goto fetch_file_process_dir_aborted;
719 
720 	}
721 
722 	/* directory list headings */
723 	dirlist_generate_headings(buffer, sizeof buffer);
724 	msg.data.header_or_data.len = strlen(buffer);
725 	if (fetch_file_send_callback(&msg, ctx))
726 		goto fetch_file_process_dir_aborted;
727 
728 	for (i = 0; i < n; i++) {
729 
730 		err = process_dir_ent(ctx, listing[i], even, buffer,
731 				       sizeof(buffer));
732 
733 		if (err == NSERROR_OK) {
734 			msg.data.header_or_data.len = strlen(buffer);
735 			if (fetch_file_send_callback(&msg, ctx))
736 				goto fetch_file_process_dir_aborted;
737 
738 			even = !even;
739 		}
740 	}
741 
742 	/* directory listing bottom */
743 	dirlist_generate_bottom(buffer, sizeof buffer);
744 	msg.data.header_or_data.len = strlen(buffer);
745 	if (fetch_file_send_callback(&msg, ctx))
746 		goto fetch_file_process_dir_aborted;
747 
748 	msg.type = FETCH_FINISHED;
749 	fetch_file_send_callback(&msg, ctx);
750 
751 fetch_file_process_dir_aborted:
752 
753 	if (listing != NULL) {
754 		for (i = 0; i < n; i++) {
755 			free(listing[i]);
756 		}
757 		free(listing);
758 	}
759 }
760 
761 
762 /* process a file fetch */
fetch_file_process(struct fetch_file_context * ctx)763 static void fetch_file_process(struct fetch_file_context *ctx)
764 {
765 	struct stat fdstat; /**< The objects stat */
766 
767 	if (stat(ctx->path, &fdstat) != 0) {
768 		/* process errors as appropriate */
769 		fetch_file_process_error(ctx,
770 				fetch_file_errno_to_http_code(errno));
771 		return;
772 	}
773 
774 	if (S_ISDIR(fdstat.st_mode)) {
775 		/* directory listing */
776 		fetch_file_process_dir(ctx, &fdstat);
777 		return;
778 	} else if (S_ISREG(fdstat.st_mode)) {
779 		/* regular file */
780 		fetch_file_process_plain(ctx, &fdstat);
781 		return;
782 	} else {
783 		/* unhandled type of file */
784 		fetch_file_process_error(ctx, 501);
785 	}
786 
787 	return;
788 }
789 
790 /** callback to poll for additional file fetch contents */
fetch_file_poll(lwc_string * scheme)791 static void fetch_file_poll(lwc_string *scheme)
792 {
793 	struct fetch_file_context *c, *save_ring = NULL;
794 
795 	while (ring != NULL) {
796 		/* Take the first entry from the ring */
797 		c = ring;
798 		RING_REMOVE(ring, c);
799 
800 		/* Ignore fetches that have been flagged as locked.
801 		 * This allows safe re-entrant calls to this function.
802 		 * Re-entrancy can occur if, as a result of a callback,
803 		 * the interested party causes fetch_poll() to be called
804 		 * again.
805 		 */
806 		if (c->locked == true) {
807 			RING_INSERT(save_ring, c);
808 			continue;
809 		}
810 
811 		/* Only process non-aborted fetches */
812 		if (c->aborted == false) {
813 			/* file fetches can be processed in one go */
814 			fetch_file_process(c);
815 		}
816 
817 		/* And now finish */
818 		fetch_remove_from_queues(c->fetchh);
819 		fetch_free(c->fetchh);
820 
821 	}
822 
823 	/* Finally, if we saved any fetches which were locked, put them back
824 	 * into the ring for next time
825 	 */
826 	ring = save_ring;
827 }
828 
fetch_file_register(void)829 nserror fetch_file_register(void)
830 {
831 	lwc_string *scheme = lwc_string_ref(corestring_lwc_file);
832 	const struct fetcher_operation_table fetcher_ops = {
833 		.initialise = fetch_file_initialise,
834 		.acceptable = fetch_file_can_fetch,
835 		.setup = fetch_file_setup,
836 		.start = fetch_file_start,
837 		.abort = fetch_file_abort,
838 		.free = fetch_file_free,
839 		.poll = fetch_file_poll,
840 		.finalise = fetch_file_finalise
841 	};
842 
843 	return fetcher_add(scheme, &fetcher_ops);
844 }
845