Skip to content
Snippets Groups Projects
mongoose.c 154 KiB
Newer Older
    fclose(fp);
    close_local_endpoint(conn);
  }
}
#endif

Daniel O'Connell's avatar
Daniel O'Connell committed
static void proxy_request(struct ns_connection *pc, struct mg_connection *c) {
  int i, sent_close_header = 0;

  ns_printf(pc, "%s %s HTTP/%s\r\n", c->request_method, c->uri,
            c->http_version);
  for (i = 0; i < c->num_headers; i++) {
    if (mg_strcasecmp(c->http_headers[i].name, "Connection") == 0) {
      // Force connection close, cause we don't parse proxy replies
      // therefore we don't know message boundaries
      //ns_printf(pc, "%s: %s\r\n", "Connection", "close");
      sent_close_header = 1;
    //} else {
    }
      ns_printf(pc, "%s: %s\r\n", c->http_headers[i].name,
                c->http_headers[i].value);
  }
  if (!sent_close_header) {
    ns_printf(pc, "%s: %s\r\n", "Connection", "close");
  }
  ns_printf(pc, "%s", "\r\n");
  ns_send(pc, c->content, c->content_len);

}

static void proxify_connection(struct connection *conn) {
  char proto[10], host[500], cert[500];
Daniel O'Connell's avatar
Daniel O'Connell committed
  unsigned short port = 80;
  struct mg_connection *c = &conn->mg_conn;
  struct ns_server *server = &conn->server->ns_server;
Sergey Lyubka's avatar
Sergey Lyubka committed
  struct ns_connection *pc = NULL;
  int n = 0, use_ssl = 0;
  const char *url = c->uri;
  proto[0] = host[0] = cert[0] = '\0';
Sergey Lyubka's avatar
Sergey Lyubka committed
  if (sscanf(url, "%499[^: ]:%hu%n", host, &port, &n) != 2 &&
      sscanf(url, "%9[a-z]://%499[^: ]:%hu%n", proto, host, &port, &n) != 3 &&
      sscanf(url, "%9[a-z]://%499[^/ ]%n", proto, host, &n) != 2) {
    n = 0;
  }

#ifdef NS_ENABLE_SSL
  // Find out whether we should be in the MITM mode
  {
    const char *certs = conn->server->config_options[SSL_MITM_CERTS];
    int host_len = strlen(host);
    struct vec a, b;

    while ((certs = next_option(certs, &a, &b)) != NULL) {
      if (a.len == host_len && mg_strncasecmp(a.ptr, host, a.len) == 0) {
        snprintf(cert, sizeof(cert), "%.*s", b.len, b.ptr);
        break;
      }
    }
  }
#endif

Daniel O'Connell's avatar
Daniel O'Connell committed
  use_ssl = port != 80 && cert[0] != '\0';
  if (n > 0 &&
Daniel O'Connell's avatar
Daniel O'Connell committed
      (pc = ns_connect(server, host, port, use_ssl, conn)) != NULL) {
    // Interlink two connections
    pc->flags |= MG_PROXY_CONN;
    conn->endpoint_type = EP_PROXY;
    conn->endpoint.nc = pc;
Daniel O'Connell's avatar
Daniel O'Connell committed
    DBG(("%p [%s] -> %p %d", conn, c->uri, pc, use_ssl));

    if (strcmp(c->request_method, "CONNECT") == 0) {
      // For CONNECT request, reply with 200 OK. Tunnel is established.
      mg_printf(c, "%s", "HTTP/1.1 200 OK\r\n\r\n");
Daniel O'Connell's avatar
Daniel O'Connell committed
      conn->request_len = 0;
      free(conn->request);
      conn->request = NULL;
#ifdef NS_ENABLE_SSL
Daniel O'Connell's avatar
Daniel O'Connell committed
      if (use_ssl) {
Daniel O'Connell's avatar
Daniel O'Connell committed
        DBG(("%s", "Triggering MITM mode: terminating SSL connection"));
        SSL_library_init();
        ctx = SSL_CTX_new(SSLv23_server_method());

        if (ctx == NULL) {
          pc->flags |= NSF_CLOSE_IMMEDIATELY;
        } else {
          SSL_CTX_use_certificate_file(ctx, cert, 1);
          SSL_CTX_use_PrivateKey_file(ctx, cert, 1);
          SSL_CTX_use_certificate_chain_file(ctx, cert);

Daniel O'Connell's avatar
Daniel O'Connell committed
          // When clear-text reply is pushed to client, switch to SSL mode.
          n = send(conn->ns_conn->sock, conn->ns_conn->send_iobuf.buf,
                   conn->ns_conn->send_iobuf.len, 0);
          DBG(("%p %lu %d SEND", c, conn->ns_conn->send_iobuf.len, n));
          conn->ns_conn->send_iobuf.len = 0;
          if ((conn->ns_conn->ssl = SSL_new(ctx)) != NULL) {
            //SSL_set_fd((SSL *) c->connection_param, conn->ns_conn->sock);
            SSL_set_fd(conn->ns_conn->ssl, conn->ns_conn->sock);
          }
          SSL_CTX_free(ctx);
        }
      }
#endif
    } else {
      // For other methods, forward the request to the target host.
Daniel O'Connell's avatar
Daniel O'Connell committed
      c->uri += n;
      proxy_request(pc, c);
    }
  } else {
    conn->ns_conn->flags |= NSF_CLOSE_IMMEDIATELY;
  }
}

