Skip to content
Snippets Groups Projects
mongoose.c 129 KiB
Newer Older
    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 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 = 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 = 0;

  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++) {
Sergey Lyubka's avatar
Sergey Lyubka committed
    if ((s == str || s[-1] == ' ') && s[n] == '=' &&
        !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;
Sergey Lyubka's avatar
Sergey Lyubka committed
    while (p < end && 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--;
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_5.2.1.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");
  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), "") == LUA_OK) {
            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_pushglobaltable(L);
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) {
#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;
  const char *cgi_pat = conn->server->config_options[CGI_PATTERN];
  const char *dir_lst = conn->server->config_options[ENABLE_DIRECTORY_LISTING];
Sergey Lyubka's avatar
Sergey Lyubka committed
#endif
  // 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;
#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);
#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_uri_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));
  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;
  }

  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) {
  // 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 (conn->mg_conn.status_code > 0 && conn->endpoint_type != EP_CLIENT &&
      conn->mg_conn.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->flags = conn->mg_conn.status_code = 0;
  conn->cl = conn->num_bytes_sent = conn->request_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);
static void execute_iteration(struct mg_server *server) {
  struct ll *lp, *tmp;
  struct connection *conn;
  union { mg_handler_t f; void *p; } msg[2];

  recv(server->ctl[1], (void *) msg, sizeof(msg), 0);
  LINKED_LIST_FOREACH(&server->active_connections, lp, tmp) {
    conn = LINKED_LIST_ENTRY(lp, struct connection, link);
    conn->mg_conn.connection_param = msg[1].p;
    msg[0].f(&conn->mg_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);
  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) {
    if (FD_ISSET(server->ctl[1], &read_set)) {
      execute_iteration(server);
    // Accept new connections
    if (FD_ISSET(server->listening_sock, &read_set)) {
      while ((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;
      call_uri_handler(conn);
    }
    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);
    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));
    LINKED_LIST_FOREACH(&s->uri_handlers, lp, tmp) {
      free(LINKED_LIST_ENTRY(lp, struct uri_handler, link)->uri);
      free(LINKED_LIST_ENTRY(lp, struct uri_handler, 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,
                                 void *param) {
  // Send closure (function + parameter) to the IO thread to execute
  union { mg_handler_t f; void *p; } msg[2];
  msg[0].f = handler;
  msg[1].p = param;
  send(server->ctl[0], (void *) msg, sizeof(msg), 0);
void mg_add_uri_handler(struct mg_server *server, const char *uri,
                        mg_handler_t handler) {
  struct uri_handler *p = (struct uri_handler *) malloc(sizeof(*p));
  if (p != NULL) {
    LINKED_LIST_ADD_TO_TAIL(&server->uri_handlers, &p->link);
    p->uri = mg_strdup(uri);
    p->handler = handler;
static int get_var(const char *data, size_t data_len, const char *name,
                   char *dst, size_t dst_len) {
  const char *p, *e, *s;
  size_t name_len;
  int len;
  if (dst == NULL || dst_len == 0) {
    len = -2;
  } else if (data == NULL || name == NULL || data_len == 0) {
    len = -1;
    dst[0] = '\0';
  } else {
    name_len = strlen(name);
    e = data + data_len;
    len = -1;
    dst[0] = '\0';
    // data is "var1=val1&var2=val2...". Find variable first
    for (p = data; p + name_len < e; p++) {
      if ((p == data || p[-1] == '&') && p[name_len] == '=' &&
          !mg_strncasecmp(name, p, name_len)) {
        // Point p to variable value
        p += name_len + 1;
        // Point s to the end of the value
        s = (const char *) memchr(p, '&', (size_t)(e - p));
        if (s == NULL) {
          s = e;
        }
        assert(s >= p);
        // Decode variable into destination buffer
        len = mg_url_decode(p, (size_t)(s - p), dst, dst_len, 1);
        // Redirect error code from -1 to -2 (destination buffer too small).
        if (len == -1) {
          len = -2;
        }
        break;
      }
    }
  }
int mg_get_var(const struct mg_connection *conn, const char *name,
               char *dst, size_t dst_len) {
  int len = get_var(conn->query_string, conn->query_string == NULL ? 0 :
                    strlen(conn->query_string), name, dst, dst_len);
  if (len < 0) {
    len = get_var(conn->content, conn->content_len, name, dst, dst_len);
Sergey Lyubka's avatar
Sergey Lyubka committed
static int get_line_len(const char *buf, int buf_len) {
  int len = 0;
  while (len < buf_len && buf[len] != '\n') len++;
  return buf[len] == '\n' ? len + 1: -1;
}

int mg_parse_multipart(const char *buf, int buf_len,
                       char *var_name, int var_name_len,
                       char *file_name, int file_name_len,
                       const char **data, int *data_len) {
  static const char cd[] = "Content-Disposition: ";
  //struct mg_connection c;
  int hl, bl, n, ll, pos, cdl = sizeof(cd) - 1;
  //char *p;

  if (buf == NULL || buf_len <= 0) return 0;
  if ((hl = get_request_len(buf, buf_len)) <= 0) return 0;
  if (buf[0] != '-' || buf[1] != '-' || buf[2] == '\n') return 0;

  // Get boundary length
  bl = get_line_len(buf, buf_len);

  // Loop through headers, fetch variable name and file name
  var_name[0] = file_name[0] = '\0';
  for (n = bl; (ll = get_line_len(buf + n, hl - n)) > 0; n += ll) {
    if (mg_strncasecmp(cd, buf + n, cdl) == 0) {
      parse_header(buf + n + cdl, ll - (cdl + 2), "name",
                   var_name, var_name_len);
      parse_header(buf + n + cdl, ll - (cdl + 2), "filename",
                   file_name, file_name_len);
    }
  }

  // Scan body, search for terminating boundary
  for (pos = hl; pos + (bl - 2) < buf_len; pos++) {
    if (buf[pos] == '-' && !memcmp(buf, &buf[pos], bl - 2)) {
      if (data_len != NULL) *data_len = (pos - 2) - hl;
      if (data != NULL) *data = buf + hl;
      return pos;
    }
  }

  return 0;