Browse Source

Added directory listing support to the PROPFIND method

Sergey Lyubka 14 years ago
parent
commit
ba714de066
1 changed files with 114 additions and 70 deletions
  1. 114 70
      mongoose.c

+ 114 - 70
mongoose.c

@@ -2393,65 +2393,87 @@ static int WINCDECL compare_dir_entries(const void *p1, const void *p2) {
   return query_string[1] == 'd' ? -cmp_result : cmp_result;
 }
 
-static void handle_directory_request(struct mg_connection *conn,
-                                     const char *dir) {
+static int scan_directory(struct mg_connection *conn, const char *dir,
+                          void *data, void (*cb)(struct de *, void *)) {
+  char path[PATH_MAX];
   struct dirent *dp;
   DIR *dirp;
-  struct de *entries = NULL;
-  char path[PATH_MAX];
-  int i, sort_direction, num_entries = 0, arr_size = 128;
+  struct de de;
 
   if ((dirp = opendir(dir)) == NULL) {
-    send_http_error(conn, 500, "Cannot open directory",
-        "Error: opendir(%s): %s", dir, strerror(ERRNO));
-    return;
-  }
-
-  (void) mg_printf(conn, "%s",
-      "HTTP/1.1 200 OK\r\n"
-      "Connection: close\r\n"
-      "Content-Type: text/html; charset=utf-8\r\n\r\n");
+    return 0;
+  } else {
+    de.conn = conn;
+
+    while ((dp = readdir(dirp)) != NULL) {
+      // Do not show current dir and passwords file
+      if (!strcmp(dp->d_name, ".") ||
+          !strcmp(dp->d_name, "..") ||
+          !strcmp(dp->d_name, PASSWORDS_FILE_NAME))
+        continue;
+
+      mg_snprintf(conn, path, sizeof(path), "%s%c%s", dir, DIRSEP, dp->d_name);
+
+      // If we don't memset stat structure to zero, mtime will have
+      // garbage and strftime() will segfault later on in
+      // print_dir_entry(). memset is required only if mg_stat()
+      // fails. For more details, see
+      // http://code.google.com/p/mongoose/issues/detail?id=79
+      if (mg_stat(path, &de.st) != 0) {
+        memset(&de.st, 0, sizeof(de.st));
+      }
+      de.file_name = dp->d_name;
 
-  sort_direction = conn->request_info.query_string != NULL &&
-    conn->request_info.query_string[1] == 'd' ? 'a' : 'd';
+      cb(&de, data);
+    }
+    (void) closedir(dirp);
+  }
+  return 1;
+}
 
