Subversion Repositories Kolibri OS

Rev

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