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("&") + 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