-  while ((dp = readdir(dirp)) != NULL) {
+struct dir_scan_data {
+  struct de *entries;
+  int num_entries;
+  int arr_size;
+};
 
-    // Do not show current dir and passwords file
-    if (!strcmp(dp->d_name, ".") ||
-        !strcmp(dp->d_name, "..") ||
-        !strcmp(dp->d_name, PASSWORDS_FILE_NAME))
-      continue;
+static void dir_scan_callback(struct de *de, void *data) {
+  struct dir_scan_data *dsd = (struct dir_scan_data *) data;
 
-    if (entries == NULL || num_entries >= arr_size) {
-      arr_size *= 2;
-      entries = (struct de *) realloc(entries,
-          arr_size * sizeof(entries[0]));
-    }
+  if (dsd->entries == NULL || dsd->num_entries >= dsd->arr_size) {
+    dsd->arr_size *= 2;
+    dsd->entries = (struct de *) realloc(dsd->entries, dsd->arr_size *
+                                         sizeof(dsd->entries[0]));
+  }
+  if (dsd->entries == NULL) {
+    // TODO(lsm): propagate an error to the caller
+    dsd->num_entries = 0;
+  } else {
+    dsd->entries[dsd->num_entries].file_name = mg_strdup(de->file_name);
+    dsd->entries[dsd->num_entries].st = de->st;
+    dsd->entries[dsd->num_entries].conn = de->conn;
+    dsd->num_entries++;
+  }
+}
 
-    if (entries == NULL) {
-      closedir(dirp);
-      send_http_error(conn, 500, "Cannot open directory",
-          "%s", "Error: cannot allocate memory");
-      return;
-    }
+static void handle_directory_request(struct mg_connection *conn,
+                                     const char *dir) {
+  int i, sort_direction;
+  struct dir_scan_data data = { NULL, 0, 128 };
 
-    mg_snprintf(conn, path, sizeof(path), "%s%c%s", dir, DIRSEP, dp->d_name);
+  if (!scan_directory(conn, dir, &data, dir_scan_callback)) {
+    send_http_error(conn, 500, "Cannot open directory",
+                    "Error: opendir(%s): %s", dir, strerror(ERRNO));
+    return;
+  }
 
-    // If we don't memset stat structure to zero, mtime will have
-    // garbage and strftime() will segfault later on in
-    // print_dir_entry(). memset is required only if mg_stat()
-    // fails. For more details, see
-    // http://code.google.com/p/mongoose/issues/detail?id=79
-    if (mg_stat(path, &entries[num_entries].st) != 0) {
-      memset(&entries[num_entries].st, 0, sizeof(entries[num_entries].st));
-    }
+  sort_direction = conn->request_info.query_string != NULL &&
+    conn->request_info.query_string[1] == 'd' ? 'a' : 'd';
 
-    entries[num_entries].conn = conn;
-    entries[num_entries].file_name = mg_strdup(dp->d_name);
-    num_entries++;
-  }
-  (void) closedir(dirp);
+  mg_printf(conn, "%s",
+            "HTTP/1.1 200 OK\r\n"
+            "Connection: close\r\n"
+            "Content-Type: text/html; charset=utf-8\r\n\r\n");
 
   conn->num_bytes_sent += mg_printf(conn,
       "<html><head><title>Index of %s</title>"
@@ -2471,12 +2493,13 @@ static void handle_directory_request(struct mg_connection *conn,
       conn->request_info.uri, "..", "Parent directory", "-", "-");
 
   // Sort and print directory entries
-  qsort(entries, (size_t)num_entries, sizeof(entries[0]), compare_dir_entries);
-  for (i = 0; i < num_entries; i++) {
-    print_dir_entry(&entries[i]);
-    free(entries[i].file_name);
+  qsort(data.entries, (size_t) data.num_entries, sizeof(data.entries[0]),
+        compare_dir_entries);
+  for (i = 0; i < data.num_entries; i++) {
+    print_dir_entry(&data.entries[i]);
+    free(data.entries[i].file_name);
   }
-  free(entries);
+  free(data.entries);
 
   conn->num_bytes_sent += mg_printf(conn, "%s", "</table></body></html>");
   conn->request_info.status_code = 200;
@@ -2511,10 +2534,14 @@ static int parse_range_header(const char *header, int64_t *a, int64_t *b) {
   return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b);
 }
 
+static void gmt_time_string(char *buf, size_t buf_len, time_t *t) {
+  strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", gmtime(t));
+}
+
 static void handle_file_request(struct mg_connection *conn, const char *path,
                                 struct mgstat *stp) {
   char date[64], lm[64], etag[64], range[64];
-  const char *fmt = "%a, %d %b %Y %H:%M:%S %Z", *msg = "OK", *hdr;
+  const char *msg = "OK", *hdr;
   time_t curtime = time(NULL);
   int64_t cl, r1, r2;
   struct vec mime_vec;
@@ -2550,8 +2577,8 @@ static void handle_file_request(struct mg_connection *conn, const char *path,
 
   // Prepare Etag, Date, Last-Modified headers. Must be in UTC, according to
   // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3
-  (void) strftime(date, sizeof(date), fmt, gmtime(&curtime));
-  (void) strftime(lm, sizeof(lm), fmt, gmtime(&stp->mtime));
+  gmt_time_string(date, sizeof(date), &curtime);
+  gmt_time_string(lm, sizeof(lm), &stp->mtime);
   (void) mg_snprintf(conn, etag, sizeof(etag), "%lx.%lx",
       (unsigned long) stp->mtime, (unsigned long) stp->size);
 
@@ -3247,11 +3274,10 @@ static void send_options(struct mg_connection *conn) {
 }
 
 // Writes PROPFIND properties for a collection element
-static void print_props(struct mg_connection *conn, const char* uri, struct mgstat* st) {
-  char mod[64];
-
-  (void) strftime(mod, sizeof(mod), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&st->mtime));
-
+static void print_props(struct mg_connection *conn, const char* uri,
+                        struct mgstat* st) {
+  char mtime[64];
+  gmt_time_string(mtime, sizeof(mtime), &st->mtime);
   conn->num_bytes_sent += mg_printf(conn,
       "<d:response>"
        "<d:href>%s</d:href>"
@@ -3263,27 +3289,45 @@ static void print_props(struct mg_connection *conn, const char* uri, struct mgst
         "</d:prop>"
         "<d:status>HTTP/1.1 200 OK</d:status>"
        "</d:propstat>"
-      "</d:response>",
+      "</d:response>\n",
       uri,
       st->is_directory ? "<d:collection/>" : "",
       st->size,
-      mod);
+      mtime);
 }
 
-static void handle_propfind(struct mg_connection *conn, const char* path, struct mgstat* st) {
-  conn->request_info.status_code = 200;
-  (void) mg_printf(conn,
-       "HTTP/1.1 207 Multi-Status\r\n"
-       "Connection: close\r\n"
-       "Content-Type: text/xml; charset=utf-8\r\n\r\n");
+static void print_dav_dir_entry(struct de *de, void *data) {
+  char href[PATH_MAX];
+  struct mg_connection *conn = (struct mg_connection *) data;
+  mg_snprintf(conn, href, sizeof(href), "%s%s",
+              conn->request_info.uri, de->file_name);
+  print_props(conn, href, &de->st);
+}
+
+static void handle_propfind(struct mg_connection *conn, const char* path,
+                            struct mgstat* st) {
+  const char *depth = mg_get_header(conn, "Depth");
+
+  conn->request_info.status_code = 207;
+  mg_printf(conn, "HTTP/1.1 207 Multi-Status\r\n"
+            "Connection: close\r\n"
+            "Content-Type: text/xml; charset=utf-8\r\n\r\n");
 
   conn->num_bytes_sent += mg_printf(conn,
       "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
-      "<d:multistatus xmlns:d='DAV:'>");
+      "<d:multistatus xmlns:d='DAV:'>\n");
 
+  // Print properties for the requested resource itself
   print_props(conn, conn->request_info.uri, st);
 
-  conn->num_bytes_sent += mg_printf(conn, "</d:multistatus>");
+  // If it is a directory, print directory entries too if Depth is not 0
+  if (st->is_directory &&
+      !mg_strcasecmp(conn->ctx->config[ENABLE_DIRECTORY_LISTING], "yes") &&
+      (depth == NULL || strcmp(depth, "0") != 0)) {
+    scan_directory(conn, path, conn, &print_dav_dir_entry);
+  }
+
+  conn->num_bytes_sent += mg_printf(conn, "%s\n", "</d:multistatus>");
 }
 
 // This is the heart of the Mongoose's logic.