Skip to content
Snippets Groups Projects
mongoose.c 167 KiB
Newer Older
  MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
  MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
  MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
  MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
  MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
  MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
  MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
  MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
  MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
  MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
  MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
  MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
  MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
  MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
  MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
  MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
  MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
  MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
  MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
  MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
  MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
  MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
  MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
  MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
  MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
  MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
  MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
  MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
  MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
  MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
  MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
  MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
  MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
  MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
  MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
  MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
  MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);

  buf[0] += a;
  buf[1] += b;
  buf[2] += c;
  buf[3] += d;
static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) {
  uint32_t t;
  t = ctx->bits[0];
  if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
    ctx->bits[1]++;
  ctx->bits[1] += len >> 29;
  t = (t >> 3) & 0x3f;
  if (t) {
    unsigned char *p = (unsigned char *) ctx->in + t;
    t = 64 - t;
    if (len < t) {
      memcpy(p, buf, len);
      return;
    memcpy(p, buf, t);
    byteReverse(ctx->in, 16);
    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
    buf += t;
    len -= t;
  while (len >= 64) {
    memcpy(ctx->in, buf, 64);
    byteReverse(ctx->in, 16);
    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
    buf += 64;
    len -= 64;
  memcpy(ctx->in, buf, len);
static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) {
  unsigned count;
  unsigned char *p;
  uint32_t *a;
  count = (ctx->bits[0] >> 3) & 0x3F;
  p = ctx->in + count;
  *p++ = 0x80;
  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
Sergey Lyubka's avatar
Sergey Lyubka committed
  if ((unsigned long) time(NULL) - (unsigned long) to64(nonce) > 3600 * 2) {
  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_TRUE : MG_FALSE;
// Authorize against the opened passwords file. Return 1 if authorized.
int mg_authorize_digest(struct mg_connection *c, FILE *fp) {
Sergey Lyubka's avatar
Sergey Lyubka committed
  struct connection *conn = MG_CONN_2_CONN(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 1 if request is authorised, 0 otherwise.
Sergey Lyubka's avatar
Sergey Lyubka committed
static int is_authorized(struct connection *conn, const char *path,
                         int is_directory) {
  int authorized = MG_TRUE;
  if ((fp = open_auth_file(conn, path, is_directory)) != 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];
  const char *method = conn->mg_conn.request_method;
  int authorized = MG_FALSE;
  // If dav_auth_file is not set, allow non-authorized PROPFIND
  if (method != NULL && !strcmp(method, "PROPFIND") && auth_file == NULL) {
    authorized = MG_TRUE;
  } else if (auth_file != NULL && (fp = fopen(auth_file, "r")) != NULL) {
    authorized = mg_authorize_digest(&conn->mg_conn, fp);
  return authorized;
#endif // MONGOOSE_NO_AUTH
static int parse_header(const char *str, size_t str_len, const char *var_name,
Sergey Lyubka's avatar
Sergey Lyubka committed
                        char *buf, size_t buf_size) {
  int ch = ' ', ch1 = ',', len = 0;
  size_t 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] == ch || s[-1] == ch1) && 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;
Sergey Lyubka's avatar
Sergey Lyubka committed
    if (*s == '"' || *s == '\'') ch = ch1 = *s++;
Sergey Lyubka's avatar
Sergey Lyubka committed
    while (p < end && p[0] != ch && p[0] != ch1 && len < (int) buf_size) {
      if (ch == ch1 && 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);
#ifndef MONGOOSE_NO_SSI
static void send_ssi_file(struct mg_connection *, const char *, FILE *, int);

static void send_file_data(struct mg_connection *conn, FILE *fp) {
  char buf[IOBUF_SIZE];
  while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) {
    mg_write(conn, buf, n);
  }
}

static void do_ssi_include(struct mg_connection *conn, const char *ssi,
                           char *tag, int include_level) {
  char file_name[IOBUF_SIZE], path[MAX_PATH_SIZE], *p;
  char **opts = (MG_CONN_2_CONN(conn))->server->config_options;
  FILE *fp;

  // sscanf() is safe here, since send_ssi_file() also uses buffer
  // of size MG_BUF_LEN to get the tag. So strlen(tag) is always < MG_BUF_LEN.
  if (sscanf(tag, " virtual=\"%[^\"]\"", file_name) == 1) {
    // File name is relative to the webserver root
    mg_snprintf(path, sizeof(path), "%s%c%s",
                opts[DOCUMENT_ROOT], '/', file_name);
  } else if (sscanf(tag, " abspath=\"%[^\"]\"", file_name) == 1) {
    // File name is relative to the webserver working directory
    // or it is absolute system path
    mg_snprintf(path, sizeof(path), "%s", file_name);
  } else if (sscanf(tag, " file=\"%[^\"]\"", file_name) == 1 ||
             sscanf(tag, " \"%[^\"]\"", file_name) == 1) {
    // File name is relative to the current document
    mg_snprintf(path, sizeof(path), "%s", ssi);
    if ((p = strrchr(path, '/')) != NULL) {
      p[1] = '\0';
    }
    mg_snprintf(path + strlen(path), sizeof(path) - strlen(path), "%s",
                file_name);
  } else {
    mg_printf(conn, "Bad SSI #include: [%s]", tag);
    return;
  }

  if ((fp = fopen(path, "rb")) == NULL) {
    mg_printf(conn, "Cannot open SSI #include: [%s]: fopen(%s): %s",
              tag, path, strerror(errno));
  } else {
    ns_set_close_on_exec(fileno(fp));
    if (mg_match_prefix(opts[SSI_PATTERN], strlen(opts[SSI_PATTERN]),
        path) > 0) {
      send_ssi_file(conn, path, fp, include_level + 1);
    } else {
      send_file_data(conn, fp);
    }
    fclose(fp);
  }
}

#ifndef MONGOOSE_NO_POPEN
static void do_ssi_exec(struct mg_connection *conn, char *tag) {
  char cmd[IOBUF_SIZE];
  FILE *fp;

  if (sscanf(tag, " \"%[^\"]\"", cmd) != 1) {
    mg_printf(conn, "Bad SSI #exec: [%s]", tag);
  } else if ((fp = popen(cmd, "r")) == NULL) {
    mg_printf(conn, "Cannot SSI #exec: [%s]: %s", cmd, strerror(errno));
  } else {
    send_file_data(conn, fp);
    pclose(fp);
  }
}
#endif // !MONGOOSE_NO_POPEN

static void send_ssi_file(struct mg_connection *conn, const char *path,
                          FILE *fp, int include_level) {
  char buf[IOBUF_SIZE];
  int ch, offset, len, in_ssi_tag;

  if (include_level > 10) {
    mg_printf(conn, "SSI #include level is too deep (%s)", path);
    return;
  }

  in_ssi_tag = len = offset = 0;
  while ((ch = fgetc(fp)) != EOF) {
    if (in_ssi_tag && ch == '>') {
      in_ssi_tag = 0;
      buf[len++] = (char) ch;
      buf[len] = '\0';
      assert(len <= (int) sizeof(buf));
      if (len < 6 || memcmp(buf, "<!--#", 5) != 0) {
        // Not an SSI tag, pass it
        (void) mg_write(conn, buf, (size_t) len);
      } else {
        if (!memcmp(buf + 5, "include", 7)) {
          do_ssi_include(conn, path, buf + 12, include_level);
#if !defined(MONGOOSE_NO_POPEN)
        } else if (!memcmp(buf + 5, "exec", 4)) {
          do_ssi_exec(conn, buf + 9);
#endif // !NO_POPEN
        } else {
          mg_printf(conn, "%s: unknown SSI " "command: \"%s\"", path, buf);
        }
      }
      len = 0;
    } else if (in_ssi_tag) {
      if (len == 5 && memcmp(buf, "<!--#", 5) != 0) {
        // Not an SSI tag
        in_ssi_tag = 0;
      } else if (len == (int) sizeof(buf) - 2) {
        mg_printf(conn, "%s: SSI tag is too large", path);
        len = 0;
      }
      buf[len++] = ch & 0xff;
    } else if (ch == '<') {
      in_ssi_tag = 1;
      if (len > 0) {
        mg_write(conn, buf, (size_t) len);
      }
      len = 0;
      buf[len++] = ch & 0xff;
    } else {
      buf[len++] = ch & 0xff;
      if (len == (int) sizeof(buf)) {
        mg_write(conn, buf, (size_t) len);
        len = 0;
      }
    }
  }

  // Send the rest of buffered data
  if (len > 0) {
    mg_write(conn, buf, (size_t) len);
  }
}

static void handle_ssi_request(struct connection *conn, const char *path) {
  FILE *fp;
  struct vec mime_vec;

  if ((fp = fopen(path, "rb")) == NULL) {
    send_http_error(conn, 500, "fopen(%s): %s", path, strerror(errno));
  } else {
    ns_set_close_on_exec(fileno(fp));
    get_mime_type(conn->server, path, &mime_vec);
    conn->mg_conn.status_code = 200;
    mg_printf(&conn->mg_conn,
              "HTTP/1.1 %d OK\r\n"
              "Content-Type: %.*s\r\n"
              "Connection: close\r\n\r\n",
              conn->mg_conn.status_code, (int) mime_vec.len, mime_vec.ptr);
    send_ssi_file(&conn->mg_conn, path, fp, 0);
    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%s%s HTTP/%s\r\n", c->request_method, c->uri,
            c->query_string ? "?" : "",
            c->query_string ? c->query_string : "",
Daniel O'Connell's avatar
Daniel O'Connell committed
            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
Sergey Lyubka's avatar
Sergey Lyubka committed
      ns_printf(pc, "%s: %s\r\n", "Connection", "close");
Daniel O'Connell's avatar
Daniel O'Connell committed
      sent_close_header = 1;
Sergey Lyubka's avatar
Sergey Lyubka committed
    } else {
Daniel O'Connell's avatar
Daniel O'Connell committed
      ns_printf(pc, "%s: %s\r\n", c->http_headers[i].name,
                c->http_headers[i].value);
Sergey Lyubka's avatar
Sergey Lyubka committed
    }
Daniel O'Connell's avatar
Daniel O'Connell committed
  }
  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);

}

#ifdef NS_ENABLE_SSL
int mg_terminate_ssl(struct mg_connection *c, const char *cert) {
  static const char ok[] = "HTTP/1.0 200 OK\r\n\r\n";
  struct connection *conn = MG_CONN_2_CONN(c);
  SSL_CTX *ctx;

  DBG(("%p MITM", conn));
  if ((ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) return 0;

  SSL_CTX_use_certificate_file(ctx, cert, 1);
  SSL_CTX_use_PrivateKey_file(ctx, cert, 1);
  SSL_CTX_use_certificate_chain_file(ctx, cert);

  // When clear-text reply is pushed to client, switch to SSL mode.
  // TODO(lsm): check for send() failure
  send(conn->ns_conn->sock, ok, sizeof(ok) - 1, 0);
  //DBG(("%p %lu %d SEND", c, (unsigned long) sizeof(ok) - 1, n));
  conn->ns_conn->send_iobuf.len = 0;
  conn->endpoint_type = EP_USER;  // To keep-alive in close_local_endpoint()
  close_local_endpoint(conn);     // Clean up current CONNECT request
  if ((conn->ns_conn->ssl = SSL_new(ctx)) != NULL) {
    SSL_set_fd(conn->ns_conn->ssl, conn->ns_conn->sock);
  }
  SSL_CTX_free(ctx);
  return 1;
}
#endif

int mg_forward(struct mg_connection *c, const char *addr) {
  static const char ok[] = "HTTP/1.1 200 OK\r\n\r\n";
Sergey Lyubka's avatar
Sergey Lyubka committed
  struct connection *conn = MG_CONN_2_CONN(c);
  struct ns_connection *pc;

  if ((pc = ns_connect(&conn->server->ns_mgr, addr,
      mg_ev_handler, conn)) == NULL) {
Sergey Lyubka's avatar
Sergey Lyubka committed
    conn->ns_conn->flags |= NSF_CLOSE_IMMEDIATELY;
    return 0;
  }
Sergey Lyubka's avatar
Sergey Lyubka committed

  // Interlink two connections
  pc->flags |= MG_PROXY_CONN;
  conn->endpoint_type = EP_PROXY;
  conn->endpoint.nc = pc;
  DBG(("%p [%s] [%s] -> %p %p", conn, c->uri, addr, pc, conn->ns_conn->ssl));
Sergey Lyubka's avatar
Sergey Lyubka committed

  if (strcmp(c->request_method, "CONNECT") == 0) {
    // For CONNECT request, reply with 200 OK. Tunnel is established.
    // TODO(lsm): check for send() failure
    (void) send(conn->ns_conn->sock, ok, sizeof(ok) - 1, 0);
Sergey Lyubka's avatar
Sergey Lyubka committed
  } else {
    // Strip "http://host:port" part from the URI
    if (memcmp(c->uri, "http://", 7) == 0) c->uri += 7;
    while (*c->uri != '\0' && *c->uri != '/') c->uri++;
    proxy_request(pc, c);
  }
  return 1;
}

static void proxify_connection(struct connection *conn) {
  char proto[10], host[500], cert[500], addr[1000];
Daniel O'Connell's avatar
Daniel O'Connell committed
  unsigned short port = 80;
  struct mg_connection *c = &conn->mg_conn;
Sergey Lyubka's avatar
Sergey Lyubka committed
  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;
  }
  snprintf(addr, sizeof(addr), "%s://%s:%hu",
           conn->ns_conn->ssl != NULL ? "ssl" : "tcp", host, port);
  if (n <= 0 || !mg_forward(c, addr)) {
    conn->ns_conn->flags |= NSF_CLOSE_IMMEDIATELY;
  }
}

#ifndef MONGOOSE_NO_FILESYSTEM
Sergey Lyubka's avatar
Sergey Lyubka committed
void mg_send_file_internal(struct mg_connection *c, const char *file_name,
                           file_stat_t *st, int exists,
                           const char *extra_headers) {
  struct connection *conn = MG_CONN_2_CONN(c);
Sergey Lyubka's avatar
Sergey Lyubka committed
  char path[MAX_PATH_SIZE];
  const int is_directory = S_ISDIR(st->st_mode);
#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);
  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)) {
  } else if ((conn->endpoint.fd = open(path, O_RDONLY | O_BINARY, 0)) != -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, extra_headers);
void mg_send_file(struct mg_connection *c, const char *file_name,
                  const char *extra_headers) {
  file_stat_t st;
  const int exists = stat(file_name, &st) == 0;
  mg_send_file_internal(c, file_name, &st, exists, extra_headers);
#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 > %lu",
                        (unsigned long) MONGOOSE_POST_SIZE_LIMIT);
  if (strcmp(conn->mg_conn.request_method, "CONNECT") == 0 ||
      mg_strncasecmp(conn->mg_conn.uri, "http", 4) == 0) {
    const char *enp = conn->server->config_options[ENABLE_PROXY];
    if (enp == NULL || strcmp(enp, "yes") != 0) {
      send_http_error(conn, 405, NULL);
    } else {
      proxify_connection(conn);
    }
  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
Sergey Lyubka's avatar
Sergey Lyubka committed
  } else if ((!is_dav_request(conn) && !is_authorized(conn, path,
               exists && S_ISDIR(st.st_mode))) ||
             (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 (must_hide_file(conn, path)) {
    send_http_error(conn, 404, NULL);
  } 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
    mg_send_file_internal(&conn->mg_conn, path, &st, exists, 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")) {
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
    mg_strncasecmp(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 *) NS_MALLOC(conn->request_len);
Sergey Lyubka's avatar
Sergey Lyubka committed
    if (conn->request == NULL) {
      conn->ns_conn->flags |= NSF_CLOSE_IMMEDIATELY;
      return;
    }
    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) {
Sergey Lyubka's avatar
Sergey Lyubka committed
  if (0 && conn->request_len == 0) {
Daniel O'Connell's avatar
Daniel O'Connell committed
    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;
Sergey Lyubka's avatar
Sergey Lyubka committed
  if (conn->endpoint_type == EP_PROXY) {
    if (conn->endpoint.nc != NULL) do_proxy(conn);
Sergey Lyubka's avatar
Sergey Lyubka committed
  try_parse(conn);
Sergey Lyubka's avatar
Sergey Lyubka committed
  DBG(("%p %d %lu %d", conn, conn->request_len, (unsigned long)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) {
    conn->mg_conn.content = io->buf;
    conn->mg_conn.content_len = io->len;
    n = call_user(conn, MG_RECV);
    if (n < 0) {
      conn->ns_conn->flags |= NSF_FINISHED_SENDING_DATA;
    } else if ((size_t) n <= io->len) {
      iobuf_remove(io, n);
    }
    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_recv = conn->request_len = 0;
  NS_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 %lu", conn, conn->request_len, (unsigned long)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 *addr) {
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_mgr, addr, mg_ev_handler, NULL);
Sergey Lyubka's avatar
Sergey Lyubka committed
  if (nsconn == NULL) return 0;
Sergey Lyubka's avatar
Sergey Lyubka committed

  if ((conn = (struct connection *) NS_CALLOC(1, sizeof(*conn))) == NULL) {
Sergey Lyubka's avatar
Sergey Lyubka committed
    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;
Ake Hedman's avatar
Ake Hedman committed
  nsconn->user_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;
  conn->mg_conn.server_param = server->ns_mgr.user_data;
Sergey Lyubka's avatar
Sergey Lyubka committed
  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 0",
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);
  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;
Ake Hedman's avatar
Ake Hedman committed
        conn->endpoint.nc->user_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);
  NS_FREE(conn->request);
  NS_FREE(conn->path_info);
  conn->endpoint.nc = NULL;
  conn->request = conn->path_info = NULL;
  conn->endpoint_type = EP_NONE;
  conn->cl = conn->num_bytes_recv = conn->request_len = 0;
  conn->ns_conn->flags &= ~(NSF_FINISHED_SENDING_DATA |
                            NSF_BUFFER_BUT_DONT_SEND | NSF_CLOSE_IMMEDIATELY |
Sergey Lyubka's avatar
Sergey Lyubka committed
                            MG_HEADERS_SENT | MG_USING_CHUNKED_API);

  // Do not memset() the whole structure, as some of the fields
  // (IP addresses & ports, server_param) must survive. Nullify the rest.
  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;
  c->connection_param = c->callback_param = 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];

  // 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);
time_t mg_poll_server(struct mg_server *server, int milliseconds) {
  return ns_mgr_poll(&server->ns_mgr, 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;

    for (i = 0; i < (int) ARRAY_SIZE(s->config_options); i++) {
      NS_FREE(s->config_options[i]);  // It is OK to free(NULL)
    *server = NULL;
Sergey Lyubka's avatar
Sergey Lyubka committed
struct mg_connection *mg_next(struct mg_server *s, struct mg_connection *c) {
  struct ns_connection *nc = ns_next(&s->ns_mgr, c == NULL ? NULL :
                                     MG_CONN_2_CONN(c)->ns_conn);
Ake Hedman's avatar
Ake Hedman committed
  if (nc != NULL && nc->user_data != NULL) {
    return & ((struct connection *) nc->user_data)->mg_conn;
static int get_var(const char *data, size_t data_len, const char *name,