Skip to content
Snippets Groups Projects
mongoose.c 132 KiB
Newer Older
  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 ?
    MG_AUTH_OK : MG_AUTH_FAIL;
// Authorize against the opened passwords file. Return 1 if authorized.
int mg_authorize_digest(struct mg_connection *c, FILE *fp) {
  struct connection *conn = (struct connection *) c;
Sergey Lyubka's avatar
Sergey Lyubka committed
  const char *hdr;
  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];

Sergey Lyubka's avatar
Sergey Lyubka committed
  if (c == NULL || fp == NULL) return 0;
  if ((hdr = mg_get_header(c, "Authorization")) == 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(c->request_method, ha1, uri,
                            nonce, nc, cnonce, qop, resp);
  return MG_AUTH_FAIL;
// Return 1 if request is authorised, 0 otherwise.
static int is_authorized(struct connection *conn, const char *path) {
  FILE *fp;
  int authorized = MG_AUTH_OK;

  if ((fp = open_auth_file(conn, path)) != NULL) {
    authorized = mg_authorize_digest(&conn->mg_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 = MG_AUTH_FAIL;

  if (auth_file != NULL && (fp = fopen(auth_file, "r")) != NULL) {
    authorized = mg_authorize_digest(&conn->mg_conn, fp);
  return authorized;
static int is_dav_mutation(const struct connection *conn) {
  const char *s = conn->mg_conn.request_method;
  return s && (!strcmp(s, "PUT") || !strcmp(s, "DELETE") ||
               !strcmp(s, "MKCOL"));
#endif // MONGOOSE_NO_AUTH
Sergey Lyubka's avatar
Sergey Lyubka committed
int parse_header(const char *str, int str_len, const char *var_name, char *buf,
                 size_t buf_size) {
  int ch = ' ', len = 0, n = strlen(var_name);
Sergey Lyubka's avatar
Sergey Lyubka committed
  const char *p, *end = str + str_len, *s = NULL;
  if (buf != NULL && buf_size > 0) buf[0] = '\0';
  // Find where variable starts
  for (s = str; s != NULL && s + n < end; s++) {
    if ((s == str || s[-1] == ' ' || s[-1] == ',') && s[n] == '=' &&
Sergey Lyubka's avatar
Sergey Lyubka committed
        !memcmp(s, var_name, n)) break;
Sergey Lyubka's avatar
Sergey Lyubka committed
  if (s != NULL && &s[n + 1] < end) {
    s += n + 1;
    if (*s == '"' || *s == '\'') ch = *s++;
    p = s;
    while (p < end && p[0] != ch && p[0] != ',' && 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--;
Sergey Lyubka's avatar
Sergey Lyubka committed
      if (len > 0 && s[len - 1] == ';') len--;
  return len;
int mg_parse_header(const char *s, const char *var_name, char *buf,
Sergey Lyubka's avatar
Sergey Lyubka committed
                    size_t buf_size) {
  return parse_header(s, s == NULL ? 0 : strlen(s), var_name, buf, buf_size);
#ifdef MONGOOSE_USE_LUA
#include <lua.h>
#include <lauxlib.h>
Sergey Lyubka's avatar
Sergey Lyubka committed
#ifdef _WIN32
static void *mmap(void *addr, int64_t len, int prot, int flags, int fd,
                  int offset) {
  HANDLE fh = (HANDLE) _get_osfhandle(fd);
  HANDLE mh = CreateFileMapping(fh, 0, PAGE_READONLY, 0, 0, 0);
  void *p = MapViewOfFile(mh, FILE_MAP_READ, 0, 0, (size_t) len);
  CloseHandle(mh);
  return p;
}
#define munmap(x, y)  UnmapViewOfFile(x)
#define MAP_FAILED NULL
#define MAP_PRIVATE 0
#define PROT_READ 0
#else
#include <sys/mman.h>
#endif

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, sent);
  } 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 MONGOOSE_USE_LUA_SQLITE3
  { extern int luaopen_lsqlite3(lua_State *); luaopen_lsqlite3(L); }
#endif

  luaL_newmetatable(L, "luasocket");
  lua_pushliteral(L, "__index");
  lua_newtable(L);
  luaL_register(L, NULL, luasocket_methods);
  //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);
  lua_pushstring(L, "content");
  lua_pushlstring(L, ri->content == NULL ? "" : ri->content, 0);
  lua_rawset(L, -3);
  reg_int(L, "content_len", ri->content_len);
  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
  (void) 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);
    (void) luaL_dostring(L, "mg.write(debug.traceback(), '\\n')");
  } else {
    printf("Lua error: [%s]\n", error_msg);
    (void) luaL_dostring(L, "print(debug.traceback(), '\\n')");
  }
  // TODO(lsm): leave the stack balanced

  return 0;
}

Sergey Lyubka's avatar
Sergey Lyubka committed
static void lsp(struct connection *conn, const char *p, int len, lua_State *L) {
  int i, j, pos = 0;

  for (i = 0; i < len; i++) {
    if (p[i] == '<' && p[i + 1] == '?') {
      for (j = i + 1; j < len ; j++) {
        if (p[j] == '?' && p[j + 1] == '>') {
          mg_write(&conn->mg_conn, p + pos, i - pos);
          if (luaL_loadbuffer(L, p + (i + 2), j - (i + 2), "") == 0) {
Sergey Lyubka's avatar
Sergey Lyubka committed
            lua_pcall(L, 0, LUA_MULTRET, 0);
          }
          pos = j + 2;
          i = pos - 1;
          break;
        }
      }
    }
  }
  if (i > pos) mg_write(&conn->mg_conn, p + pos, i - pos);
}
Sergey Lyubka's avatar
Sergey Lyubka committed
static void handle_lsp_request(struct connection *conn, const char *path,
                               file_stat_t *st) {
  void *p = NULL;
  lua_State *L = NULL;
  FILE *fp = NULL;

  if ((fp = fopen(path, "r")) == NULL ||
      (p = mmap(NULL, st->st_size, PROT_READ, MAP_PRIVATE,
                fileno(fp), 0)) == MAP_FAILED ||
      (L = luaL_newstate()) == NULL) {
    send_http_error(conn, 500, "mmap(%s): %s", path, strerror(errno));
Sergey Lyubka's avatar
Sergey Lyubka committed
  } else {
    // We're not sending HTTP headers here, Lua page must do it.
    prepare_lua_environment(&conn->mg_conn, L);
    lua_pushcclosure(L, &lua_error_handler, 0);
    lua_pushvalue(L, LUA_GLOBALSINDEX);
Sergey Lyubka's avatar
Sergey Lyubka committed
    close_local_endpoint(conn);
Sergey Lyubka's avatar
Sergey Lyubka committed

  if (L != NULL) lua_close(L);
  if (p != NULL) munmap(p, st->st_size);
  if (fp != NULL) fclose(fp);
#endif // MONGOOSE_USE_LUA
static void open_local_endpoint(struct connection *conn, int skip_user) {
#ifndef MONGOOSE_NO_FILESYSTEM
  static const char lua_pat[] = LUA_SCRIPT_PATTERN;
  file_stat_t st;
Sergey Lyubka's avatar
Sergey Lyubka committed
  char path[MAX_PATH_SIZE];
  int exists = 0, is_directory = 0;
#ifndef MONGOOSE_NO_CGI
  const char *cgi_pat = conn->server->config_options[CGI_PATTERN];
#else
  const char *cgi_pat = DEFAULT_CGI_PATTERN;
#endif
#ifndef MONGOOSE_NO_DIRECTORY_LISTING
  const char *dir_lst = conn->server->config_options[ENABLE_DIRECTORY_LISTING];
#else
  const char *dir_lst = "yes";
#endif
Sergey Lyubka's avatar
Sergey Lyubka committed
#endif
#ifndef MONGOOSE_NO_AUTH
  // Call auth handler
  if (conn->server->auth_handler != NULL &&
      conn->server->auth_handler(&conn->mg_conn) == MG_AUTH_FAIL) {
    mg_send_digest_auth_request(&conn->mg_conn);
    return;
  }
  // Call URI handler if one is registered for this URI
  if (skip_user == 0 && conn->server->request_handler != NULL) {
    conn->endpoint_type = EP_USER;
#if MONGOOSE_USE_POST_SIZE_LIMIT > 1
    {
      const char *cl = mg_get_header(&conn->mg_conn, "Content-Length");
      if (!strcmp(conn->mg_conn.request_method, "POST") &&
          (cl == NULL || to64(cl) > MONGOOSE_USE_POST_SIZE_LIMIT)) {
        send_http_error(conn, 500, "POST size > %zu",
                        (size_t) MONGOOSE_USE_POST_SIZE_LIMIT);
#ifdef MONGOOSE_NO_FILESYSTEM
  if (!strcmp(conn->mg_conn.request_method, "OPTIONS")) {
    send_options(conn);
  } else {
    send_http_error(conn, 404, NULL);
  }
Sergey Lyubka's avatar
Sergey Lyubka committed
#else
  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, NULL);
#ifndef MONGOOSE_NO_AUTH
  } else if ((!is_dav_mutation(conn) && !is_authorized(conn, path)) ||
             (is_dav_mutation(conn) && !is_authorized_for_dav(conn))) {
    mg_send_digest_auth_request(&conn->mg_conn);
    close_local_endpoint(conn);
Sergey Lyubka's avatar
Sergey Lyubka committed
#endif
#ifndef MONGOOSE_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, NULL);
  } else if (is_directory &&
             conn->mg_conn.uri[strlen(conn->mg_conn.uri) - 1] != '/') {
Sergey Lyubka's avatar
Sergey Lyubka committed
    conn->mg_conn.status_code = 301;
    mg_printf(&conn->mg_conn, "HTTP/1.1 301 Moved Permanently\r\n"
              "Location: %s/\r\n\r\n", conn->mg_conn.uri);
    close_local_endpoint(conn);
  } else if (is_directory && !find_index_file(conn, path, sizeof(path), &st)) {
    if (!mg_strcasecmp(dir_lst, "yes")) {
#ifndef MONGOOSE_NO_DIRECTORY_LISTING
      send_directory_listing(conn, path);
#else
      send_http_error(conn, 501, NULL);
      send_http_error(conn, 403, NULL);
  } else if (match_prefix(lua_pat, sizeof(lua_pat) - 1, path) > 0) {
#ifdef MONGOOSE_USE_LUA
Sergey Lyubka's avatar
Sergey Lyubka committed
    handle_lsp_request(conn, path, &st);
#else
    send_http_error(conn, 501, NULL);
#endif
  } else if (match_prefix(cgi_pat, strlen(cgi_pat), path) > 0) {
#if !defined(MONGOOSE_NO_CGI)
    open_cgi_endpoint(conn, path);
    send_http_error(conn, 501, NULL);
#endif // !MONGOOSE_NO_CGI
  } else if (is_not_modified(conn, &st)) {
    send_http_error(conn, 304, NULL);
  } else if ((conn->endpoint.fd = open(path, O_RDONLY | O_BINARY)) != -1) {
    // O_BINARY is required for Windows, otherwise in default text mode
    // two bytes \r\n will be read as one.
    open_file_endpoint(conn, path, &st);
  } else {
    send_http_error(conn, 404, NULL);
#endif  // MONGOOSE_NO_FILESYSTEM
static void send_continue_if_expected(struct connection *conn) {
  static const char expect_response[] = "HTTP/1.1 100 Continue\r\n\r\n";
  const char *expect_hdr = mg_get_header(&conn->mg_conn, "Expect");
  if (expect_hdr != NULL && !mg_strcasecmp(expect_hdr, "100-continue")) {
    spool(&conn->remote_iobuf, expect_response, sizeof(expect_response) - 1);
static int is_valid_uri(const char *uri) {
  // Conform to http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
  // URI can be an asterisk (*) or should start with slash.
  return uri[0] == '/' || (uri[0] == '*' && uri[1] == '\0');
}
static void try_http_parse_and_set_content_length(struct connection *conn) {
  struct iobuf *io = &conn->local_iobuf;

  if (conn->request_len == 0 &&
      (conn->request_len = get_request_len(io->buf, io->len)) > 0) {
    // If request is buffered in, remove it from the iobuf. This is because
    // iobuf could be reallocated, and pointers in parsed request could
    // become invalid.
    conn->request = (char *) malloc(conn->request_len);
    memcpy(conn->request, io->buf, conn->request_len);
    DBG(("%p [%.*s]", conn, conn->request_len, conn->request));
    discard_leading_iobuf_bytes(io, conn->request_len);
    conn->request_len = parse_http_message(conn->request, conn->request_len,
                                           &conn->mg_conn);
    if (conn->request_len > 0) {
      const char *cl_hdr = mg_get_header(&conn->mg_conn, "Content-Length");
      conn->cl = cl_hdr == NULL ? 0 : to64(cl_hdr);
      conn->mg_conn.content_len = (long int) conn->cl;
    }
static void process_request(struct connection *conn) {
  struct iobuf *io = &conn->local_iobuf;

  try_http_parse_and_set_content_length(conn);
  DBG(("%p %d %d %d [%.*s]", conn, conn->request_len, io->len, conn->flags,
       io->len, io->buf));
  if (conn->request_len < 0 ||
      (conn->request_len > 0 && !is_valid_uri(conn->mg_conn.uri))) {
    send_http_error(conn, 400, NULL);
  } else if (conn->request_len == 0 && io->len > MAX_REQUEST_SIZE) {
    send_http_error(conn, 413, NULL);
  } else if (conn->request_len > 0 &&
             strcmp(conn->mg_conn.http_version, "1.0") != 0 &&
             strcmp(conn->mg_conn.http_version, "1.1") != 0) {
    send_http_error(conn, 505, NULL);
  } else if (conn->request_len > 0 && conn->endpoint_type == EP_NONE) {
#ifndef MONGOOSE_NO_WEBSOCKET
    send_websocket_handshake_if_requested(&conn->mg_conn);
#endif
    send_continue_if_expected(conn);
    open_local_endpoint(conn, 0);
#ifndef MONGOOSE_NO_CGI
  if (conn->endpoint_type == EP_CGI && io->len > 0) {
    forward_post_data(conn);
  }
#endif
  if (conn->endpoint_type == EP_USER) {
    call_request_handler_if_data_is_buffered(conn);
#ifndef MONGOOSE_NO_DAV
  if (conn->endpoint_type == EP_PUT && io->len > 0) {
    forward_put_data(conn);
static void call_http_client_handler(struct connection *conn, int code) {
  conn->mg_conn.status_code = code;
  // For responses without Content-Lengh, use the whole buffer
  if (conn->cl == 0 && code == MG_DOWNLOAD_SUCCESS) {
    conn->mg_conn.content_len = conn->local_iobuf.len;
  }
  conn->mg_conn.content = conn->local_iobuf.buf;
  if (conn->handler(&conn->mg_conn) || code == MG_CONNECT_FAILURE ||
      code == MG_DOWNLOAD_FAILURE) {
    conn->flags |= CONN_CLOSE;
  }
  discard_leading_iobuf_bytes(&conn->local_iobuf, conn->mg_conn.content_len);
  conn->flags = conn->mg_conn.status_code = 0;
  conn->cl = conn->num_bytes_sent = conn->request_len = 0;
  free(conn->request);
  conn->request = NULL;
}

static void process_response(struct connection *conn) {
  struct iobuf *io = &conn->local_iobuf;

  try_http_parse_and_set_content_length(conn);
  DBG(("%p %d %d [%.*s]", conn, conn->request_len, io->len,
       io->len > 40 ? 40 : io->len, io->buf));
  if (conn->request_len < 0 ||
      (conn->request_len == 0 && io->len > MAX_REQUEST_SIZE)) {
    call_http_client_handler(conn, MG_DOWNLOAD_FAILURE);
  }
  if (io->len >= conn->cl) {
    call_http_client_handler(conn, MG_DOWNLOAD_SUCCESS);
  }
}

static void read_from_socket(struct connection *conn) {
  char buf[IOBUF_SIZE];
  int n = 0;
  if (conn->endpoint_type == EP_CLIENT && conn->flags & CONN_CONNECTING) {
    callback_http_client_on_connect(conn);
    return;
  }

#ifdef MONGOOSE_USE_SSL
Sergey Lyubka's avatar
Sergey Lyubka committed
  if (conn->ssl != NULL) {
    if (conn->flags & CONN_SSL_HANDS_SHAKEN) {
      n = SSL_read(conn->ssl, buf, sizeof(buf));
      if (SSL_accept(conn->ssl) == 1) {
        conn->flags |= CONN_SSL_HANDS_SHAKEN;
      }
      return;
Sergey Lyubka's avatar
Sergey Lyubka committed
  } else
Sergey Lyubka's avatar
Sergey Lyubka committed
  {
    n = recv(conn->client_sock, buf, sizeof(buf), 0);
  }
  DBG(("%p %d %d (1)", conn, n, conn->flags));

#ifdef MONGOOSE_HEXDUMP
  hexdump(conn, buf, n, "<-");
  if (is_error(n)) {
    if (conn->endpoint_type == EP_CLIENT && conn->local_iobuf.len > 0) {
      call_http_client_handler(conn, MG_DOWNLOAD_SUCCESS);
    }
    conn->flags |= CONN_CLOSE;
  } else if (n > 0) {
    spool(&conn->local_iobuf, buf, n);
    if (conn->endpoint_type == EP_CLIENT) {
      process_response(conn);
    } else {
      process_request(conn);
    }
  DBG(("%p %d %d (2)", conn, n, conn->flags));
int mg_connect(struct mg_server *server, const char *host, int port,
               int use_ssl, mg_handler_t handler, void *param) {
Sergey Lyubka's avatar
Sergey Lyubka committed
  sock_t sock = INVALID_SOCKET;
  struct sockaddr_in sin;
  struct hostent *he = NULL;
  struct connection *conn = NULL;
  int connect_ret_val;

  if (host == NULL || (he = gethostbyname(host)) == NULL ||
      (sock = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) return 0;
#ifndef MONGOOSE_USE_SSL
  if (use_ssl) return 0;
#endif
Sergey Lyubka's avatar
Sergey Lyubka committed

  sin.sin_family = AF_INET;
  sin.sin_port = htons((uint16_t) port);
  sin.sin_addr = * (struct in_addr *) he->h_addr_list[0];
  set_non_blocking_mode(sock);

  connect_ret_val = connect(sock, (struct sockaddr *) &sin, sizeof(sin));
  if (is_error(connect_ret_val)) {
Sergey Lyubka's avatar
Sergey Lyubka committed
    return 0;
  } else if ((conn = (struct connection *) calloc(1, sizeof(*conn))) == NULL) {
    closesocket(sock);
    return 0;
  }

Sergey Lyubka's avatar
Sergey Lyubka committed
  conn->server = server;
Sergey Lyubka's avatar
Sergey Lyubka committed
  conn->client_sock = sock;
  conn->endpoint_type = EP_CLIENT;
  conn->handler = handler;
  conn->mg_conn.server_param = server->server_data;
  conn->mg_conn.connection_param = param;
  conn->birth_time = conn->last_activity_time = time(NULL);
  conn->flags = CONN_CONNECTING;
  conn->mg_conn.status_code = MG_CONNECT_FAILURE;
#ifdef MONGOOSE_USE_SSL
  if (use_ssl && (conn->ssl = SSL_new(server->client_ssl_ctx)) != NULL) {
    SSL_set_fd(conn->ssl, sock);
  }
#endif
Sergey Lyubka's avatar
Sergey Lyubka committed
  LINKED_LIST_ADD_TO_FRONT(&server->active_connections, &conn->link);
  DBG(("%p %s:%d", conn, host, port));
#ifndef MONGOOSE_NO_LOGGING
static void log_header(const struct mg_connection *conn, const char *header,
                       FILE *fp) {
  const char *header_value;
  if ((header_value = mg_get_header(conn, header)) == NULL) {
    (void) fprintf(fp, "%s", " -");
  } else {
    (void) fprintf(fp, " \"%s\"", header_value);
static void log_access(const struct connection *conn, const char *path) {
  const struct mg_connection *c = &conn->mg_conn;
  FILE *fp = (path == NULL) ?  NULL : fopen(path, "a+");
Sergey Lyubka's avatar
Sergey Lyubka committed
  char date[64], user[100];
  if (fp == NULL) return;
  strftime(date, sizeof(date), "%d/%b/%Y:%H:%M:%S %z",
           localtime(&conn->birth_time));
  flockfile(fp);
  mg_parse_header(mg_get_header(&conn->mg_conn, "Authorization"), "username",
                  user, sizeof(user));
  fprintf(fp, "%s - %s [%s] \"%s %s HTTP/%s\" %d %" INT64_FMT,
Sergey Lyubka's avatar
Sergey Lyubka committed
          c->remote_ip, user[0] == '\0' ? "-" : user, date,
          c->request_method ? c->request_method : "-",
          c->uri ? c->uri : "-", c->http_version,
          c->status_code, conn->num_bytes_sent);
  log_header(c, "Referer", fp);
  log_header(c, "User-Agent", fp);
  fputc('\n', fp);
  fflush(fp);
  funlockfile(fp);
  fclose(fp);
static void close_local_endpoint(struct connection *conn) {
  struct mg_connection *c = &conn->mg_conn;
  // Must be done before free()
  int keep_alive = should_keep_alive(&conn->mg_conn) &&
    (conn->endpoint_type == EP_FILE || conn->endpoint_type == EP_USER);
  DBG(("%p %d %d %d", conn, conn->endpoint_type, keep_alive, conn->flags));
  switch (conn->endpoint_type) {
    case EP_PUT: close(conn->endpoint.fd); break;
    case EP_FILE: close(conn->endpoint.fd); break;
    case EP_CGI: closesocket(conn->endpoint.cgi_sock); break;
    default: break;
  }
#ifndef MONGOOSE_NO_LOGGING
  if (c->status_code > 0 && conn->endpoint_type != EP_CLIENT &&
      c->status_code != 400) {
    log_access(conn, conn->server->config_options[ACCESS_LOG_FILE]);
  }
#endif
  // Gobble possible POST data sent to the URI handler
  discard_leading_iobuf_bytes(&conn->local_iobuf, conn->mg_conn.content_len);
  conn->endpoint_type = EP_NONE;
  conn->cl = conn->num_bytes_sent = conn->request_len = conn->flags = 0;
  c->request_method = c->uri = c->http_version = c->query_string = NULL;
  c->num_headers = c->status_code = c->is_websocket = c->content_len = 0;
  free(conn->request);
  conn->request = NULL;
  if (keep_alive) {
    process_request(conn);  // Can call us recursively if pipelining is used
  } else {
    conn->flags |= conn->remote_iobuf.len == 0 ? CONN_CLOSE : CONN_SPOOL_DONE;
static void transfer_file_data(struct connection *conn) {
  char buf[IOBUF_SIZE];
Sergey Lyubka's avatar
Sergey Lyubka committed
  int n = read(conn->endpoint.fd, buf, conn->cl < (int64_t) sizeof(buf) ?
               (int) conn->cl : (int) sizeof(buf));
  if (is_error(n)) {
    close_local_endpoint(conn);
  } else if (n > 0) {
    conn->cl -= n;
    spool(&conn->remote_iobuf, buf, n);
    if (conn->cl <= 0) {
      close_local_endpoint(conn);
void add_to_set(sock_t sock, fd_set *set, sock_t *max_fd) {
  FD_SET(sock, set);
  if (sock > *max_fd) {
    *max_fd = sock;
unsigned int mg_poll_server(struct mg_server *server, int milliseconds) {
  struct ll *lp, *tmp;
  struct connection *conn;
  struct timeval tv;
  fd_set read_set, write_set;
  sock_t max_fd = -1;
  time_t current_time = time(NULL), expire_time = current_time -
    MONGOOSE_USE_IDLE_TIMEOUT_SECONDS;
  if (server->listening_sock == INVALID_SOCKET) return 0;
  FD_ZERO(&read_set);
  FD_ZERO(&write_set);
  add_to_set(server->listening_sock, &read_set, &max_fd);
#ifndef MONGOOSE_NO_SOCKETPAIR
  add_to_set(server->ctl[1], &read_set, &max_fd);

  LINKED_LIST_FOREACH(&server->active_connections, lp, tmp) {
    conn = LINKED_LIST_ENTRY(lp, struct connection, link);
    add_to_set(conn->client_sock, &read_set, &max_fd);
    if (conn->endpoint_type == EP_CLIENT && (conn->flags & CONN_CONNECTING)) {
      add_to_set(conn->client_sock, &write_set, &max_fd);
    }
    if (conn->endpoint_type == EP_FILE) {
      transfer_file_data(conn);
    } else if (conn->endpoint_type == EP_CGI) {
      add_to_set(conn->endpoint.cgi_sock, &read_set, &max_fd);
    }
Sergey Lyubka's avatar
Sergey Lyubka committed
    if (conn->remote_iobuf.len > 0 && !(conn->flags & CONN_BUFFER)) {
      add_to_set(conn->client_sock, &write_set, &max_fd);
    } else if (conn->flags & CONN_CLOSE) {
      close_conn(conn);
  tv.tv_sec = milliseconds / 1000;
  tv.tv_usec = (milliseconds % 1000) * 1000;
  if (select(max_fd + 1, &read_set, &write_set, NULL, &tv) > 0) {
    // Accept new connections
    if (FD_ISSET(server->listening_sock, &read_set)) {
Sergey Lyubka's avatar
Sergey Lyubka committed
      // We're not looping here, and accepting just one connection at
      // a time. The reason is that eCos does not respect non-blocking
      // flag on a listening socket and hangs in a loop.
      if ((conn = accept_new_connection(server)) != NULL) {
        conn->birth_time = conn->last_activity_time = current_time;
      }
    }

    // Read/write from clients
    LINKED_LIST_FOREACH(&server->active_connections, lp, tmp) {
      conn = LINKED_LIST_ENTRY(lp, struct connection, link);
      if (FD_ISSET(conn->client_sock, &read_set)) {
        conn->last_activity_time = current_time;
        read_from_socket(conn);
#ifndef MONGOOSE_NO_CGI
      if (conn->endpoint_type == EP_CGI &&
          FD_ISSET(conn->endpoint.cgi_sock, &read_set)) {
        read_from_cgi(conn);
      if (FD_ISSET(conn->client_sock, &write_set)) {
        if (conn->endpoint_type == EP_CLIENT &&
            (conn->flags & CONN_CONNECTING)) {
          read_from_socket(conn);
        } else if (!(conn->flags & CONN_BUFFER)) {
          conn->last_activity_time = current_time;
          write_to_socket(conn);
        }
  // Close expired connections and those that need to be closed
  LINKED_LIST_FOREACH(&server->active_connections, lp, tmp) {
    conn = LINKED_LIST_ENTRY(lp, struct connection, link);
    if (conn->mg_conn.is_websocket) {
      ping_idle_websocket_connection(conn, current_time);
    }
    if (conn->flags & CONN_LONG_RUNNING) {
      conn->mg_conn.wsbits = conn->flags & CONN_CLOSE ? 1 : 0;
      if (call_request_handler(conn) == MG_REQUEST_PROCESSED) {
        conn->flags |= conn->remote_iobuf.len == 0 ? CONN_CLOSE : CONN_SPOOL_DONE;
    if (conn->flags & CONN_CLOSE || conn->last_activity_time < expire_time) {
      close_conn(conn);
    }
void mg_destroy_server(struct mg_server **server) {
  struct ll *lp, *tmp;

  if (server != NULL && *server != NULL) {
    struct mg_server *s = *server;
    // Do one last poll, see https://github.com/cesanta/mongoose/issues/286
    mg_poll_server(s, 0);
    closesocket(s->listening_sock);
#ifndef MONGOOSE_NO_SOCKETPAIR
    closesocket(s->ctl[0]);
    closesocket(s->ctl[1]);
    LINKED_LIST_FOREACH(&s->active_connections, lp, tmp) {
      close_conn(LINKED_LIST_ENTRY(lp, struct connection, link));
    for (i = 0; i < (int) ARRAY_SIZE(s->config_options); i++) {
      free(s->config_options[i]);  // It is OK to free(NULL)
#ifdef MONGOOSE_USE_SSL
    if (s->ssl_ctx != NULL) SSL_CTX_free((*server)->ssl_ctx);
    if (s->client_ssl_ctx != NULL) SSL_CTX_free(s->client_ssl_ctx);
Sergey Lyubka's avatar
Sergey Lyubka committed
#endif
    free(s);
    *server = NULL;
// Apply function to all active connections.
void mg_iterate_over_connections(struct mg_server *server, mg_handler_t handler,
  struct ll *lp, *tmp;
  struct connection *conn;

  LINKED_LIST_FOREACH(&server->active_connections, lp, tmp) {
    conn = LINKED_LIST_ENTRY(lp, struct connection, link);
    conn->mg_conn.callback_param = param;