Skip to content
Snippets Groups Projects
mongoose.c 167 KiB
Newer Older
  mg_write(conn, buf, strlen(buf));
}

static size_t deliver_websocket_frame(struct connection *conn) {
  // Having buf unsigned char * is important, as it is used below in arithmetic
Sergey Lyubka's avatar
Sergey Lyubka committed
  unsigned char *buf = (unsigned char *) conn->ns_conn->recv_iobuf.buf;
Sergey Lyubka's avatar
Sergey Lyubka committed
  size_t i, len, buf_len = conn->ns_conn->recv_iobuf.len, frame_len = 0,
      mask_len = 0, header_len = 0, data_len = 0, buffered = 0;

  if (buf_len >= 2) {
    len = buf[1] & 127;
    mask_len = buf[1] & 128 ? 4 : 0;
    if (len < 126 && buf_len >= mask_len) {
      data_len = len;
      header_len = 2 + mask_len;
    } else if (len == 126 && buf_len >= 4 + mask_len) {
      header_len = 4 + mask_len;
Sergey Lyubka's avatar
Sergey Lyubka committed
      data_len = ((((size_t) buf[2]) << 8) + buf[3]);
    } else if (buf_len >= 10 + mask_len) {
      header_len = 10 + mask_len;
Sergey Lyubka's avatar
Sergey Lyubka committed
      data_len = (size_t) (((uint64_t) htonl(* (uint32_t *) &buf[2])) << 32) +
        htonl(* (uint32_t *) &buf[6]);
    }
  }

  frame_len = header_len + data_len;
  buffered = frame_len > 0 && frame_len <= buf_len;

  if (buffered) {
    conn->mg_conn.content_len = data_len;
    conn->mg_conn.content = (char *) buf + header_len;
    conn->mg_conn.wsbits = buf[0];

    // Apply mask if necessary
    if (mask_len > 0) {
      for (i = 0; i < data_len; i++) {
        buf[i + header_len] ^= (buf + header_len - mask_len)[i % 4];
    // Call the handler and remove frame from the iobuf
    if (call_user(conn, MG_REQUEST) == MG_FALSE ||
        (buf[0] & 0x0f) == WEBSOCKET_OPCODE_CONNECTION_CLOSE) {
Sergey Lyubka's avatar
Sergey Lyubka committed
      conn->ns_conn->flags |= NSF_FINISHED_SENDING_DATA;
Sergey Lyubka's avatar
Sergey Lyubka committed
    iobuf_remove(&conn->ns_conn->recv_iobuf, frame_len);
  return buffered;
Pavel Pimenov's avatar
Pavel Pimenov committed
size_t mg_websocket_write(struct mg_connection *conn, int opcode,
Sergey Lyubka's avatar
Sergey Lyubka committed
                          const char *data, size_t data_len) {
    unsigned char mem[4192], *copy = mem;
    size_t copy_len = 0;

Sergey Lyubka's avatar
Sergey Lyubka committed
    /* Check overflow */
    if (data_len > ~(size_t)0 - (size_t)10) {
      return 0;
    }

    if (data_len + 10 > sizeof(mem) &&
        (copy = (unsigned char *) NS_MALLOC(data_len + 10)) == NULL) {
    }

    copy[0] = 0x80 + (opcode & 0x0f);

    // Frame format: http://tools.ietf.org/html/rfc6455#section-5.2
    if (data_len < 126) {
      // Inline 7-bit length field
      copy[1] = data_len;
      memcpy(copy + 2, data, data_len);
      copy_len = 2 + data_len;
    } else if (data_len <= 0xFFFF) {
      // 16-bit length field
      copy[1] = 126;
      * (uint16_t *) (copy + 2) = (uint16_t) htons((uint16_t) data_len);
      memcpy(copy + 4, data, data_len);
      copy_len = 4 + data_len;
    } else {
      // 64-bit length field
      const uint32_t hi = htonl((uint32_t) ((uint64_t) data_len >> 32));
      const uint32_t lo = htonl(data_len & 0xffffffff);
      copy[1] = 127;
      memcpy(copy+2,&hi,sizeof(hi));
      memcpy(copy+6,&lo,sizeof(lo));
      memcpy(copy + 10, data, data_len);
      copy_len = 10 + data_len;
    }

    if (copy_len > 0) {
Sergey Lyubka's avatar
Sergey Lyubka committed
    // If we send closing frame, schedule a connection to be closed after
    // data is drained to the client.
    if (opcode == WEBSOCKET_OPCODE_CONNECTION_CLOSE) {
      MG_CONN_2_CONN(conn)->ns_conn->flags |= NSF_FINISHED_SENDING_DATA;
    }

    return MG_CONN_2_CONN(conn)->ns_conn->send_iobuf.len;
Pavel Pimenov's avatar
Pavel Pimenov committed
size_t mg_websocket_printf(struct mg_connection *conn, int opcode,
  char mem[4192], *buf = mem;
  va_list ap;
  int len;

  va_start(ap, fmt);
  if ((len = ns_avprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
    mg_websocket_write(conn, opcode, buf, len);
  }
  va_end(ap);

  if (buf != mem && buf != NULL) {
  return MG_CONN_2_CONN(conn)->ns_conn->send_iobuf.len;
static void send_websocket_handshake_if_requested(struct mg_connection *conn) {
  const char *ver = mg_get_header(conn, "Sec-WebSocket-Version"),
        *key = mg_get_header(conn, "Sec-WebSocket-Key");
  if (ver != NULL && key != NULL) {
    conn->is_websocket = 1;
Sergey Lyubka's avatar
Sergey Lyubka committed
    if (call_user(MG_CONN_2_CONN(conn), MG_WS_HANDSHAKE) == MG_FALSE) {
      send_websocket_handshake(conn, key);
    }
Sergey Lyubka's avatar
Sergey Lyubka committed
    call_user(MG_CONN_2_CONN(conn), MG_WS_CONNECT);

static void ping_idle_websocket_connection(struct connection *conn, time_t t) {
Sergey Lyubka's avatar
Sergey Lyubka committed
  if (t - conn->ns_conn->last_io_time > MONGOOSE_USE_WEBSOCKET_PING_INTERVAL) {
    mg_websocket_write(&conn->mg_conn, WEBSOCKET_OPCODE_PING, "", 0);
  }
}
#else
#define ping_idle_websocket_connection(conn, t)
#endif // !MONGOOSE_NO_WEBSOCKET
static void write_terminating_chunk(struct connection *conn) {
  mg_write(&conn->mg_conn, "0\r\n\r\n", 5);
}

static int call_request_handler(struct connection *conn) {
  int result;
Sergey Lyubka's avatar
Sergey Lyubka committed
  conn->mg_conn.content = conn->ns_conn->recv_iobuf.buf;
  if ((result = call_user(conn, MG_REQUEST)) == MG_TRUE) {
Sergey Lyubka's avatar
Sergey Lyubka committed
    if (conn->ns_conn->flags & MG_USING_CHUNKED_API) {
      terminate_headers(&conn->mg_conn);
      write_terminating_chunk(conn);
    }
    close_local_endpoint(conn);
const char *mg_get_mime_type(const char *path, const char *default_mime_type) {
  const char *ext;
  size_t i, path_len;

  path_len = strlen(path);

  for (i = 0; static_builtin_mime_types[i].extension != NULL; i++) {
    ext = path + (path_len - static_builtin_mime_types[i].ext_len);
    if (path_len > static_builtin_mime_types[i].ext_len &&
        mg_strcasecmp(ext, static_builtin_mime_types[i].extension) == 0) {
      return static_builtin_mime_types[i].mime_type;
    }
  }

#ifndef MONGOOSE_NO_FILESYSTEM
// Convert month to the month number. Return -1 on error, or month number
static int get_month_index(const char *s) {
Sergey Lyubka's avatar
Sergey Lyubka committed
  static const char *month_names[] = {
    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
  };
Sergey Lyubka's avatar
Sergey Lyubka committed
  for (i = 0; i < (int) ARRAY_SIZE(month_names); i++)
    if (!strcmp(s, month_names[i]))
static int num_leap_years(int year) {
  return year / 4 - year / 100 + year / 400;
// Parse UTC date-time string, and return the corresponding time_t value.
static time_t parse_date_string(const char *datetime) {
  static const unsigned short days_before_month[] = {
    0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
  };
  char month_str[32];
  int second, minute, hour, day, month, year, leap_days, days;
  time_t result = (time_t) 0;

  if (((sscanf(datetime, "%d/%3s/%d %d:%d:%d",
               &day, month_str, &year, &hour, &minute, &second) == 6) ||
       (sscanf(datetime, "%d %3s %d %d:%d:%d",
               &day, month_str, &year, &hour, &minute, &second) == 6) ||
       (sscanf(datetime, "%*3s, %d %3s %d %d:%d:%d",
               &day, month_str, &year, &hour, &minute, &second) == 6) ||
       (sscanf(datetime, "%d-%3s-%d %d:%d:%d",
               &day, month_str, &year, &hour, &minute, &second) == 6)) &&
      year > 1970 &&
      (month = get_month_index(month_str)) != -1) {
    leap_days = num_leap_years(year) - num_leap_years(1970);
    year -= 1970;
    days = year * 365 + days_before_month[month] + (day - 1) + leap_days;
    result = days * 24 * 3600 + hour * 3600 + minute * 60 + second;
  }
  return result;
}
Sergey Lyubka's avatar
Sergey Lyubka committed
// Look at the "path" extension and figure what mime type it has.
// Store mime type in the vector.
static void get_mime_type(const struct mg_server *server, const char *path,
                          struct vec *vec) {
  struct vec ext_vec, mime_vec;
  const char *list, *ext;
  size_t path_len;

  path_len = strlen(path);

  // Scan user-defined mime types first, in case user wants to
  // override default mime types.
  list = server->config_options[EXTRA_MIME_TYPES];
  while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) {
    // ext now points to the path suffix
    ext = path + path_len - ext_vec.len;
    if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) {
      *vec = mime_vec;
      return;
    }
  }

  vec->ptr = mg_get_mime_type(path, "text/plain");
Sergey Lyubka's avatar
Sergey Lyubka committed
  vec->len = strlen(vec->ptr);
}

static const char *suggest_connection_header(const struct mg_connection *conn) {
  return should_keep_alive(conn) ? "keep-alive" : "close";
}

static void construct_etag(char *buf, size_t buf_len, const file_stat_t *st) {
  mg_snprintf(buf, buf_len, "\"%lx.%" INT64_FMT "\"",
              (unsigned long) st->st_mtime, (int64_t) st->st_size);
}

// Return True if we should reply 304 Not Modified.
static int is_not_modified(const struct connection *conn,
                           const file_stat_t *stp) {
  char etag[64];
  const char *ims = mg_get_header(&conn->mg_conn, "If-Modified-Since");
  const char *inm = mg_get_header(&conn->mg_conn, "If-None-Match");
  construct_etag(etag, sizeof(etag), stp);
  return (inm != NULL && !mg_strcasecmp(etag, inm)) ||
    (ims != NULL && stp->st_mtime <= parse_date_string(ims));
}
// For given directory path, substitute it to valid index file.
// Return 0 if index file has been found, -1 if not found.
// If the file is found, it's stats is returned in stp.
static int find_index_file(struct connection *conn, char *path,
                           size_t path_len, file_stat_t *stp) {
  const char *list = conn->server->config_options[INDEX_FILES];
  file_stat_t st;
  struct vec filename_vec;
  size_t n = strlen(path);
  int found = 0;

  // The 'path' given to us points to the directory. Remove all trailing
  // directory separator characters from the end of the path, and
  // then append single directory separator character.
  while (n > 0 && path[n - 1] == '/') {
    n--;
  path[n] = '/';
  // Traverse index files list. For each entry, append it to the given
  // path and see if the file exists. If it exists, break the loop
  while ((list = next_option(list, &filename_vec, NULL)) != NULL) {
    if (path_len <= n + 2) {
      continue;
    }

    // Ignore too long entries that may overflow path buffer
    if (filename_vec.len > (path_len - (n + 2)))
    // Prepare full path to the index file
Sergey Lyubka's avatar
Sergey Lyubka committed
    strncpy(path + n + 1, filename_vec.ptr, filename_vec.len);
    path[n + 1 + filename_vec.len] = '\0';
    //DBG(("[%s]", path));

    // Does it exist?
    if (!stat(path, &st)) {
      // Yes it does, break the loop
      *stp = st;
      found = 1;
      break;
    }
  // If no index file exists, restore directory path
  if (!found) {
    path[n] = '\0';
  }

  return found;
Sergey Lyubka's avatar
Sergey Lyubka committed
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 open_file_endpoint(struct connection *conn, const char *path,
                               file_stat_t *st, const char *extra_headers) {
  char date[64], lm[64], etag[64], range[64], headers[1000];
Sergey Lyubka's avatar
Sergey Lyubka committed
  const char *msg = "OK", *hdr;
Sergey Lyubka's avatar
Sergey Lyubka committed
  time_t t, curtime = time(NULL);
Sergey Lyubka's avatar
Sergey Lyubka committed
  int64_t r1, r2;
  struct vec mime_vec;
  int n;

  conn->endpoint_type = EP_FILE;
Sergey Lyubka's avatar
Sergey Lyubka committed
  ns_set_close_on_exec(conn->endpoint.fd);
Sergey Lyubka's avatar
Sergey Lyubka committed
  conn->mg_conn.status_code = 200;

  get_mime_type(conn->server, path, &mime_vec);
  conn->cl = st->st_size;
  range[0] = '\0';

  // If Range: header specified, act accordingly
  r1 = r2 = 0;
  hdr = mg_get_header(&conn->mg_conn, "Range");
  if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0 &&
      r1 >= 0 && r2 >= 0) {
    conn->mg_conn.status_code = 206;
    conn->cl = n == 2 ? (r2 > conn->cl ? conn->cl : r2) - r1 + 1: conn->cl - r1;
    mg_snprintf(range, sizeof(range), "Content-Range: bytes "
                "%" INT64_FMT "-%" INT64_FMT "/%" INT64_FMT "\r\n",
                r1, r1 + conn->cl - 1, (int64_t) st->st_size);
    msg = "Partial Content";
    lseek(conn->endpoint.fd, r1, SEEK_SET);
  }

  // Prepare Etag, Date, Last-Modified headers. Must be in UTC, according to
  // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3
  gmt_time_string(date, sizeof(date), &curtime);
Sergey Lyubka's avatar
Sergey Lyubka committed
  t = st->st_mtime; // store in local variable for NDK compile
  gmt_time_string(lm, sizeof(lm), &t);
Sergey Lyubka's avatar
Sergey Lyubka committed
  construct_etag(etag, sizeof(etag), st);

  n = mg_snprintf(headers, sizeof(headers),
                  "HTTP/1.1 %d %s\r\n"
                  "Date: %s\r\n"
                  "Last-Modified: %s\r\n"
                  "Etag: %s\r\n"
                  "Content-Type: %.*s\r\n"
                  "Content-Length: %" INT64_FMT "\r\n"
                  "Connection: %s\r\n"
                  "Accept-Ranges: bytes\r\n"
Sergey Lyubka's avatar
Sergey Lyubka committed
                  conn->mg_conn.status_code, msg, date, lm, etag,
                  (int) mime_vec.len, mime_vec.ptr, conn->cl,
                  suggest_connection_header(&conn->mg_conn),
                  range, extra_headers == NULL ? "" : extra_headers,
                  MONGOOSE_USE_EXTRA_HTTP_HEADERS);
Sergey Lyubka's avatar
Sergey Lyubka committed
  ns_send(conn->ns_conn, headers, n);
Sergey Lyubka's avatar
Sergey Lyubka committed

  if (!strcmp(conn->mg_conn.request_method, "HEAD")) {
Sergey Lyubka's avatar
Sergey Lyubka committed
    conn->ns_conn->flags |= NSF_FINISHED_SENDING_DATA;
Sergey Lyubka's avatar
Sergey Lyubka committed
    close(conn->endpoint.fd);
    conn->endpoint_type = EP_NONE;
  }
}

void mg_send_file_data(struct mg_connection *c, int fd) {
  struct connection *conn = MG_CONN_2_CONN(c);
  conn->endpoint_type = EP_FILE;
  conn->endpoint.fd = fd;
  ns_set_close_on_exec(conn->endpoint.fd);
}
#endif  // MONGOOSE_NO_FILESYSTEM
static void call_request_handler_if_data_is_buffered(struct connection *conn) {
#ifndef MONGOOSE_NO_WEBSOCKET
  if (conn->mg_conn.is_websocket) {
    do { } while (deliver_websocket_frame(conn));
  } else
  if (conn->num_bytes_recv >= (conn->cl + conn->request_len) &&
Sergey Lyubka's avatar
Sergey Lyubka committed
      call_request_handler(conn) == MG_FALSE) {
    open_local_endpoint(conn, 1);
#if !defined(MONGOOSE_NO_DIRECTORY_LISTING) || !defined(MONGOOSE_NO_DAV)
#ifdef _WIN32
struct dirent {
  char d_name[MAX_PATH_SIZE];
};
typedef struct DIR {
  HANDLE   handle;
  WIN32_FIND_DATAW info;
  struct dirent result;
} DIR;
// Implementation of POSIX opendir/closedir/readdir for Windows.
static DIR *opendir(const char *name) {
  DIR *dir = NULL;
  wchar_t wpath[MAX_PATH_SIZE];
  DWORD attrs;
  if (name == NULL) {
    SetLastError(ERROR_BAD_ARGUMENTS);
  } else if ((dir = (DIR *) NS_MALLOC(sizeof(*dir))) == NULL) {
    SetLastError(ERROR_NOT_ENOUGH_MEMORY);
  } else {
    to_wchar(name, wpath, ARRAY_SIZE(wpath));
    attrs = GetFileAttributesW(wpath);
    if (attrs != 0xFFFFFFFF &&
        ((attrs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) {
      (void) wcscat(wpath, L"\\*");
      dir->handle = FindFirstFileW(wpath, &dir->info);
      dir->result.d_name[0] = '\0';
    } else {
  return dir;
static int closedir(DIR *dir) {
  int result = 0;
  if (dir != NULL) {
    if (dir->handle != INVALID_HANDLE_VALUE)
      result = FindClose(dir->handle) ? 0 : -1;
  } else {
    result = -1;
    SetLastError(ERROR_BAD_ARGUMENTS);
  return result;
static struct dirent *readdir(DIR *dir) {
  struct dirent *result = 0;
  if (dir) {
    if (dir->handle != INVALID_HANDLE_VALUE) {
      result = &dir->result;
      (void) WideCharToMultiByte(CP_UTF8, 0,
          dir->info.cFileName, -1, result->d_name,
          sizeof(result->d_name), NULL, NULL);
      if (!FindNextFileW(dir->handle, &dir->info)) {
        (void) FindClose(dir->handle);
        dir->handle = INVALID_HANDLE_VALUE;
      }
    } else {
      SetLastError(ERROR_FILE_NOT_FOUND);
  } else {
    SetLastError(ERROR_BAD_ARGUMENTS);
  return result;
}
#endif // _WIN32  POSIX opendir/closedir/readdir implementation
static int scan_directory(struct connection *conn, const char *dir,
                          struct dir_entry **arr) {
  char path[MAX_PATH_SIZE];
  struct dir_entry *p;
  struct dirent *dp;
  int arr_size = 0, arr_ind = 0, inc = 100;
  if ((dirp = (opendir(dir))) == NULL) return 0;

  while ((dp = readdir(dirp)) != NULL) {
    // Do not show current dir and hidden files
    if (!strcmp(dp->d_name, ".") ||
        !strcmp(dp->d_name, "..") ||
        must_hide_file(conn, dp->d_name)) {
      continue;
    mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
    if (arr_ind >= arr_size) {
      if ((p = (struct dir_entry *)
           NS_REALLOC(*arr, (inc + arr_size) * sizeof(**arr))) != NULL) {
        // Memset new chunk to zero, otherwise st_mtime will have garbage which
        // can make strftime() segfault, see
        // http://code.google.com/p/mongoose/issues/detail?id=79
        memset(p + arr_size, 0, sizeof(**arr) * inc);
        arr_size += inc;

    if (arr_ind < arr_size) {
      (*arr)[arr_ind].conn = conn;
      (*arr)[arr_ind].file_name = strdup(dp->d_name);
      stat(path, &(*arr)[arr_ind].st);
      arr_ind++;
    }
  closedir(dirp);
  return arr_ind;
size_t mg_url_encode(const char *src, size_t s_len, char *dst, size_t dst_len) {
  static const char *dont_escape = "._-$,;~()";
  static const char *hex = "0123456789abcdef";
  for (i = j = 0; dst_len > 0 && i < s_len && j + 2 < dst_len - 1; i++, j++) {
    if (isalnum(* (const unsigned char *) (src + i)) ||
        strchr(dont_escape, * (const unsigned char *) (src + i)) != NULL) {
      dst[j] = src[i];
    } else if (j + 3 < dst_len) {
      dst[j] = '%';
      dst[j + 1] = hex[(* (const unsigned char *) (src + i)) >> 4];
      dst[j + 2] = hex[(* (const unsigned char *) (src + i)) & 0xf];
      j += 2;
#endif  // !NO_DIRECTORY_LISTING || !MONGOOSE_NO_DAV
#ifndef MONGOOSE_NO_DIRECTORY_LISTING
static void print_dir_entry(const struct dir_entry *de) {
Sergey Lyubka's avatar
Sergey Lyubka committed
  char size[64], mod[64], href[MAX_PATH_SIZE * 3];
  int64_t fsize = de->st.st_size;
Sergey Lyubka's avatar
Sergey Lyubka committed
  int is_dir = S_ISDIR(de->st.st_mode);
  const char *slash = is_dir ? "/" : "";
Sergey Lyubka's avatar
Sergey Lyubka committed
  time_t t;
  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);
Sergey Lyubka's avatar
Sergey Lyubka committed
  t = de->st.st_mtime;  // store in local variable for NDK compile
  strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&t));
  mg_url_encode(de->file_name, strlen(de->file_name), href, sizeof(href));
Sergey Lyubka's avatar
Sergey Lyubka committed
  mg_printf_data(&de->conn->mg_conn,
Sergey Lyubka's avatar
Sergey Lyubka committed
                  "<tr><td><a href=\"%s%s\">%s%s</a></td>"
                  "<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
Sergey Lyubka's avatar
Sergey Lyubka committed
                  href, slash, de->file_name, slash, mod, size);
// 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) {
  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';

Sergey Lyubka's avatar
Sergey Lyubka committed
  mg_send_header(&conn->mg_conn, "Transfer-Encoding", "chunked");
  mg_send_header(&conn->mg_conn, "Content-Type", "text/html; charset=utf-8");
Sergey Lyubka's avatar
Sergey Lyubka committed
  mg_printf_data(&conn->mg_conn,
              "<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);

  num_entries = scan_directory(conn, dir, &arr);
  if (arr) {
      qsort(arr, num_entries, sizeof(arr[0]), compare_dir_entries);
      for (i = 0; i < num_entries; i++) {
        print_dir_entry(&arr[i]);
        NS_FREE(arr[i].file_name);
      }
      NS_FREE(arr);
  write_terminating_chunk(conn);
  close_local_endpoint(conn);
}
#endif  // MONGOOSE_NO_DIRECTORY_LISTING
#ifndef MONGOOSE_NO_DAV
static void print_props(struct connection *conn, const char *uri,
                        file_stat_t *stp) {
Sergey Lyubka's avatar
Sergey Lyubka committed
  char mtime[64];
  time_t t = stp->st_mtime;  // store in local variable for NDK compile
  gmt_time_string(mtime, sizeof(mtime), &t);
Sergey Lyubka's avatar
Sergey Lyubka committed
  mg_printf(&conn->mg_conn,
      "<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);
static void handle_propfind(struct connection *conn, const char *path,
                            file_stat_t *stp, int exists) {
  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");
#ifdef MONGOOSE_NO_DIRECTORY_LISTING
  const char *list_dir = "no";
#else
  const char *list_dir = conn->server->config_options[ENABLE_DIRECTORY_LISTING];
#endif
  conn->mg_conn.status_code = 207;
  // Print properties for the requested resource itself
  if (!exists) {
    conn->mg_conn.status_code = 404;
    mg_printf(&conn->mg_conn, "%s", "HTTP/1.1 404 Not Found\r\n\r\n");
  } else if (S_ISDIR(stp->st_mode) && mg_strcasecmp(list_dir, "yes") != 0) {
    conn->mg_conn.status_code = 403;
    mg_printf(&conn->mg_conn, "%s",
              "HTTP/1.1 403 Directory Listing Denied\r\n\r\n");
    ns_send(conn->ns_conn, header, sizeof(header) - 1);
    print_props(conn, conn->mg_conn.uri, stp);
    if (S_ISDIR(stp->st_mode) &&
             (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 * 3];
        struct dir_entry *de = &arr[i];
        mg_url_encode(de->file_name, strlen(de->file_name), buf, sizeof(buf));
        print_props(conn, buf, &de->st);
        NS_FREE(de->file_name);
    ns_send(conn->ns_conn, 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, NULL);
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) != 0) {
    send_http_error(conn, 404, NULL);
  } else if (S_ISDIR(st.st_mode)) {
    remove_directory(path);
    send_http_error(conn, 204, NULL);
  } else if (remove(path) == 0) {
    send_http_error(conn, 204, NULL);
    send_http_error(conn, 423, NULL);
// 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;
Sergey Lyubka's avatar
Sergey Lyubka committed
  // Create intermediate directories if they do not exist
  for (s = p = path + 1; (p = strchr(s, '/')) != NULL; s = ++p) {
    if (p - path >= (int) sizeof(buf)) return -1; // Buffer overflow
    memcpy(buf, path, p - path);
    buf[p - path] = '\0';
    if (stat(buf, &st) != 0 && mkdir(buf, 0755) != 0) return -1;
    if (p[1] == '\0') return 0;  // Path is a directory itself
Sergey Lyubka's avatar
Sergey Lyubka committed
  return 1;
static void handle_put(struct connection *conn, const char *path) {
  file_stat_t st;
  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_printf(&conn->mg_conn, "HTTP/1.1 %d OK\r\n\r\n",
              conn->mg_conn.status_code);
    close_local_endpoint(conn);
Sergey Lyubka's avatar
Sergey Lyubka committed
  } else if (rc == -1) {
    send_http_error(conn, 500, "put_dir: %s", strerror(errno));
  } else if (cl_hdr == NULL) {
    send_http_error(conn, 411, NULL);
  } else if ((conn->endpoint.fd =
              open(path, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0644)) < 0) {
    send_http_error(conn, 500, "open(%s): %s", path, strerror(errno));
Sergey Lyubka's avatar
Sergey Lyubka committed
  } else {
    DBG(("PUT [%s] %lu", path, (unsigned long) conn->ns_conn->recv_iobuf.len));
    conn->endpoint_type = EP_PUT;
Sergey Lyubka's avatar
Sergey Lyubka committed
    ns_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;
Sergey Lyubka's avatar
Sergey Lyubka committed
    mg_printf(&conn->mg_conn, "HTTP/1.1 %d OK\r\nContent-Length: 0\r\n\r\n",
              conn->mg_conn.status_code);
static void forward_put_data(struct connection *conn) {
Sergey Lyubka's avatar
Sergey Lyubka committed
  struct iobuf *io = &conn->ns_conn->recv_iobuf;
  size_t k = conn->cl < (int64_t) io->len ? conn->cl : (int64_t) io->len;   // To write
  size_t n = write(conn->endpoint.fd, io->buf, k);   // Write them!
  if (n > 0) {
Sergey Lyubka's avatar
Sergey Lyubka committed
    iobuf_remove(io, n);
    conn->cl -= n;
  }
  if (conn->cl <= 0) {
    close_local_endpoint(conn);
#endif //  MONGOOSE_NO_DAV
static void send_options(struct connection *conn) {
  conn->mg_conn.status_code = 200;
  mg_printf(&conn->mg_conn, "%s",
            "HTTP/1.1 200 OK\r\nAllow: GET, POST, HEAD, CONNECT, PUT, "
            "DELETE, OPTIONS, PROPFIND, MKCOL\r\nDAV: 1\r\n\r\n");
  close_local_endpoint(conn);
#ifndef MONGOOSE_NO_AUTH
void mg_send_digest_auth_request(struct mg_connection *c) {
Sergey Lyubka's avatar
Sergey Lyubka committed
  struct connection *conn = MG_CONN_2_CONN(c);
Sergey Lyubka's avatar
Sergey Lyubka committed
            "HTTP/1.1 401 Unauthorized\r\n"
Sergey Lyubka's avatar
Sergey Lyubka committed
            "Content-Length: 0\r\n"
Sergey Lyubka's avatar
Sergey Lyubka committed
            "WWW-Authenticate: Digest qop=\"auth\", "
            "realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
            conn->server->config_options[AUTH_DOMAIN],
            (unsigned long) time(NULL));
  close_local_endpoint(conn);
}

// Use the global passwords file, if specified by auth_gpass option,
// or search for .htpasswd in the requested directory.
Sergey Lyubka's avatar
Sergey Lyubka committed
static FILE *open_auth_file(struct connection *conn, const char *path,
                            int is_directory) {
  char name[MAX_PATH_SIZE];
  const char *p, *gpass = conn->server->config_options[GLOBAL_AUTH_FILE];
  FILE *fp = NULL;
  if (gpass != NULL) {
    // Use global passwords file
    fp = fopen(gpass, "r");
  } else if (is_directory) {
    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");
#if !defined(HAVE_MD5) && !defined(MONGOOSE_NO_AUTH)
Sergey Lyubka's avatar
Sergey Lyubka committed
/*
 * This code implements the MD5 message-digest algorithm.
 * The algorithm is due to Ron Rivest.	This code was
 * written by Colin Plumb in 1993, no copyright is claimed.
 * This code is in the public domain; do with it what you wish.
 *
 * Equivalent code is available from RSA Data Security, Inc.
 * This code has been tested against that, and is equivalent,
 * except that you don't need to include two pages of legalese
 * with every copy.
 *
 * To compute the message digest of a chunk of bytes, declare an
 * MD5Context structure, pass it to MD5Init, call MD5Update as
 * needed on buffers full of bytes, and then call MD5Final, which
 * will fill a supplied 16-byte array with the digest.
 */

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);