Skip to content
Snippets Groups Projects
mongoose.c 167 KiB
Newer Older
                   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);
    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;
void mg_copy_listeners(struct mg_server *s, struct mg_server *to) {
  struct ns_connection *c;
  for (c = ns_next(&s->ns_mgr, NULL); c != NULL; c = ns_next(&s->ns_mgr, c)) {
    struct ns_connection *tmp;
    if ((c->flags & NSF_LISTENING) &&
        (tmp = (struct ns_connection *) NS_MALLOC(sizeof(*tmp))) != NULL) {
Sergey Lyubka's avatar
Sergey Lyubka committed

Sergey Lyubka's avatar
Sergey Lyubka committed
#if defined(NS_ENABLE_SSL) && defined(HEADER_SSL_H)
      /* OpenSSL only. See https://github.com/cesanta/mongoose/issues/441 */
Sergey Lyubka's avatar
Sergey Lyubka committed
      if (tmp->ssl_ctx != NULL) {
        tmp->ssl_ctx->references++;
      }
Sergey Lyubka's avatar
Sergey Lyubka committed
#endif

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) {
  if (value == NULL || value[0] == '\0') return NULL;

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

  if (ind == LISTENING_PORT) {
Sergey Lyubka's avatar
Sergey Lyubka committed
    char buf[500] = "";
    size_t n = 0;
    struct vec vec;
Sergey Lyubka's avatar
Sergey Lyubka committed
    /*
     * Ports can be specified as 0, meaning that OS has to choose any
     * free port that is available. In order to pass chosen port number to
     * the user, we rewrite all 0 port to chosen values.
     */
    while ((value = next_option(value, &vec, NULL)) != NULL) {
      struct ns_connection *c = ns_bind(&server->ns_mgr, vec.ptr,
                                        mg_ev_handler, NULL);
Sergey Lyubka's avatar
Sergey Lyubka committed
      if (c == NULL) {
        error_msg = "Cannot bind to port";
        break;
      } else {
        char buf2[50], cert[100], ca[100];
        union socket_address sa;
        int proto, use_ssl;

        ns_parse_address(vec.ptr, &sa, &proto, &use_ssl, cert, ca);
Sergey Lyubka's avatar
Sergey Lyubka committed
        ns_sock_to_str(c->sock, buf2, sizeof(buf2),
                       memchr(vec.ptr, ':', vec.len) == NULL ? 2 : 3);

        n += snprintf(buf + n, sizeof(buf) - n, "%s%s%s%s%s%s%s",
                      n > 0 ? "," : "",
                      use_ssl ? "ssl://" : "",
                      buf2, cert[0] ? ":" : "", cert, ca[0] ? ":" : "", ca);
Sergey Lyubka's avatar
Sergey Lyubka committed
    buf[sizeof(buf) - 1] = '\0';
    NS_FREE(*v);
    *v = mg_strdup(buf);
#ifndef MONGOOSE_NO_FILESYSTEM
  } else if (ind == HEXDUMP_FILE) {
    server->ns_mgr.hexdump_file = *v;
#if !defined(_WIN32) && !defined(MONGOOSE_NO_USER)
  } 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";
    }
#endif
static void set_ips(struct ns_connection *nc, int is_rem) {
Ake Hedman's avatar
Ake Hedman committed
  struct connection *conn = (struct connection *) nc->user_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->mgr;
Sergey Lyubka's avatar
Sergey Lyubka committed
  struct connection *conn;

  if (!check_acl(server->config_options[ACCESS_CONTROL_LIST],
                 ntohl(* (uint32_t *) &sa->sin.sin_addr)) ||
      (conn = (struct connection *) NS_CALLOC(1, sizeof(*conn))) == NULL) {
Sergey Lyubka's avatar
Sergey Lyubka committed
    nc->flags |= NSF_CLOSE_IMMEDIATELY;
  } else {
    // Circularly link two connection structures
Ake Hedman's avatar
Ake Hedman committed
    nc->user_data = conn;
Sergey Lyubka's avatar
Sergey Lyubka committed
    conn->ns_conn = nc;

    // Initialize the rest of connection attributes
    conn->server = server;
    conn->mg_conn.server_param = nc->mgr->user_data;
    set_ips(nc, 1);
    set_ips(nc, 0);
Sergey Lyubka's avatar
Sergey Lyubka committed
static void process_udp(struct ns_connection *nc) {
  struct iobuf *io = &nc->recv_iobuf;
  struct connection conn;

  memset(&conn, 0, sizeof(conn));
  conn.ns_conn = nc;
  conn.server = (struct mg_server *) nc->mgr;
  conn.request_len = parse_http_message(io->buf, io->len, &conn.mg_conn);
  on_recv_data(&conn);
  //ns_printf(nc, "%s", "HTTP/1.0 200 OK\r\n\r\n");
}

static void send_ns_event(struct ns_connection *nc, int ev, void *p) {
  struct connection *conn = (struct connection *) nc->user_data;
  if (conn != NULL) {
    void *param[2] = { nc, p };
    conn->mg_conn.callback_param = param;
}
#else
static void send_ns_event(struct ns_connection *nc, int ev, void *p) {
  (void) nc; (void) p;
}
static void mg_ev_handler(struct ns_connection *nc, int ev, void *p) {
  struct connection *conn = (struct connection *) nc->user_data;

  // 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.
  send_ns_event(nc, ev, p);

Sergey Lyubka's avatar
Sergey Lyubka committed
  switch (ev) {
    case NS_ACCEPT:
      on_accept(nc, (union socket_address *) p);
      send_ns_event(nc, ev, p);
Sergey Lyubka's avatar
Sergey Lyubka committed
      break;

    case NS_CONNECT:
Ake Hedman's avatar
Ake Hedman committed
      if (nc->user_data != NULL) {
Daniel O'Connell's avatar
Daniel O'Connell committed
        set_ips(nc, 1);
        set_ips(nc, 0);
      }
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:
      if (conn != NULL) {
        conn->num_bytes_recv += * (int *) p;
      }
Sergey Lyubka's avatar
Sergey Lyubka committed

      if (nc->flags & NSF_UDP) {
        process_udp(nc);
      } else if (nc->listener != NULL) {
        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:
      break;

    case NS_CLOSE:
Ake Hedman's avatar
Ake Hedman committed
      nc->user_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
      }
      break;

    case NS_POLL:
      if (conn != NULL) {
        if (call_user(conn, MG_POLL) == MG_TRUE) {
          if (conn->ns_conn->flags & MG_HEADERS_SENT) {
            write_terminating_chunk(conn);
          }
          close_local_endpoint(conn);
           * MG_POLL callback returned MG_TRUE,
           * i.e. data is sent, set corresponding flag
           */
          conn->ns_conn->flags |= NSF_FINISHED_SENDING_DATA;
Sergey Lyubka's avatar
Sergey Lyubka committed

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

      // 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->listener != NULL &&
            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, int ev, void *param) {
  mg_handler_t func = NULL;
Ake Hedman's avatar
Ake Hedman committed
  struct connection *conn = (struct connection *) nc->user_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 != 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_broadcast(&server->ns_mgr, iter2, buf, len + 1);
Sergey Lyubka's avatar
Sergey Lyubka committed
void mg_wakeup_server(struct mg_server *server) {
  ns_broadcast(&server->ns_mgr, NULL, (void *) "", 0);
const char *mg_get_option(const struct mg_server *server, const char *name) {
  const char **opts = (const char **) server->config_options;
  int i = get_option_index(name);
  return i == -1 ? NULL : opts[i] == NULL ? "" : opts[i];
}

struct mg_server *mg_create_server(void *server_data, mg_handler_t handler) {
  struct mg_server *server = (struct mg_server *) NS_CALLOC(1, sizeof(*server));
  ns_mgr_init(&server->ns_mgr, server_data);
  set_default_option_values(server->config_options);
  server->event_handler = handler;
  return server;