Subversion Repositories Kolibri OS

Rev

Rev 4364 | Go to most recent revision | Blame | Last modification | View Log | RSS feed

  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. /* file: URL handling. Based on the data fetcher by Rob Kendrick */
  20.  
  21. #include <sys/types.h>
  22. #include <sys/stat.h>
  23. #include <fcntl.h>
  24. #include <unistd.h>
  25. #include <assert.h>
  26. #include <errno.h>
  27. #include <stdbool.h>
  28. #include <inttypes.h>
  29. #include <string.h>
  30. #include <strings.h>
  31. #include <time.h>
  32. #include <stdio.h>
  33. #include <dirent.h>
  34. #include <limits.h>
  35. #include <stdarg.h>
  36.  
  37. #include "utils/config.h"
  38.  
  39. #ifdef HAVE_MMAP
  40. #include <sys/mman.h>
  41. #endif
  42.  
  43. #include <libwapcaplet/libwapcaplet.h>
  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() */
  72.  
  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, "<html><head><title>%s</title></head>"
  236.                  "<body><h1>%s</h1>"
  237.                         "<p>Error %d while fetching file %s</p></body></html>",
  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("&amp;") + 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 <nice_path>" */
  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. }
  744.