#ifndef MONGOOSE_NO_FILESYSTEM
void mg_send_file(struct mg_connection *c, const char *file_name) {
  struct connection *conn = MG_CONN_2_CONN(c);
  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

  mg_snprintf(path, sizeof(path), "%s", file_name);
  exists = stat(path, &st) == 0;
  is_directory = S_ISDIR(st.st_mode);
  
  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] != '/') {
    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);
#endif
    } else {
      send_http_error(conn, 403, NULL);
    }
  } else if (mg_match_prefix(cgi_pat, strlen(cgi_pat), path) > 0) {
#if !defined(MONGOOSE_NO_CGI)
    open_cgi_endpoint(conn, path);
#else
    send_http_error(conn, 501, NULL);
#endif // !MONGOOSE_NO_CGI
#ifndef MONGOOSE_NO_SSI
  } else if (mg_match_prefix(conn->server->config_options[SSI_PATTERN],
                             strlen(conn->server->config_options[SSI_PATTERN]),
                             path) > 0) {
    handle_ssi_request(conn, path);
Sergey Lyubka's avatar
Sergey Lyubka committed
#endif
  } 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 open_local_endpoint(struct connection *conn, int skip_user) {
#ifndef MONGOOSE_NO_FILESYSTEM
  char path[MAX_PATH_SIZE];
  file_stat_t st;
  int exists = 0;
  // If EP_USER was set in a prev call, reset it
  conn->endpoint_type = EP_NONE;

#ifndef MONGOOSE_NO_AUTH
  if (conn->server->event_handler && call_user(conn, MG_AUTH) == MG_FALSE) {
    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->event_handler != NULL) {
    conn->endpoint_type = EP_USER;
Sergey Lyubka's avatar
Sergey Lyubka committed
#if MONGOOSE_POST_SIZE_LIMIT > 1
    {
      const char *cl = mg_get_header(&conn->mg_conn, "Content-Length");
      if ((strcmp(conn->mg_conn.request_method, "POST") == 0 ||
           strcmp(conn->mg_conn.request_method, "PUT") == 0) &&
Sergey Lyubka's avatar
Sergey Lyubka committed
          (cl == NULL || to64(cl) > MONGOOSE_POST_SIZE_LIMIT)) {
        send_http_error(conn, 500, "POST size > %zu",
Sergey Lyubka's avatar
Sergey Lyubka committed
                        (size_t) MONGOOSE_POST_SIZE_LIMIT);
  if (strcmp(conn->mg_conn.request_method, "CONNECT") == 0 ||
      memcmp(conn->mg_conn.uri, "http", 4) == 0) {
    proxify_connection(conn);
    return;
  }
  if (!strcmp(conn->mg_conn.request_method, "OPTIONS")) {
    send_options(conn);
  
#ifdef MONGOOSE_NO_FILESYSTEM
  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);
  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_request(conn) && !is_authorized(conn, path)) ||
             (is_dav_request(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, exists);
  } 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
#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")) {
Sergey Lyubka's avatar
Sergey Lyubka committed
    ns_send(conn->ns_conn, expect_response, sizeof(expect_response) - 1);
// Conform to http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
static int is_valid_uri(const char *uri) {
  unsigned short n;
  return uri[0] == '/' ||
    strcmp(uri, "*") == 0 ||            // OPTIONS method can use asterisk URI
    memcmp(uri, "http", 4) == 0 ||      // Naive check for the absolute URI
    sscanf(uri, "%*[^ :]:%hu", &n) > 0; // CONNECT method can use host:port
Sergey Lyubka's avatar
Sergey Lyubka committed
static void try_parse(struct connection *conn) {
  struct iobuf *io = &conn->ns_conn->recv_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));
Sergey Lyubka's avatar
Sergey Lyubka committed
    iobuf_remove(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);
Sergey Lyubka's avatar
Sergey Lyubka committed
      conn->mg_conn.content_len = (size_t) conn->cl;
Daniel O'Connell's avatar
Daniel O'Connell committed
static void do_proxy(struct connection *conn) {
  if (conn->request_len == 0) {
    try_parse(conn);
    DBG(("%p parsing -> %d", conn, conn->request_len));
    if (conn->request_len > 0 && call_user(conn, MG_REQUEST) == MG_FALSE) {
Daniel O'Connell's avatar
Daniel O'Connell committed
      proxy_request(conn->endpoint.nc, &conn->mg_conn);
    } else if (conn->request_len < 0) {
      ns_forward(conn->ns_conn, conn->endpoint.nc);
    }
  } else {
    DBG(("%p forwarding", conn));
    ns_forward(conn->ns_conn, conn->endpoint.nc);
  }
static void on_recv_data(struct connection *conn) {
Sergey Lyubka's avatar
Sergey Lyubka committed
  struct iobuf *io = &conn->ns_conn->recv_iobuf;
Daniel O'Connell's avatar
Daniel O'Connell committed
  if (conn->endpoint_type == EP_PROXY && conn->endpoint.nc != NULL) {
    do_proxy(conn);
    return;
  }

Sergey Lyubka's avatar
Sergey Lyubka committed
  try_parse(conn);
  DBG(("%p %d %zu %d", conn, conn->request_len, io->len, conn->ns_conn->flags));
  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 && conn->endpoint.nc != NULL) {
    ns_forward(conn->ns_conn, conn->endpoint.nc);
  }
#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) {
  //conn->mg_conn.status_code = code;
  // For responses without Content-Lengh, use the whole buffer
Sergey Lyubka's avatar
Sergey Lyubka committed
    conn->mg_conn.content_len = conn->ns_conn->recv_iobuf.len;
Sergey Lyubka's avatar
Sergey Lyubka committed
  conn->mg_conn.content = conn->ns_conn->recv_iobuf.buf;
  if (call_user(conn, MG_REPLY) == MG_FALSE) {
Sergey Lyubka's avatar
Sergey Lyubka committed
    conn->ns_conn->flags |= NSF_CLOSE_IMMEDIATELY;
Sergey Lyubka's avatar
Sergey Lyubka committed
  iobuf_remove(&conn->ns_conn->recv_iobuf, conn->mg_conn.content_len);
  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) {
Sergey Lyubka's avatar
Sergey Lyubka committed
  struct iobuf *io = &conn->ns_conn->recv_iobuf;
Sergey Lyubka's avatar
Sergey Lyubka committed
  try_parse(conn);
  DBG(("%p %d %zu", conn, conn->request_len, io->len));
  if (conn->request_len < 0 ||
      (conn->request_len == 0 && io->len > MAX_REQUEST_SIZE)) {
Sergey Lyubka's avatar
Sergey Lyubka committed
  } else if ((int64_t) io->len >= conn->cl) {
struct mg_connection *mg_connect(struct mg_server *server, const char *host,
                                 int port, int use_ssl) {
Sergey Lyubka's avatar
Sergey Lyubka committed
  struct ns_connection *nsconn;
  struct connection *conn;
Sergey Lyubka's avatar
Sergey Lyubka committed

  nsconn = ns_connect(&server->ns_server, host, port, use_ssl, NULL);
Sergey Lyubka's avatar
Sergey Lyubka committed
  if (nsconn == NULL) return 0;
Sergey Lyubka's avatar
Sergey Lyubka committed

Sergey Lyubka's avatar
Sergey Lyubka committed
  if ((conn = (struct connection *) calloc(1, sizeof(*conn))) == NULL) {
    nsconn->flags |= NSF_CLOSE_IMMEDIATELY;
Sergey Lyubka's avatar
Sergey Lyubka committed
    return 0;
  }

Sergey Lyubka's avatar
Sergey Lyubka committed
  // Interlink two structs
  conn->ns_conn = nsconn;
  nsconn->connection_data = conn;

Sergey Lyubka's avatar
Sergey Lyubka committed
  conn->server = server;
Sergey Lyubka's avatar
Sergey Lyubka committed
  conn->endpoint_type = EP_CLIENT;
  //conn->handler = handler;
Sergey Lyubka's avatar
Sergey Lyubka committed
  conn->mg_conn.server_param = server->ns_server.server_data;
  conn->ns_conn->flags = NSF_CONNECTING;
Sergey Lyubka's avatar
Sergey Lyubka committed

  return &conn->mg_conn;
#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];
  time_t now;
  if (fp == NULL) return;
  now = time(NULL);
  strftime(date, sizeof(date), "%d/%b/%Y:%H:%M:%S %z", localtime(&now));
  flockfile(fp);
  mg_parse_header(mg_get_header(&conn->mg_conn, "Authorization"), "username",
                  user, sizeof(user));
  fprintf(fp, "%s - %s [%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->query_string ? "?" : "",
          c->query_string ? c->query_string : "",
          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);
Sergey Lyubka's avatar
Sergey Lyubka committed
  DBG(("%p %d %d %d", conn, conn->endpoint_type, keep_alive,
       conn->ns_conn->flags));
  switch (conn->endpoint_type) {
Sergey Lyubka's avatar
Sergey Lyubka committed
    case EP_PUT:
    case EP_FILE:
      close(conn->endpoint.fd);
      break;
    case EP_CGI:
    case EP_PROXY:
      if (conn->endpoint.nc != NULL) {
        DBG(("%p %p %p :-)", conn, conn->ns_conn, conn->endpoint.nc));
        conn->endpoint.nc->flags |= NSF_CLOSE_IMMEDIATELY;
        conn->endpoint.nc->connection_data = NULL;
Sergey Lyubka's avatar
Sergey Lyubka committed
      }
      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
  iobuf_free(&conn->ns_conn->recv_iobuf);
  free(conn->request);
  free(conn->path_info);

  conn->endpoint_type = EP_NONE;
Sergey Lyubka's avatar
Sergey Lyubka committed
  conn->cl = conn->num_bytes_sent = conn->request_len = 0;
  conn->ns_conn->flags &= ~(NSF_FINISHED_SENDING_DATA |
                            NSF_BUFFER_BUT_DONT_SEND | NSF_CLOSE_IMMEDIATELY |
                            MG_HEADERS_SENT | MG_LONG_RUNNING);
  c->num_headers = c->status_code = c->is_websocket = c->content_len = 0;
  conn->endpoint.nc = NULL;
  c->request_method = c->uri = c->http_version = c->query_string = NULL;
  conn->request = conn->path_info = NULL;
  if (keep_alive) {
    on_recv_data(conn);  // Can call us recursively if pipelining is used
  } else {
Sergey Lyubka's avatar
Sergey Lyubka committed
    conn->ns_conn->flags |= conn->ns_conn->send_iobuf.len == 0 ?
      NSF_CLOSE_IMMEDIATELY : NSF_FINISHED_SENDING_DATA;
static void transfer_file_data(struct connection *conn) {
  char buf[IOBUF_SIZE];
  int n;

  // If output buffer is too big, don't send anything. Wait until
  // mongoose drains already buffered data to the client.
  if (conn->ns_conn->send_iobuf.len > sizeof(buf) * 2) return;

  // Do not send anyt
  n = read(conn->endpoint.fd, buf, conn->cl < (int64_t) sizeof(buf) ?
           (int) conn->cl : (int) sizeof(buf));
Sergey Lyubka's avatar
Sergey Lyubka committed
  if (n <= 0) {
    close_local_endpoint(conn);
  } else if (n > 0) {
    conn->cl -= n;
Sergey Lyubka's avatar
Sergey Lyubka committed
    ns_send(conn->ns_conn, buf, n);
    if (conn->cl <= 0) {
      close_local_endpoint(conn);
Sergey Lyubka's avatar
Sergey Lyubka committed
int mg_poll_server(struct mg_server *server, int milliseconds) {
  return ns_server_poll(&server->ns_server, milliseconds);
void mg_destroy_server(struct mg_server **server) {
  if (server != NULL && *server != NULL) {
    struct mg_server *s = *server;
Sergey Lyubka's avatar
Sergey Lyubka committed
    int i;

    ns_server_free(&s->ns_server);
    for (i = 0; i < (int) ARRAY_SIZE(s->config_options); i++) {
      free(s->config_options[i]);  // It is OK to free(NULL)
    free(s);
    *server = NULL;
Sergey Lyubka's avatar
Sergey Lyubka committed
struct mg_iterator {
  mg_handler_t cb;
  void *param;
};

static void iter(struct ns_connection *nsconn, enum ns_event ev, void *param) {
  if (ev == NS_POLL) {
    struct mg_iterator *it = (struct mg_iterator *) param;
Sergey Lyubka's avatar
Sergey Lyubka committed
    struct connection *c = (struct connection *) nsconn->connection_data;
    if (c != NULL) c->mg_conn.callback_param = it->param;
    it->cb(&c->mg_conn, MG_POLL);
Sergey Lyubka's avatar
Sergey Lyubka committed
struct mg_connection *mg_next(struct mg_server *s, struct mg_connection *c) {
  struct connection *conn = MG_CONN_2_CONN(c);
  struct ns_connection *nc = ns_next(&s->ns_server,
                                     c == NULL ? NULL : conn->ns_conn);
    
  return nc == NULL ? NULL :
    & ((struct connection *) nc->connection_data)->mg_conn;
}

// Apply function to all active connections.
void mg_iterate_over_connections(struct mg_server *server, mg_handler_t cb,
  void *param) {
  struct mg_iterator it = { cb, param };
  ns_iterate(&server->ns_server, iter, &it);
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;
}

const char **mg_get_valid_option_names(void) {
  return static_config_options;
static int get_option_index(const char *name) {
  int i;

  for (i = 0; static_config_options[i * 2] != NULL; i++) {
    if (strcmp(static_config_options[i * 2], name) == 0) {
      return i;
  }
  return -1;
}

static void set_default_option_values(char **opts) {
  const char *value, **all_opts = mg_get_valid_option_names();
  int i;
  for (i = 0; all_opts[i * 2] != NULL; i++) {
    value = all_opts[i * 2 + 1];
    if (opts[i] == NULL && value != NULL) {
      opts[i] = mg_strdup(value);
const char *mg_set_option(struct mg_server *server, const char *name,
                          const char *value) {
  int ind = get_option_index(name);
  const char *error_msg = NULL;
  if (ind < 0) return  "No such option";
  v = &server->config_options[ind];
  // Return success immediately if setting to the same value
  if ((*v == NULL && value == NULL) ||
      (value != NULL && *v != NULL && !strcmp(value, *v))) {
    return NULL;
  }

  if (*v != NULL) {
    free(*v);
    *v = NULL;
  }

  if (value == NULL || value[0] == '\0') return NULL;

  *v = mg_strdup(value);
  DBG(("%s [%s]", name, *v));

  if (ind == LISTENING_PORT) {
    int port = ns_bind(&server->ns_server, value);
    if (port < 0) {
      error_msg = "Cannot bind to port";
    } else {
      if (!strcmp(value, "0")) {
        char buf[10];
        mg_snprintf(buf, sizeof(buf), "%d", port);
        free(*v);
        *v = mg_strdup(buf);
Sergey Lyubka's avatar
Sergey Lyubka committed
#ifndef _WIN32
  } else if (ind == RUN_AS_USER) {
    struct passwd *pw;
    if ((pw = getpwnam(value)) == NULL) {
      error_msg = "Unknown user";
    } else if (setgid(pw->pw_gid) != 0) {
      error_msg = "setgid() failed";
    } else if (setuid(pw->pw_uid) != 0) {
      error_msg = "setuid() failed";
    }
Sergey Lyubka's avatar
Sergey Lyubka committed
#endif
Sergey Lyubka's avatar
Sergey Lyubka committed
#ifdef NS_ENABLE_SSL
  } else if (ind == SSL_CERTIFICATE) {
    int res = ns_set_ssl_cert(&server->ns_server, value);
    if (res == -2) {
      error_msg = "Cannot load PEM";
    } else if (res == -3) {
      error_msg = "SSL not enabled";
    } else if (res == -1) {
      error_msg = "SSL_CTX_new() failed";
  } else if (ind == SSL_CA_CERTIFICATE) {
    if (ns_set_ssl_ca_cert(&server->ns_server, value) != 0) {
       error_msg = "Error setting CA cert";
     }
static void set_ips(struct ns_connection *nc, int is_rem) {
  struct connection *conn = (struct connection *) nc->connection_data;
  struct mg_connection *c = &conn->mg_conn;
  char buf[100];

  ns_sock_to_str(nc->sock, buf, sizeof(buf), is_rem ? 7 : 3);
  sscanf(buf, "%47[^:]:%hu",
         is_rem ? c->remote_ip : c->local_ip,
         is_rem ? &c->remote_port : &c->local_port);
  //DBG(("%p %s %s", conn, is_rem ? "rem" : "loc", buf));
Sergey Lyubka's avatar
Sergey Lyubka committed
static void on_accept(struct ns_connection *nc, union socket_address *sa) {
  struct mg_server *server = (struct mg_server *) nc->server;
  struct connection *conn;

  if (!check_acl(server->config_options[ACCESS_CONTROL_LIST],
                 ntohl(* (uint32_t *) &sa->sin.sin_addr)) ||
      (conn = (struct connection *) calloc(1, sizeof(*conn))) == NULL) {
    nc->flags |= NSF_CLOSE_IMMEDIATELY;
  } else {
    // Circularly link two connection structures
    nc->connection_data = conn;
    conn->ns_conn = nc;

    // Initialize the rest of connection attributes
    conn->server = server;
    conn->mg_conn.server_param = nc->server->server_data;
    set_ips(nc, 1);
    set_ips(nc, 0);
#ifndef MONGOOSE_NO_FILESYSTEM
Sergey Lyubka's avatar
Sergey Lyubka committed
static void hexdump(struct ns_connection *nc, const char *path,
                    int num_bytes, int is_sent) {
  const struct iobuf *io = is_sent ? &nc->send_iobuf : &nc->recv_iobuf;
  FILE *fp;
Daniel O'Connell's avatar
Daniel O'Connell committed
  char *buf, src[60], dst[60];
Sergey Lyubka's avatar
Sergey Lyubka committed
  int buf_size = num_bytes * 5 + 100;

  if (path != NULL && (fp = fopen(path, "a")) != NULL) {
Daniel O'Connell's avatar
Daniel O'Connell committed
    ns_sock_to_str(nc->sock, src, sizeof(src), 3);
    ns_sock_to_str(nc->sock, dst, sizeof(dst), 7);
    fprintf(fp, "%lu %p %s %s %s %d\n", (unsigned long) time(NULL),
            nc->connection_data, src,
            is_sent == 0 ? "<-" : is_sent == 1 ? "->" :
Daniel O'Connell's avatar
Daniel O'Connell committed
            is_sent == 2 ? "<A" : "C>", dst, num_bytes);
    if (num_bytes > 0 && (buf = (char *) malloc(buf_size)) != NULL) {
Sergey Lyubka's avatar
Sergey Lyubka committed
      ns_hexdump(io->buf + (is_sent ? 0 : io->len) - (is_sent ? 0 : num_bytes),
                 num_bytes, buf, buf_size);
      fprintf(fp, "%s", buf);
      free(buf);
    }
    fclose(fp);
  }
}
Sergey Lyubka's avatar
Sergey Lyubka committed
static void mg_ev_handler(struct ns_connection *nc, enum ns_event ev, void *p) {
  struct connection *conn = (struct connection *) nc->connection_data;
#ifndef MONGOOSE_NO_FILESYSTEM
Sergey Lyubka's avatar
Sergey Lyubka committed
  struct mg_server *server = (struct mg_server *) nc->server;
  // Send NS event to the handler. Note that call_user won't send an event
  // if conn == NULL. Therefore, repeat this for NS_ACCEPT event as well.
#ifdef MONGOOSE_SEND_NS_EVENTS
  {
    struct connection *conn = (struct connection *) nc->connection_data;
    if (conn != NULL) conn->mg_conn.callback_param = p;
    call_user(conn, (enum mg_event) ev);
  }
#endif

Sergey Lyubka's avatar
Sergey Lyubka committed
  switch (ev) {
    case NS_ACCEPT:
      on_accept(nc, (union socket_address *) p);
#ifndef MONGOOSE_NO_FILESYSTEM
      hexdump(nc, server->config_options[HEXDUMP_FILE], 0, 2);
#endif
#ifdef MONGOOSE_SEND_NS_EVENTS
      {
        struct connection *conn = (struct connection *) nc->connection_data;
        if (conn != NULL) conn->mg_conn.callback_param = p;
        call_user(conn, (enum mg_event) ev);
      }
#endif
Sergey Lyubka's avatar
Sergey Lyubka committed
      break;

    case NS_CONNECT:
Daniel O'Connell's avatar
Daniel O'Connell committed
      if (nc->connection_data != NULL) {
        set_ips(nc, 1);
        set_ips(nc, 0);
      }
#ifndef MONGOOSE_NO_FILESYSTEM
      hexdump(nc, server->config_options[HEXDUMP_FILE], 0, 3);
#endif
Sergey Lyubka's avatar
Sergey Lyubka committed
      conn->mg_conn.status_code = * (int *) p;
      if (conn->mg_conn.status_code != 0 ||
          (!(nc->flags & MG_PROXY_CONN) &&
           call_user(conn, MG_CONNECT) == MG_FALSE)) {
Sergey Lyubka's avatar
Sergey Lyubka committed
        nc->flags |= NSF_CLOSE_IMMEDIATELY;
Sergey Lyubka's avatar
Sergey Lyubka committed
      }
      break;

    case NS_RECV:
#ifndef MONGOOSE_NO_FILESYSTEM
Sergey Lyubka's avatar
Sergey Lyubka committed
      hexdump(nc, server->config_options[HEXDUMP_FILE], * (int *) p, 0);
Sergey Lyubka's avatar
Sergey Lyubka committed
      if (nc->flags & NSF_ACCEPTED) {
        on_recv_data(conn);
Sergey Lyubka's avatar
Sergey Lyubka committed
      } else if (nc->flags & MG_CGI_CONN) {
        on_cgi_data(nc);
      } else if (nc->flags & MG_PROXY_CONN) {
        if (conn != NULL) {
          ns_forward(nc, conn->ns_conn);
        }
Sergey Lyubka's avatar
Sergey Lyubka committed
      } else {
        process_response(conn);
      }
      break;

    case NS_SEND:
#ifndef MONGOOSE_NO_FILESYSTEM
Sergey Lyubka's avatar
Sergey Lyubka committed
      hexdump(nc, server->config_options[HEXDUMP_FILE], * (int *) p, 1);
Sergey Lyubka's avatar
Sergey Lyubka committed
      break;

    case NS_CLOSE:
      nc->connection_data = NULL;
      if (nc->flags & (MG_CGI_CONN | MG_PROXY_CONN)) {
        DBG(("%p %p closing cgi/proxy conn", conn, nc));
        if (conn && conn->ns_conn) {
          conn->ns_conn->flags &= ~NSF_BUFFER_BUT_DONT_SEND;
          conn->ns_conn->flags |= conn->ns_conn->send_iobuf.len > 0 ?
            NSF_FINISHED_SENDING_DATA : NSF_CLOSE_IMMEDIATELY;
          conn->endpoint.nc = NULL;
        }
Sergey Lyubka's avatar
Sergey Lyubka committed
      } else if (conn != NULL) {
        DBG(("%p %p %d closing", conn, nc, conn->endpoint_type));
Sergey Lyubka's avatar
Sergey Lyubka committed

        if (conn->endpoint_type == EP_CLIENT && nc->recv_iobuf.len > 0) {
          call_http_client_handler(conn);
        call_user(conn, MG_CLOSE);
Sergey Lyubka's avatar
Sergey Lyubka committed
        close_local_endpoint(conn);
        conn->ns_conn = NULL;
Sergey Lyubka's avatar
Sergey Lyubka committed
        free(conn);
      }
      break;

    case NS_POLL:
Sergey Lyubka's avatar
Sergey Lyubka committed
      if (call_user(conn, MG_POLL) == MG_TRUE) {
        nc->flags |= NSF_FINISHED_SENDING_DATA;
      }

Sergey Lyubka's avatar
Sergey Lyubka committed
      if (conn != NULL && conn->endpoint_type == EP_FILE) {
        transfer_file_data(conn);
      }

      // Expire idle connections
      {
        time_t current_time = * (time_t *) p;

        if (conn != NULL && conn->mg_conn.is_websocket) {
Sergey Lyubka's avatar
Sergey Lyubka committed
          ping_idle_websocket_connection(conn, current_time);
        }

        if (nc->last_io_time + MONGOOSE_IDLE_TIMEOUT_SECONDS < current_time) {
          mg_ev_handler(nc, NS_CLOSE, NULL);
Sergey Lyubka's avatar
Sergey Lyubka committed
          nc->flags |= NSF_CLOSE_IMMEDIATELY;
        }
      }
      break;

    default:
      break;
  }
}

static void iter2(struct ns_connection *nc, enum ns_event ev, void *param) {
  mg_handler_t func = NULL;
  struct connection *conn = (struct connection *) nc->connection_data;
  const char *msg = (const char *) param;
  int n;
  (void) ev;

  //DBG(("%p [%s]", conn, msg));
  if (sscanf(msg, "%p %n", &func, &n) && func != NULL) {
    conn->mg_conn.callback_param = (void *) (msg + n);
    func(&conn->mg_conn, MG_POLL);
  }
}

void mg_wakeup_server_ex(struct mg_server *server, mg_handler_t cb,
                         const char *fmt, ...) {
  va_list ap;
  char buf[8 * 1024];
  int len;

  // Encode callback (cb) into a buffer
  len = snprintf(buf, sizeof(buf), "%p ", cb);
  va_start(ap, fmt);
  len += vsnprintf(buf + len, sizeof(buf) - len, fmt, ap);
  va_end(ap);

  // "len + 1" is to include terminating \0 in the message
  ns_server_wakeup_ex(&server->ns_server, iter2, buf, len + 1);
}

Sergey Lyubka's avatar
Sergey Lyubka committed
void mg_wakeup_server(struct mg_server *server) {
  ns_server_wakeup_ex(&server->ns_server, NULL, (void *) "", 0);
void mg_set_listening_socket(struct mg_server *server, int sock) {
Sergey Lyubka's avatar
Sergey Lyubka committed
  if (server->ns_server.listening_sock != INVALID_SOCKET) {
    closesocket(server->ns_server.listening_sock);
Sergey Lyubka's avatar
Sergey Lyubka committed
  server->ns_server.listening_sock = (sock_t) sock;
Sergey Lyubka's avatar
Sergey Lyubka committed
  return server->ns_server.listening_sock;
const char *mg_get_option(const struct mg_server *server, const char *name) {