Skip to content
Snippets Groups Projects
mongoose.c 111 KiB
Newer Older
      *dst = *src;
    } else if (dst + 2 < end) {
      dst[0] = '%';
      dst[1] = hex[(* (const unsigned char *) src) >> 4];
      dst[2] = hex[(* (const unsigned char *) src) & 0xf];
      dst += 2;
    }
#endif  // !NO_DIRECTORY_LISTING || !NO_DAV
#ifndef NO_DIRECTORY_LISTING
static int mg_write_chunked(struct connection *conn, const char *buf, int len) {
  char chunk_size[50];
  int n = mg_snprintf(chunk_size, sizeof(chunk_size), "%X\r\n", len);
  n = spool(&conn->remote_iobuf, chunk_size, n);
  n += spool(&conn->remote_iobuf, buf, len);
  n += spool(&conn->remote_iobuf, "\r\n", 2);
static void print_dir_entry(const struct dir_entry *de) {
  char size[64], mod[64], href[MAX_PATH_SIZE * 3], chunk[MAX_PATH_SIZE * 4];
  int64_t fsize = de->st.st_size;
  int is_dir = S_ISDIR(de->st.st_mode), n;
  const char *slash = is_dir ? "/" : "";
  if (is_dir) {
    mg_snprintf(size, sizeof(size), "%s", "[DIRECTORY]");
  } else {
     // We use (signed) cast below because MSVC 6 compiler cannot
     // convert unsigned __int64 to double.
    if (fsize < 1024) {
      mg_snprintf(size, sizeof(size), "%d", (int) fsize);
    } else if (fsize < 0x100000) {
      mg_snprintf(size, sizeof(size), "%.1fk", (double) fsize / 1024.0);
    } else if (fsize < 0x40000000) {
      mg_snprintf(size, sizeof(size), "%.1fM", (double) fsize / 1048576);
    } else {
      mg_snprintf(size, sizeof(size), "%.1fG", (double) fsize / 1073741824);
  strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&de->st.st_mtime));
  mg_url_encode(de->file_name, href, sizeof(href));
  n = mg_snprintf(chunk, sizeof(chunk),
                  "<tr><td><a href=\"%s%s%s\">%s%s</a></td>"
                  "<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
                  de->conn->mg_conn.uri, href, slash, de->file_name, slash,
                  mod, size);
  mg_write_chunked((struct connection *) de->conn, chunk, n);
}
// Sort directory entries by size, or name, or modification time.
// On windows, __cdecl specification is needed in case if project is built
// with __stdcall convention. qsort always requires __cdels callback.
static int __cdecl compare_dir_entries(const void *p1, const void *p2) {
  const struct dir_entry *a = (const struct dir_entry *) p1,
        *b = (const struct dir_entry *) p2;
  const char *qs = a->conn->mg_conn.query_string ?
    a->conn->mg_conn.query_string : "na";
  int cmp_result = 0;

  if (S_ISDIR(a->st.st_mode) && !S_ISDIR(b->st.st_mode)) {
    return -1;  // Always put directories on top
  } else if (!S_ISDIR(a->st.st_mode) && S_ISDIR(b->st.st_mode)) {
    return 1;   // Always put directories on top
  } else if (*qs == 'n') {
    cmp_result = strcmp(a->file_name, b->file_name);
  } else if (*qs == 's') {
    cmp_result = a->st.st_size == b->st.st_size ? 0 :
      a->st.st_size > b->st.st_size ? 1 : -1;
  } else if (*qs == 'd') {
    cmp_result = a->st.st_mtime == b->st.st_mtime ? 0 :
      a->st.st_mtime > b->st.st_mtime ? 1 : -1;

  return qs[1] == 'd' ? -cmp_result : cmp_result;
static void send_directory_listing(struct connection *conn, const char *dir) {
  char buf[2000];
  struct dir_entry *arr = NULL;
  int i, num_entries, sort_direction = conn->mg_conn.query_string != NULL &&
    conn->mg_conn.query_string[1] == 'd' ? 'a' : 'd';

  conn->mg_conn.status_code = 200;
  mg_snprintf(buf, sizeof(buf), "%s",
              "HTTP/1.1 200 OK\r\n"
              "Transfer-Encoding: Chunked\r\n"
              "Content-Type: text/html; charset=utf-8\r\n\r\n");
  spool(&conn->remote_iobuf, buf, strlen(buf));

  mg_snprintf(buf, sizeof(buf),
              "<html><head><title>Index of %s</title>"
              "<style>th {text-align: left;}</style></head>"
              "<body><h1>Index of %s</h1><pre><table cellpadding=\"0\">"
              "<tr><th><a href=\"?n%c\">Name</a></th>"
              "<th><a href=\"?d%c\">Modified</a></th>"
              "<th><a href=\"?s%c\">Size</a></th></tr>"
              "<tr><td colspan=\"3\"><hr></td></tr>",
              conn->mg_conn.uri, conn->mg_conn.uri,
              sort_direction, sort_direction, sort_direction);
  mg_write_chunked(conn, buf, strlen(buf));

  num_entries = scan_directory(conn, dir, &arr);
  qsort(arr, num_entries, sizeof(arr[0]), compare_dir_entries);
  for (i = 0; i < num_entries; i++) {
    print_dir_entry(&arr[i]);
    free(arr[i].file_name);
  }
  free(arr);

  mg_write_chunked(conn, "", 0);  // Write final zero-length chunk
  close_local_endpoint(conn);
}
#endif  // NO_DIRECTORY_LISTING

#ifndef NO_DAV
static void print_props(struct connection *conn, const char *uri,
                        file_stat_t *stp) {
  char mtime[64], buf[MAX_PATH_SIZE + 200];

  gmt_time_string(mtime, sizeof(mtime), &stp->st_mtime);
  mg_snprintf(buf, sizeof(buf),
      "<d:response>"
       "<d:href>%s</d:href>"
       "<d:propstat>"
        "<d:prop>"
         "<d:resourcetype>%s</d:resourcetype>"
         "<d:getcontentlength>%" INT64_FMT "</d:getcontentlength>"
         "<d:getlastmodified>%s</d:getlastmodified>"
        "</d:prop>"
        "<d:status>HTTP/1.1 200 OK</d:status>"
       "</d:propstat>"
      "</d:response>\n",
      uri, S_ISDIR(stp->st_mode) ? "<d:collection/>" : "",
      (int64_t) stp->st_size, mtime);
  spool(&conn->remote_iobuf, buf, strlen(buf));
static void handle_propfind(struct connection *conn, const char *path,
                            file_stat_t *stp) {
  static const char header[] = "HTTP/1.1 207 Multi-Status\r\n"
    "Connection: close\r\n"
    "Content-Type: text/xml; charset=utf-8\r\n\r\n"
    "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
    "<d:multistatus xmlns:d='DAV:'>\n";
  static const char footer[] = "</d:multistatus>";
  const char *depth = mg_get_header(&conn->mg_conn, "Depth"),
        *list_dir = conn->server->config_options[ENABLE_DIRECTORY_LISTING];
  conn->mg_conn.status_code = 207;
  spool(&conn->remote_iobuf, header, sizeof(header) - 1);
  // Print properties for the requested resource itself
  print_props(conn, conn->mg_conn.uri, stp);
  // If it is a directory, print directory entries too if Depth is not 0
  if (S_ISDIR(stp->st_mode) && !mg_strcasecmp(list_dir, "yes") &&
      (depth == NULL || strcmp(depth, "0") != 0)) {
    struct dir_entry *arr = NULL;
    int i, num_entries = scan_directory(conn, path, &arr);
    for (i = 0; i < num_entries; i++) {
      char buf[MAX_PATH_SIZE], buf2[sizeof(buf) * 3];
      struct dir_entry *de = &arr[i];
      mg_snprintf(buf, sizeof(buf), "%s%s", de->conn->mg_conn.uri,
                  de->file_name);
      mg_url_encode(buf, buf2, sizeof(buf2) - 1);
      print_props(conn, buf, &de->st);
  spool(&conn->remote_iobuf, footer, sizeof(footer) - 1);
  close_local_endpoint(conn);
static void handle_mkcol(struct connection *conn, const char *path) {
  int status_code = 500;

  if (conn->mg_conn.content_len > 0) {
    status_code = 415;
  } else if (!mkdir(path, 0755)) {
    status_code = 201;
  } else if (errno == EEXIST) {
    status_code = 405;
  } else if (errno == EACCES) {
    status_code = 403;
  } else if (errno == ENOENT) {
    status_code = 409;
  }
  send_http_error(conn, status_code);
static int remove_directory(const char *dir) {
  char path[MAX_PATH_SIZE];
  struct dirent *dp;
  file_stat_t st;
  DIR *dirp;
  if ((dirp = opendir(dir)) == NULL) return 0;
  while ((dp = readdir(dirp)) != NULL) {
    if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue;
    mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
    stat(path, &st);
    if (S_ISDIR(st.st_mode)) {
      remove_directory(path);
    } else {
      remove(path);
    }
  closedir(dirp);
  rmdir(dir);
  return 1;
}

static void handle_delete(struct connection *conn, const char *path) {
  file_stat_t st;

  if (!stat(path, &st)) {
    send_http_error(conn, 404);
  } else if (S_ISDIR(st.st_mode)) {
    remove_directory(path);
    send_http_error(conn, 204);
  } else if (!remove(path) == 0) {
    send_http_error(conn, 204);
  } else {
    send_http_error(conn, 423);
// For a given PUT path, create all intermediate subdirectories
// for given path. Return 0 if the path itself is a directory,
// or -1 on error, 1 if OK.
static int put_dir(const char *path) {
  char buf[MAX_PATH_SIZE];
  const char *s, *p;
  file_stat_t st;
  int len, res = 1;
  for (s = p = path + 2; (p = strchr(s, '/')) != NULL; s = ++p) {
    len = p - path;
    if (len >= (int) sizeof(buf)) {
      res = -1;
      break;
    }
    mg_strlcpy(buf, path, len);
    memcpy(buf, path, len);

    // Try to create intermediate directory
    if (stat(buf, &st) != 0 && mkdir(buf, 0755) != 0) {
      res = -1;
      break;
    }

    // Is path itself a directory?
    if (p[1] == '\0') {
      res = 0;
    }
static void handle_put(struct connection *conn, const char *path) {
  file_stat_t st;
  char buf[100];
  const char *range, *cl_hdr = mg_get_header(&conn->mg_conn, "Content-Length");
Sergey Lyubka's avatar
Sergey Lyubka committed
  int64_t r1, r2;
  int rc;

  conn->mg_conn.status_code = !stat(path, &st) ? 200 : 201;
Sergey Lyubka's avatar
Sergey Lyubka committed
  if ((rc = put_dir(path)) == 0) {
Sergey Lyubka's avatar
Sergey Lyubka committed
    mg_fmt(conn, "HTTP/1.1 %d OK\r\n\r\n", conn->mg_conn.status_code);
Sergey Lyubka's avatar
Sergey Lyubka committed
  } else if (rc == -1) {
    send_http_error(conn, 500);
  } else if (cl_hdr == NULL) {
    send_http_error(conn, 411);
  } else if ((conn->endpoint.fd =
              open(path, O_RDWR | O_CREAT | O_TRUNC, 0644)) < 0) {
    send_http_error(conn, 500);
Sergey Lyubka's avatar
Sergey Lyubka committed
  } else {
    DBG(("PUT [%s] %d", path, conn->local_iobuf.len));
    conn->endpoint_type = EP_PUT;
    set_close_on_exec(conn->endpoint.fd);
    range = mg_get_header(&conn->mg_conn, "Content-Range");
    conn->cl = to64(cl_hdr);
Sergey Lyubka's avatar
Sergey Lyubka committed
    r1 = r2 = 0;
    if (range != NULL && parse_range_header(range, &r1, &r2) > 0) {
      conn->mg_conn.status_code = 206;
      lseek(conn->endpoint.fd, r1, SEEK_SET);
      conn->cl = r2 > r1 ? r2 - r1 + 1: conn->cl - r1;
    mg_snprintf(buf, sizeof(buf), "HTTP/1.1 %d OK\r\nContent-Length: 0\r\n\r\n",
                conn->mg_conn.status_code);
    spool(&conn->remote_iobuf, buf, strlen(buf));
static void forward_put_data(struct connection *conn) {
  struct iobuf *io = &conn->local_iobuf;
  int n = write(conn->endpoint.fd, io->buf, io->len);
  if (n > 0) {
    memmove(io->buf, io->buf + n, io->len - n);
    io->len -= n;
    conn->cl -= n;
    if (conn->cl <= 0) {
      close_local_endpoint(conn);
}
#endif //  NO_DAV
static void send_options(struct connection *conn) {
  static const char reply[] = "HTTP/1.1 200 OK\r\nAllow: GET, POST, HEAD, "
    "CONNECT, PUT, DELETE, OPTIONS, PROPFIND, MKCOL\r\nDAV: 1\r\n\r\n";
  spool(&conn->remote_iobuf, reply, sizeof(reply) - 1);
  conn->flags |= CONN_SPOOL_DONE;
#ifndef NO_AUTH
static void send_authorization_request(struct connection *conn) {
  conn->mg_conn.status_code = 401;
Sergey Lyubka's avatar
Sergey Lyubka committed
  mg_fmt(conn,
         "HTTP/1.1 401 Unauthorized\r\n"
         "WWW-Authenticate: Digest qop=\"auth\", "
         "realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
         conn->server->config_options[AUTH_DOMAIN],
         (unsigned long) time(NULL));
}

// Use the global passwords file, if specified by auth_gpass option,
// or search for .htpasswd in the requested directory.
static FILE *open_auth_file(struct connection *conn, const char *path) {
  char name[MAX_PATH_SIZE];
  const char *p, *gpass = conn->server->config_options[GLOBAL_AUTH_FILE];
  file_stat_t st;
  FILE *fp = NULL;
  if (gpass != NULL) {
    // Use global passwords file
    fp = fopen(gpass, "r");
  } else if (!stat(path, &st) && S_ISDIR(st.st_mode)) {
    mg_snprintf(name, sizeof(name), "%s%c%s", path, '/', PASSWORDS_FILE_NAME);
    fp = fopen(name, "r");
Sergey Lyubka's avatar
Sergey Lyubka committed
  } else {
    // Try to find .htpasswd in requested directory.
    if ((p = strrchr(path, '/')) == NULL) p = path;
    mg_snprintf(name, sizeof(name), "%.*s%c%s",
                (int) (p - path), path, '/', PASSWORDS_FILE_NAME);
    fp = fopen(name, "r");
#ifndef HAVE_MD5
typedef struct MD5Context {
  uint32_t buf[4];
  uint32_t bits[2];
  unsigned char in[64];
} MD5_CTX;
static void byteReverse(unsigned char *buf, unsigned longs) {
  uint32_t t;
  // Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN
  if (is_big_endian()) {
    do {
      t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
        ((unsigned) buf[1] << 8 | buf[0]);
      * (uint32_t *) buf = t;
      buf += 4;
    } while (--longs);
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))
#define MD5STEP(f, w, x, y, z, data, s) \
  ( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )
// Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
// initialization constants.
static void MD5Init(MD5_CTX *ctx) {
  ctx->buf[0] = 0x67452301;
  ctx->buf[1] = 0xefcdab89;
  ctx->buf[2] = 0x98badcfe;
  ctx->buf[3] = 0x10325476;
  ctx->bits[0] = 0;
  ctx->bits[1] = 0;
static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {
  register uint32_t a, b, c, d;
  a = buf[0];
  b = buf[1];
  c = buf[2];
  d = buf[3];
  MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
  MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
  MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
  MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
  MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
  MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
  MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
  MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
  MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
  MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
  MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
  MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
  MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
  MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
  MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
  MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
  MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
  MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
  MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
  MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
  MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
  MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
  MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
  MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
  MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
  MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
  MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
  MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
  MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
  MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
  MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
  MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
  MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
  MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
  MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
  MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
  MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
  MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
  MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
  MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
  MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
  MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
  MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
  MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
  MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
  MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
  MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
  MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
  MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
  MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
  MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
  MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
  MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
  MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
  MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
  MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
  MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
  MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
  MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
  MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
  MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
  MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
  MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
  MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);

  buf[0] += a;
  buf[1] += b;
  buf[2] += c;
  buf[3] += d;
static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) {
  uint32_t t;
  t = ctx->bits[0];
  if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
    ctx->bits[1]++;
  ctx->bits[1] += len >> 29;
  t = (t >> 3) & 0x3f;
  if (t) {
    unsigned char *p = (unsigned char *) ctx->in + t;
    t = 64 - t;
    if (len < t) {
      memcpy(p, buf, len);
      return;
    memcpy(p, buf, t);
    byteReverse(ctx->in, 16);
    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
    buf += t;
    len -= t;
  while (len >= 64) {
    memcpy(ctx->in, buf, 64);
    byteReverse(ctx->in, 16);
    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
    buf += 64;
    len -= 64;
  memcpy(ctx->in, buf, len);
static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) {
  unsigned count;
  unsigned char *p;
  uint32_t *a;
  count = (ctx->bits[0] >> 3) & 0x3F;
  p = ctx->in + count;
  *p++ = 0x80;
  count = 64 - 1 - count;
  if (count < 8) {
    memset(p, 0, count);
    byteReverse(ctx->in, 16);
    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
    memset(ctx->in, 0, 56);
Sergey Lyubka's avatar
Sergey Lyubka committed
  } else {
    memset(p, 0, count - 8);
  byteReverse(ctx->in, 14);
  a = (uint32_t *)ctx->in;
  a[14] = ctx->bits[0];
  a[15] = ctx->bits[1];
  MD5Transform(ctx->buf, (uint32_t *) ctx->in);
  byteReverse((unsigned char *) ctx->buf, 4);
  memcpy(digest, ctx->buf, 16);
  memset((char *) ctx, 0, sizeof(*ctx));
#endif // !HAVE_MD5
// Stringify binary data. Output buffer must be twice as big as input,
// because each byte takes 2 bytes in string representation
static void bin2str(char *to, const unsigned char *p, size_t len) {
  static const char *hex = "0123456789abcdef";
  for (; len--; p++) {
    *to++ = hex[p[0] >> 4];
    *to++ = hex[p[0] & 0x0f];
  *to = '\0';
// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes.
char *mg_md5(char buf[33], ...) {
  unsigned char hash[16];
  const char *p;
  va_list ap;
  MD5_CTX ctx;
  MD5Init(&ctx);
  va_start(ap, buf);
  while ((p = va_arg(ap, const char *)) != NULL) {
    MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p));
  va_end(ap);
  MD5Final(hash, &ctx);
  bin2str(buf, hash, sizeof(hash));
  return buf;
// Check the user's password, return 1 if OK
static int check_password(const char *method, const char *ha1, const char *uri,
                          const char *nonce, const char *nc, const char *cnonce,
                          const char *qop, const char *response) {
  char ha2[32 + 1], expected_response[32 + 1];
#if 0
  // Check for authentication timeout
  if ((unsigned long) time(NULL) - (unsigned long) to64(nonce) > 3600) {
    return 0;
  }
  mg_md5(ha2, method, ":", uri, NULL);
  mg_md5(expected_response, ha1, ":", nonce, ":", nc,
      ":", cnonce, ":", qop, ":", ha2, NULL);
  return mg_strcasecmp(response, expected_response) == 0;
// Authorize against the opened passwords file. Return 1 if authorized.
static int authorize(struct connection *conn, FILE *fp) {
  const char *hdr = mg_get_header(&conn->mg_conn, "Authorization");
  char line[256], f_user[256], ha1[256], f_domain[256], user[100], nonce[100],
       uri[MAX_REQUEST_SIZE], cnonce[100], resp[100], qop[100], nc[100];

  if (hdr == NULL || mg_strncasecmp(hdr, "Digest ", 7) != 0) return 0;
  if (!mg_parse_header(hdr, "username", user, sizeof(user))) return 0;
  if (!mg_parse_header(hdr, "cnonce", cnonce, sizeof(cnonce))) return 0;
  if (!mg_parse_header(hdr, "response", resp, sizeof(resp))) return 0;
  if (!mg_parse_header(hdr, "uri", uri, sizeof(uri))) return 0;
  if (!mg_parse_header(hdr, "qop", qop, sizeof(qop))) return 0;
  if (!mg_parse_header(hdr, "nc", nc, sizeof(nc))) return 0;
  if (!mg_parse_header(hdr, "nonce", nonce, sizeof(nonce))) return 0;
  while (fgets(line, sizeof(line), fp) != NULL) {
    if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) == 3 &&
        !strcmp(user, f_user) &&
        // NOTE(lsm): due to a bug in MSIE, we do not compare URIs
        !strcmp(conn->server->config_options[AUTH_DOMAIN], f_domain))
      return check_password(conn->mg_conn.request_method, ha1, uri,
                            nonce, nc, cnonce, qop, resp);
// Return 1 if request is authorised, 0 otherwise.
static int is_authorized(struct connection *conn, const char *path) {
  FILE *fp;
  int authorized = 1;

  if ((fp = open_auth_file(conn, path)) != NULL) {
    authorized = authorize(conn, fp);
    fclose(fp);
  return authorized;
static int is_authorized_for_dav(struct connection *conn) {
  const char *auth_file = conn->server->config_options[DAV_AUTH_FILE];
  FILE *fp;
  int authorized = 0;

  if (auth_file != NULL && (fp = fopen(auth_file, "r")) != NULL) {
    authorized = authorize(conn, fp);
    fclose(fp);
  }
  return authorized;
static int is_dangerous_dav_request(const struct connection *conn) {
  const char *s = conn->mg_conn.request_method;
  return s && (!strcmp(s, "PUT") || !strcmp(s, "DELETE") ||
               !strcmp(s, "MKCOL"));
#endif // NO_AUTH
int mg_parse_header(const char *str, const char *var_name, char *buf,
                    size_t buf_size) {
  int ch = ' ', len = 0, n = strlen(var_name);
  const char *p, *s = NULL;
  if (buf != NULL) buf[0] = '\0';
  // Find where variable starts
  while (str != NULL && (s = strstr(str, var_name)) != NULL &&
         ((s > str && s[-1] != ' ') || s[n] != '=')) {
    str = s + n;
  if (s != NULL && s[n + 1] != '\0') {
    s += n + 1;
    if (*s == '"' || *s == '\'') ch = *s++;
    p = s;
    while (p[0] != '\0' && p[0] != ch && len < (int) buf_size) {
      if (p[0] == '\\' && p[1] == ch) p++;
      buf[len++] = *p++;
    }
    if (len >= (int) buf_size || (ch != ' ' && *p != ch)) {
      len = 0;
    } else {
      if (len > 0 && s[len - 1] == ',') len--;
      buf[len] = '\0';
    }
  }
  return len;
#ifdef USE_LUA
#include <netdb.h>
#include "lua_5.2.1.h"

static void reg_string(struct lua_State *L, const char *name, const char *val) {
  lua_pushstring(L, name);
  lua_pushstring(L, val);
  lua_rawset(L, -3);
}

static void reg_int(struct lua_State *L, const char *name, int val) {
  lua_pushstring(L, name);
  lua_pushinteger(L, val);
  lua_rawset(L, -3);
}

static void reg_function(struct lua_State *L, const char *name,
                         lua_CFunction func, struct mg_connection *conn) {
  lua_pushstring(L, name);
  lua_pushlightuserdata(L, conn);
  lua_pushcclosure(L, func, 1);
  lua_rawset(L, -3);
}

static int lua_write(lua_State *L) {
  int i, num_args;
  const char *str;
  size_t size;
  struct mg_connection *conn = (struct mg_connection *)
    lua_touserdata(L, lua_upvalueindex(1));

  num_args = lua_gettop(L);
  for (i = 1; i <= num_args; i++) {
    if (lua_isstring(L, i)) {
      str = lua_tolstring(L, i, &size);
      mg_write(conn, str, size);
    }
  }

  return 0;
}

static int lsp_sock_close(lua_State *L) {
  if (lua_gettop(L) > 0 && lua_istable(L, -1)) {
    lua_getfield(L, -1, "sock");
    closesocket((sock_t) lua_tonumber(L, -1));
  } else {
    return luaL_error(L, "invalid :close() call");
  }
  return 1;
}

static int lsp_sock_recv(lua_State *L) {
  char buf[2000];
  int n;

  if (lua_gettop(L) > 0 && lua_istable(L, -1)) {
    lua_getfield(L, -1, "sock");
    n = recv((sock_t) lua_tonumber(L, -1), buf, sizeof(buf), 0);
    if (n <= 0) {
      lua_pushnil(L);
    } else {
      lua_pushlstring(L, buf, n);
    }
  } else {
    return luaL_error(L, "invalid :close() call");
  }
  return 1;
}

static int lsp_sock_send(lua_State *L) {
  const char *buf;
  size_t len, sent = 0;
  int n, sock;

  if (lua_gettop(L) > 1 && lua_istable(L, -2) && lua_isstring(L, -1)) {
    buf = lua_tolstring(L, -1, &len);
    lua_getfield(L, -2, "sock");
    sock = (int) lua_tonumber(L, -1);
    while (sent < len) {
      if ((n = send(sock, buf + sent, len - sent, 0)) <= 0) {
        break;
      }
      sent += n;
    }
    lua_pushnumber(L, n);
  } else {
    return luaL_error(L, "invalid :close() call");
  }
  return 1;
}

static const struct luaL_Reg luasocket_methods[] = {
  {"close", lsp_sock_close},
  {"send", lsp_sock_send},
  {"recv", lsp_sock_recv},
  {NULL, NULL}
};

static sock_t conn2(const char *host, int port) {
  struct sockaddr_in sin;
  struct hostent *he = NULL;
  sock_t sock = INVALID_SOCKET;

  if (host != NULL &&
      (he = gethostbyname(host)) != NULL &&
    (sock = socket(PF_INET, SOCK_STREAM, 0)) != INVALID_SOCKET) {
    set_close_on_exec(sock);
    sin.sin_family = AF_INET;
    sin.sin_port = htons((uint16_t) port);
    sin.sin_addr = * (struct in_addr *) he->h_addr_list[0];
    if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) != 0) {
      closesocket(sock);
      sock = INVALID_SOCKET;
    }
  }
  return sock;
}

static int lsp_connect(lua_State *L) {
  sock_t sock;

  if (lua_isstring(L, -2) && lua_isnumber(L, -1)) {
    sock = conn2(lua_tostring(L, -2), (int) lua_tonumber(L, -1));
    if (sock == INVALID_SOCKET) {
      lua_pushnil(L);
    } else {
      lua_newtable(L);
      reg_int(L, "sock", sock);
      reg_string(L, "host", lua_tostring(L, -4));
      luaL_getmetatable(L, "luasocket");
      lua_setmetatable(L, -2);
    }
  } else {
    return luaL_error(L, "connect(host,port): invalid parameter given.");
  }
  return 1;
}

static void prepare_lua_environment(struct mg_connection *ri, lua_State *L) {
  extern void luaL_openlibs(lua_State *);
  int i;

  luaL_openlibs(L);
#ifdef USE_LUA_SQLITE3
  { extern int luaopen_lsqlite3(lua_State *); luaopen_lsqlite3(L); }
#endif

  luaL_newmetatable(L, "luasocket");
  lua_pushliteral(L, "__index");
  luaL_newlib(L, luasocket_methods);
  lua_rawset(L, -3);
  lua_pop(L, 1);
  lua_register(L, "connect", lsp_connect);

  if (ri == NULL) return;

  // Register mg module
  lua_newtable(L);
  reg_function(L, "write", lua_write, ri);

  // Export request_info
  lua_pushstring(L, "request_info");
  lua_newtable(L);
  reg_string(L, "request_method", ri->request_method);
  reg_string(L, "uri", ri->uri);
  reg_string(L, "http_version", ri->http_version);
  reg_string(L, "query_string", ri->query_string);
  reg_string(L, "remote_ip", ri->remote_ip);
  reg_int(L, "remote_port", ri->remote_port);
  reg_int(L, "num_headers", ri->num_headers);
  lua_pushstring(L, "http_headers");
  lua_newtable(L);
  for (i = 0; i < ri->num_headers; i++) {
    reg_string(L, ri->http_headers[i].name, ri->http_headers[i].value);
  }
  lua_rawset(L, -3);
  lua_rawset(L, -3);

  lua_setglobal(L, "mg");

  // Register default mg.onerror function
  luaL_dostring(L, "mg.onerror = function(e) mg.write('\\nLua error:\\n', "
                "debug.traceback(e, 1)) end");
}

static int lua_error_handler(lua_State *L) {
  const char *error_msg =  lua_isstring(L, -1) ?  lua_tostring(L, -1) : "?\n";

  lua_getglobal(L, "mg");
  if (!lua_isnil(L, -1)) {
    lua_getfield(L, -1, "write");   // call mg.write()
    lua_pushstring(L, error_msg);
    lua_pushliteral(L, "\n");
    lua_call(L, 2, 0);
    luaL_dostring(L, "mg.write(debug.traceback(), '\\n')");
  } else {
    printf("Lua error: [%s]\n", error_msg);
    luaL_dostring(L, "print(debug.traceback(), '\\n')");
  }
  // TODO(lsm): leave the stack balanced

  return 0;
}

static void handle_lua_request(struct connection *conn, const char *path) {
  lua_State *L;

  if (path != NULL && (L = luaL_newstate()) != NULL) {
    prepare_lua_environment(&conn->mg_conn, L);
    lua_pushcclosure(L, &lua_error_handler, 0);

    lua_pushglobaltable(L);

    if (luaL_loadfile(L, path) != 0) {
      lua_error_handler(L);
    }
    lua_pcall(L, 0, 0, -2);
    lua_close(L);
  }
  close_local_endpoint(conn);
}
#endif // USE_LUA

static void open_local_endpoint(struct connection *conn) {
  static const char lua_pat[] = LUA_SCRIPT_PATTERN;
  char path[MAX_PATH_SIZE] = {'\0'};
  file_stat_t st;
  int exists = 0, is_directory = 0;
  const char *cl_hdr = mg_get_header(&conn->mg_conn, "Content-Length");
  const char *cgi_pat = conn->server->config_options[CGI_PATTERN];
  const char *dir_lst = conn->server->config_options[ENABLE_DIRECTORY_LISTING];
  conn->mg_conn.content_len = cl_hdr == NULL ? 0 : (int) to64(cl_hdr);
  // Call URI handler if one is registered for this URI
  conn->endpoint.uh = find_uri_handler(conn->server, conn->mg_conn.uri);
  if (conn->endpoint.uh != NULL) {
    conn->endpoint_type = EP_USER;
    conn->mg_conn.content = conn->local_iobuf.buf;
    return;
  exists = convert_uri_to_file_name(conn, path, sizeof(path), &st);
  is_directory = S_ISDIR(st.st_mode);
  if (!strcmp(conn->mg_conn.request_method, "OPTIONS")) {
    send_options(conn);
  } else if (conn->server->config_options[DOCUMENT_ROOT] == NULL) {
    send_http_error(conn, 404);
#ifndef NO_AUTH
  } else if ((!is_dangerous_dav_request(conn) && !is_authorized(conn, path)) ||
             (is_dangerous_dav_request(conn) && !is_authorized_for_dav(conn))) {
    send_authorization_request(conn);
Sergey Lyubka's avatar
Sergey Lyubka committed
#endif
#ifndef NO_DAV
  } else if (!strcmp(conn->mg_conn.request_method, "PROPFIND")) {
    handle_propfind(conn, path, &st);
  } else if (!strcmp(conn->mg_conn.request_method, "MKCOL")) {
    handle_mkcol(conn, path);
  } else if (!strcmp(conn->mg_conn.request_method, "DELETE")) {
    handle_delete(conn, path);
  } else if (!strcmp(conn->mg_conn.request_method, "PUT")) {
    handle_put(conn, path);
Sergey Lyubka's avatar
Sergey Lyubka committed
#endif
  } else if (!exists || must_hide_file(conn, path)) {
    send_http_error(conn, 404);
  } else if (is_directory &&
             conn->mg_conn.uri[strlen(conn->mg_conn.uri) - 1] != '/') {
Sergey Lyubka's avatar
Sergey Lyubka committed
    mg_fmt(conn, "HTTP/1.1 301 Moved Permanently\r\n"
           "Location: %s/\r\n\r\n", conn->mg_conn.uri);
  } else if (is_directory && !find_index_file(conn, path, sizeof(path), &st)) {
    if (!mg_strcasecmp(dir_lst, "yes")) {
#ifndef NO_DIRECTORY_LISTING
      send_directory_listing(conn, path);
#else
      send_http_error(conn, 501);
#endif
    } else {
      send_http_error(conn, 403);
    }
  } else if (match_prefix(lua_pat, sizeof(lua_pat) - 1, path) > 0) {
#ifdef USE_LUA
    handle_lua_request(conn, path);
#else
    send_http_error(conn, 501);
#endif
  } else if (match_prefix(cgi_pat, strlen(cgi_pat), path) > 0) {
    if (strcmp(conn->mg_conn.request_method, "POST") &&
        strcmp(conn->mg_conn.request_method, "HEAD") &&
        strcmp(conn->mg_conn.request_method, "GET")) {
      send_http_error(conn, 501);
    } else {
#if !defined(NO_CGI)
      open_cgi_endpoint(conn, path);
#else
      send_http_error(conn, 501);
#endif // !NO_CGI