Example #1
0
static const char *test_parse_http_message() {
  struct mg_connection ri;
  char req1[] = "GET / HTTP/1.1\r\n\r\n";
  char req2[] = "BLAH / HTTP/1.1\r\n\r\n";
  char req3[] = "GET / HTTP/1.1\r\nBah\r\n";
  char req4[] = "GET / HTTP/1.1\r\nA: foo bar\r\nB: bar\r\nbaz\r\n\r\n";
  char req5[] = "GET / HTTP/1.1\r\n\r\n";
  char req6[] = "G";
  char req7[] = " blah ";
  char req8[] = " HTTP/1.1 200 OK \n\n";
  char req9[] = "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n";

  ASSERT(get_request_len("\r\n", 3) == -1);
  ASSERT(get_request_len("\r\n", 2) == 0);
  ASSERT(get_request_len("GET", 3) == 0);
  ASSERT(get_request_len("\n\n", 2) == 2);
  ASSERT(get_request_len("\n\r\n", 3) == 3);
  ASSERT(get_request_len("\xdd\xdd", 2) == 0);
  ASSERT(get_request_len("\xdd\x03", 2) == -1);
  ASSERT(get_request_len(req3, sizeof(req3) - 1) == 0);
  ASSERT(get_request_len(req6, sizeof(req6) - 1) == 0);
  ASSERT(get_request_len(req7, sizeof(req7) - 1) == 0);

  ASSERT(parse_http_message(req9, sizeof(req9) - 1, &ri) == sizeof(req9) - 1);
  ASSERT(ri.num_headers == 1);

  ASSERT(parse_http_message(req1, sizeof(req1) - 1, &ri) == sizeof(req1) - 1);
  ASSERT(strcmp(ri.http_version, "1.1") == 0);
  ASSERT(ri.num_headers == 0);

  ASSERT(parse_http_message(req2, sizeof(req2) - 1, &ri) == -1);
  ASSERT(parse_http_message(req6, 0, &ri) == -1);
  ASSERT(parse_http_message(req8, sizeof(req8) - 1, &ri) == sizeof(req8) - 1);

  // TODO(lsm): Fix this. Header value may span multiple lines.
  ASSERT(parse_http_message(req4, sizeof(req4) - 1, &ri) == sizeof(req4) - 1);
  ASSERT(strcmp(ri.http_version, "1.1") == 0);
  ASSERT(ri.num_headers == 3);
  ASSERT(strcmp(ri.http_headers[0].name, "A") == 0);
  ASSERT(strcmp(ri.http_headers[0].value, "foo bar") == 0);
  ASSERT(strcmp(ri.http_headers[1].name, "B") == 0);
  ASSERT(strcmp(ri.http_headers[1].value, "bar") == 0);
  ASSERT(strcmp(ri.http_headers[2].name, "baz\r\n\r") == 0);
  ASSERT(strcmp(ri.http_headers[2].value, "") == 0);

  ASSERT(parse_http_message(req5, sizeof(req5) - 1, &ri) == sizeof(req5) - 1);
  ASSERT(strcmp(ri.request_method, "GET") == 0);
  ASSERT(strcmp(ri.http_version, "1.1") == 0);

  return NULL;
}
Example #2
0
// Keep reading the input from socket sock
// into buffer buf, until \r\n\r\n appears in the buffer (which marks the end
// of HTTP request). Buffer buf may already have some data. The length of the
// data is stored in nread.  Upon every read operation, increase nread by the
// number of bytes read.
static int read_request(SOCKET sock, char *buf, int bufsiz,
                        int *nread) {
  int n, request_len;

  request_len = 0;
  while (*nread < bufsiz && request_len == 0) {
    n = pull(sock, buf + *nread, bufsiz - *nread);
    if (n <= 0) {
      break;
    } else {
      *nread += n;
      request_len = get_request_len(buf, *nread);
    }
  }

  return request_len;
}
Example #3
0
// Parse HTTP request, fill in mg_request_info structure.
// This function modifies the buffer by NUL-terminating
// HTTP request components, header names and header values.
static int parse_http_message(char *buf, int len, struct mg_request_info *ri) {
  int request_length = get_request_len(buf, len);
  if (request_length > 0) {
    // Reset attributes. DO NOT TOUCH is_ssl, remote_ip, remote_port
    ri->remote_user = ri->request_method = ri->uri = ri->http_version = NULL;
    ri->num_headers = 0;

    buf[request_length - 1] = '\0';

    // RFC says that all initial whitespaces should be ingored
    while (*buf != '\0' && isspace(* (unsigned char *) buf)) {
      buf++;
    }
    ri->request_method = skip(&buf, " ");
    ri->uri = skip(&buf, " ");
    ri->http_version = skip(&buf, "\r\n");
    parse_http_headers(&buf, ri);
  }
  return request_length;
}
Example #4
0
static void process_new_connection(struct mg_connection *conn) {
  struct mg_request_info *ri = &conn->request_info;
  const char *cl;

  reset_per_request_attributes(conn);

  // If next request is not pipelined, read it in
  if ((conn->request_len = get_request_len(conn->buf, conn->data_len)) == 0) {
    conn->request_len = read_request(conn->client.sock,
        conn->buf, conn->buf_size, &conn->data_len);
  }
  assert(conn->data_len >= conn->request_len);
  if (conn->request_len == 0 && conn->data_len == conn->buf_size) {
    mg_send_http_error(conn, 413, "Request Too Large", "");
    return;
  } if (conn->request_len <= 0) {
    return;  // Remote end closed the connection
  }

  // Nul-terminate the request cause parse_http_request() uses sscanf
  conn->buf[conn->request_len - 1] = '\0';
  if (!parse_http_request(conn->buf, ri)) {
    // Do not put garbage in the access log, just send it back to the client
    mg_send_http_error(conn, 400, "Bad Request",
        "Cannot parse HTTP request: [%.*s]", conn->data_len, conn->buf);
  } else if (strcmp(ri->http_version, "1.0") && strcmp(ri->http_version, "1.1")) {
    // Request seems valid, but HTTP version is strange
    mg_send_http_error(conn, 505, "HTTP version not supported", "");
  } else {
    // Request is valid, handle it
    cl = get_header(ri, "Content-Length");
    conn->content_len = cl == NULL ? -1 : strtoll(cl, NULL, 10);
    conn->birth_time = time(NULL);
    handle_request(conn);
    discard_current_request_from_buffer(conn);
  }
}
Example #5
0
FILE *mg_upload(struct mg_connection *conn, const char *destination_dir,
                char *path, int path_len) {
  const char *content_type_header, *boundary_start;
  char *buf, fname[1024], boundary[100], *s, *p;
  int bl, n, i, j, headers_len, boundary_len, eof, buf_len, to_read, len = 0;
  FILE *fp;

  // Request looks like this:
  //
  // POST /upload HTTP/1.1
  // Host: 127.0.0.1:8080
  // Content-Length: 244894
  // Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryRVr
  //
  // ------WebKitFormBoundaryRVr
  // Content-Disposition: form-data; name="file"; filename="accum.png"
  // Content-Type: image/png
  //
  //  <89>PNG
  //  <PNG DATA>
  // ------WebKitFormBoundaryRVr

  // Extract boundary string from the Content-Type header
  if ((content_type_header = mg_get_header(conn, "Content-Type")) == NULL ||
      (boundary_start = mg_strcasestr(content_type_header,
                                      "boundary=")) == NULL ||
      (sscanf(boundary_start, "boundary=\"%99[^\"]\"", boundary) == 0 &&
       sscanf(boundary_start, "boundary=%99s", boundary) == 0) ||
      boundary[0] == '\0') {
    return NULL;
  }

  boundary_len = strlen(boundary);
  bl = boundary_len + 4;  // \r\n--<boundary>

  //                     buf
  // conn->buf            |<--------- buf_len ------>|
  //    |=================|==========|===============|
  //    |<--request_len-->|<--len--->|               |
  //    |<-----------data_len------->|      conn->buf + conn->buf_size

  buf = conn->buf + conn->request_len;
  buf_len = conn->buf_size - conn->request_len;
  len = conn->data_len - conn->request_len;

  for (;;) {
    // Pull in headers
    assert(len >= 0 && len <= buf_len);
    to_read = buf_len - len;
    if (to_read > left_to_read(conn)) {
      to_read = (int) left_to_read(conn);
    }
    while (len < buf_len &&
           (n = pull(NULL, conn, buf + len, to_read)) > 0) {
      len += n;
    }
    if ((headers_len = get_request_len(buf, len)) <= 0) {
      break;
    }

    // Fetch file name.
    fname[0] = '\0';
    for (i = j = 0; i < headers_len; i++) {
      if (buf[i] == '\r' && buf[i + 1] == '\n') {
        buf[i] = buf[i + 1] = '\0';
        // TODO(lsm): don't expect filename to be the 3rd field,
        // parse the header properly instead.
        sscanf(&buf[j], "Content-Disposition: %*s %*s filename=\"%1023[^\"]",
               fname);
        j = i + 2;
      }
    }

    // Give up if the headers are not what we expect
    if (fname[0] == '\0') {
      break;
    }

    // Move data to the beginning of the buffer
    assert(len >= headers_len);
    memmove(buf, &buf[headers_len], len - headers_len);
    len -= headers_len;
    conn->data_len = conn->request_len + len;

    // We open the file with exclusive lock held. This guarantee us
    // there is no other thread can save into the same file simultaneously.
    fp = NULL;

    // Construct destination file name. Do not allow paths to have slashes.
    s = fname;
    if ((p = strrchr(fname, '/')) != NULL && p > s) s = p;
    if ((p = strrchr(fname, '\\')) != NULL && p > s) s = p;

    // Open file in binary mode. TODO: set an exclusive lock.
    snprintf(path, path_len, "%s/%s", destination_dir, s);
    if ((fp = fopen(path, "wb")) == NULL) {
      break;
    }

    // Read POST data, write into file until boundary is found.
    eof = n = 0;
    do {
      len += n;
      for (i = 0; i < len - bl; i++) {
        if (!memcmp(&buf[i], "\r\n--", 4) &&
            !memcmp(&buf[i + 4], boundary, boundary_len)) {
          // Found boundary, that's the end of file data.
          fwrite(buf, 1, i, fp);
          eof = 1;
          memmove(buf, &buf[i + bl], len - (i + bl));
          len -= i + bl;
          break;
        }
      }
      if (!eof && len > bl) {
        fwrite(buf, 1, len - bl, fp);
        memmove(buf, &buf[len - bl], bl);
        len = bl;
      }
      to_read = buf_len - len;
      if (to_read > left_to_read(conn)) {
        to_read = (int) left_to_read(conn);
      }
    } while (!eof && (n = pull(NULL, conn, buf + len, to_read)) > 0);
    conn->data_len = conn->request_len + len;

    if (eof) {
      rewind(fp);
      return fp;
    } else {
      fclose(fp);
    }
  }

  return NULL;
}