/* * Copyright (c) 2014 Cesanta Software Limited * All rights reserved */ #ifndef NS_DISABLE_HTTP_WEBSOCKET #include "net_skeleton.h" #include "sha1.h" #include "util.h" #include "http.h" /* * Check whether full request is buffered. Return: * -1 if request is malformed * 0 if request is not yet fully buffered * >0 actual request length, including last \r\n\r\n */ static int get_request_len(const char *s, int buf_len) { const unsigned char *buf = (unsigned char *) s; int i; for (i = 0; i < buf_len; i++) { if (!isprint(buf[i]) && buf[i] != '\r' && buf[i] != '\n' && buf[i] < 128) { return -1; } else if (buf[i] == '\n' && i + 1 < buf_len && buf[i + 1] == '\n') { return i + 2; } else if (buf[i] == '\n' && i + 2 < buf_len && buf[i + 1] == '\r' && buf[i + 2] == '\n') { return i + 3; } } return 0; } int ns_parse_http(const char *s, int n, struct http_message *req) { const char *end; int len, i; if ((len = get_request_len(s, n)) <= 0) return len; memset(req, 0, sizeof(*req)); req->message.p = s; req->body.p = s + len; req->message.len = req->body.len = (size_t) ~0; end = s + len; /* Request is fully buffered. Skip leading whitespaces. */ while (s < end && isspace(* (unsigned char *) s)) s++; /* Parse request line: method, URI, proto */ s = ns_skip(s, end, " ", &req->method); s = ns_skip(s, end, " ", &req->uri); s = ns_skip(s, end, "\r\n", &req->proto); if (req->uri.p <= req->method.p || req->proto.p <= req->uri.p) return -1; for (i = 0; i < (int) ARRAY_SIZE(req->header_names); i++) { struct ns_str *k = &req->header_names[i], *v = &req->header_values[i]; s = ns_skip(s, end, ": ", k); s = ns_skip(s, end, "\r\n", v); while (v->len > 0 && v->p[v->len - 1] == ' ') { v->len--; /* Trim trailing spaces in header value */ } if (k->len == 0 || v->len == 0) { k->p = v->p = NULL; break; } if (!ns_ncasecmp(k->p, "Content-Length", 14)) { req->body.len = to64(v->p); req->message.len = len + req->body.len; } } if (req->body.len == (size_t) ~0 && ns_vcasecmp(&req->method, "GET") == 0) { req->body.len = 0; req->message.len = len; } return len; } struct ns_str *ns_get_http_header(struct http_message *hm, const char *name) { size_t i, len = strlen(name); for (i = 0; i < ARRAY_SIZE(hm->header_names); i++) { struct ns_str *h = &hm->header_names[i], *v = &hm->header_values[i]; if (h->p != NULL && h->len == len && !ns_ncasecmp(h->p, name, len)) return v; } return NULL; } static int is_ws_fragment(unsigned char flags) { return (flags & 0x80) == 0 || (flags & 0x0f) == 0; } static int is_ws_first_fragment(unsigned char flags) { return (flags & 0x80) == 0 && (flags & 0x0f) != 0; } static int deliver_websocket_data(struct ns_connection *nc) { /* Using unsigned char *, cause of integer arithmetic below */ uint64_t i, data_len = 0, frame_len = 0, buf_len = nc->recv_iobuf.len, len, mask_len = 0, header_len = 0; unsigned char *p = (unsigned char *) nc->recv_iobuf.buf, *buf = p, *e = p + buf_len; unsigned *sizep = (unsigned *) &p[1]; /* Size ptr for defragmented frames */ int ok, reass = buf_len > 0 && is_ws_fragment(p[0]) && !(nc->flags & NSF_WEBSOCKET_NO_DEFRAG); /* If that's a continuation frame that must be reassembled, handle it */ if (reass && !is_ws_first_fragment(p[0]) && buf_len >= 1 + sizeof(*sizep) && buf_len >= 1 + sizeof(*sizep) + *sizep) { buf += 1 + sizeof(*sizep) + *sizep; buf_len -= 1 + sizeof(*sizep) + *sizep; } if (buf_len >= 2) { len = buf[1] & 127; mask_len = buf[1] & 128 ? 4 : 0; if (len < 126 && buf_len >= mask_len) { data_len = len; header_len = 2 + mask_len; } else if (len == 126 && buf_len >= 4 + mask_len) { header_len = 4 + mask_len; data_len = ntohs(* (uint16_t *) &buf[2]); } else if (buf_len >= 10 + mask_len) { header_len = 10 + mask_len; data_len = (((uint64_t) ntohl(* (uint32_t *) &buf[2])) << 32) + ntohl(* (uint32_t *) &buf[6]); } } frame_len = header_len + data_len; ok = frame_len > 0 && frame_len <= buf_len; if (ok) { struct websocket_message wsm; wsm.size = (size_t) data_len; wsm.data = buf + header_len; wsm.flags = buf[0]; /* Apply mask if necessary */ if (mask_len > 0) { for (i = 0; i < data_len; i++) { buf[i + header_len] ^= (buf + header_len - mask_len)[i % 4]; } } if (reass) { /* On first fragmented frame, nullify size */ if (is_ws_first_fragment(wsm.flags)) { iobuf_resize(&nc->recv_iobuf, nc->recv_iobuf.size + sizeof(*sizep)); p[0] &= ~0x0f; /* Next frames will be treated as continuation */ buf = p + 1 + sizeof(*sizep); *sizep = 0; /* TODO(lsm): fix. this can stomp over frame data */ } /* Append this frame to the reassembled buffer */ memmove(buf, wsm.data, e - wsm.data); (*sizep) += wsm.size; nc->recv_iobuf.len -= wsm.data - buf; /* On last fragmented frame - call user handler and remove data */ if (wsm.flags & 0x80) { wsm.data = p + 1 + sizeof(*sizep); wsm.size = *sizep; nc->handler(nc, NS_WEBSOCKET_FRAME, &wsm); iobuf_remove(&nc->recv_iobuf, 1 + sizeof(*sizep) + *sizep); } } else { /* TODO(lsm): properly handle OOB control frames during defragmentation */ nc->handler(nc, NS_WEBSOCKET_FRAME, &wsm); /* Call handler */ iobuf_remove(&nc->recv_iobuf, (size_t) frame_len); /* Cleanup frame */ } } return ok; } static void ns_send_ws_header(struct ns_connection *nc, int op, size_t len) { int header_len; unsigned char header[10]; header[0] = 0x80 + (op & 0x0f); if (len < 126) { header[1] = len; header_len = 2; } else if (len < 65535) { header[1] = 126; * (uint16_t *) &header[2] = htons((uint16_t) len); header_len = 4; } else { header[1] = 127; * (uint32_t *) &header[2] = htonl((uint32_t) ((uint64_t) len >> 32)); * (uint32_t *) &header[6] = htonl((uint32_t) (len & 0xffffffff)); header_len = 10; } ns_send(nc, header, header_len); } void ns_send_websocket_frame(struct ns_connection *nc, int op, const void *data, size_t len) { ns_send_ws_header(nc, op, len); ns_send(nc, data, len); if (op == WEBSOCKET_OP_CLOSE) { nc->flags |= NSF_FINISHED_SENDING_DATA; } } void ns_send_websocket_framev(struct ns_connection *nc, int op, const struct ns_str *strv, int strvcnt) { int i; int len = 0; for (i=0; iflags |= NSF_FINISHED_SENDING_DATA; } } void ns_printf_websocket_frame(struct ns_connection *nc, int op, const char *fmt, ...) { char mem[4192], *buf = mem; va_list ap; int len; va_start(ap, fmt); if ((len = ns_avprintf(&buf, sizeof(mem), fmt, ap)) > 0) { ns_send_websocket_frame(nc, op, buf, len); } va_end(ap); if (buf != mem && buf != NULL) { free(buf); } } static void websocket_handler(struct ns_connection *nc, int ev, void *ev_data) { nc->handler(nc, ev, ev_data); switch (ev) { case NS_RECV: do { } while (deliver_websocket_data(nc)); break; default: break; } } static void ws_handshake(struct ns_connection *nc, const struct ns_str *key) { static const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; char buf[500], sha[20], b64_sha[sizeof(sha) * 2]; SHA1_CTX sha_ctx; snprintf(buf, sizeof(buf), "%.*s%s", (int) key->len, key->p, magic); SHA1Init(&sha_ctx); SHA1Update(&sha_ctx, (unsigned char *) buf, strlen(buf)); SHA1Final((unsigned char *) sha, &sha_ctx); ns_base64_encode((unsigned char *) sha, sizeof(sha), b64_sha); ns_printf(nc, "%s%s%s", "HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Accept: ", b64_sha, "\r\n\r\n"); } static void http_handler(struct ns_connection *nc, int ev, void *ev_data) { struct iobuf *io = &nc->recv_iobuf; struct http_message hm; struct ns_str *vec; int req_len; /* * For HTTP messages without Content-Length, always send HTTP message * before NS_CLOSE message. */ if (ev == NS_CLOSE && io->len > 0 && ns_parse_http(io->buf, io->len, &hm) > 0) { hm.body.len = io->buf + io->len - hm.body.p; nc->handler(nc, nc->listener ? NS_HTTP_REQUEST : NS_HTTP_REPLY, &hm); } nc->handler(nc, ev, ev_data); if (ev == NS_RECV) { req_len = ns_parse_http(io->buf, io->len, &hm); if (req_len < 0 || (req_len == 0 && io->len >= NS_MAX_HTTP_REQUEST_SIZE)) { nc->flags |= NSF_CLOSE_IMMEDIATELY; } else if (req_len == 0) { /* Do nothing, request is not yet fully buffered */ } else if (nc->listener == NULL && ns_get_http_header(&hm, "Sec-WebSocket-Accept")) { /* We're websocket client, got handshake response from server. */ /* TODO(lsm): check the validity of accept Sec-WebSocket-Accept */ iobuf_remove(io, req_len); nc->proto_handler = websocket_handler; nc->flags |= NSF_IS_WEBSOCKET; nc->handler(nc, NS_WEBSOCKET_HANDSHAKE_DONE, NULL); websocket_handler(nc, NS_RECV, ev_data); } else if (nc->listener != NULL && (vec = ns_get_http_header(&hm, "Sec-WebSocket-Key")) != NULL) { /* This is a websocket request. Switch protocol handlers. */ iobuf_remove(io, req_len); nc->proto_handler = websocket_handler; nc->flags |= NSF_IS_WEBSOCKET; /* Send handshake */ nc->handler(nc, NS_WEBSOCKET_HANDSHAKE_REQUEST, &hm); if (!(nc->flags & NSF_CLOSE_IMMEDIATELY)) { if (nc->send_iobuf.len == 0) { ws_handshake(nc, vec); } nc->handler(nc, NS_WEBSOCKET_HANDSHAKE_DONE, NULL); websocket_handler(nc, NS_RECV, ev_data); } } else if (hm.message.len <= io->len) { /* Whole HTTP message is fully buffered, call event handler */ nc->handler(nc, nc->listener ? NS_HTTP_REQUEST : NS_HTTP_REPLY, &hm); iobuf_remove(io, hm.message.len); } } } void ns_set_protocol_http_websocket(struct ns_connection *nc) { nc->proto_handler = http_handler; } void ns_send_websocket_handshake(struct ns_connection *nc, const char *uri, const char *extra_headers) { unsigned long random = (unsigned long) uri; char key[sizeof(random) * 2]; ns_base64_encode((unsigned char *) &random, sizeof(random), key); ns_printf(nc, "GET %s HTTP/1.1\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Version: 13\r\n" "Sec-WebSocket-Key: %s\r\n" "%s\r\n", uri, key, extra_headers == NULL ? "" : extra_headers); } void ns_send_http_file(struct ns_connection *nc, const char *path, ns_stat_t *st) { char buf[BUFSIZ]; size_t n; FILE *fp; if ((fp = fopen(path, "rb")) != NULL) { ns_printf(nc, "HTTP/1.1 200 OK\r\n" "Content-Length: %lu\r\n\r\n", (unsigned long) st->st_size); while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) { ns_send(nc, buf, n); } fclose(fp); } else { ns_printf(nc, "%s", "HTTP/1.1 500 Server Error\r\n" "Content-Length: 0\r\n\r\n"); } } static void remove_double_dots(char *s) { char *p = s; while (*s != '\0') { *p++ = *s++; if (s[-1] == '/' || s[-1] == '\\') { while (s[0] != '\0') { if (s[0] == '/' || s[0] == '\\') { s++; } else if (s[0] == '.' && s[1] == '.') { s += 2; } else { break; } } } } *p = '\0'; } int ns_url_decode(const char *src, int src_len, char *dst, int dst_len, int is_form_url_encoded) { int i, j, a, b; #define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W') for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) { if (src[i] == '%' && i < src_len - 2 && isxdigit(* (const unsigned char *) (src + i + 1)) && isxdigit(* (const unsigned char *) (src + i + 2))) { a = tolower(* (const unsigned char *) (src + i + 1)); b = tolower(* (const unsigned char *) (src + i + 2)); dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b)); i += 2; } else if (is_form_url_encoded && src[i] == '+') { dst[j] = ' '; } else { dst[j] = src[i]; } } dst[j] = '\0'; /* Null-terminate the destination */ return i >= src_len ? j : -1; } int ns_get_http_var(const struct ns_str *buf, 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 (buf->p == NULL || name == NULL || buf->len == 0) { len = -1; dst[0] = '\0'; } else { name_len = strlen(name); e = buf->p + buf->len; len = -1; dst[0] = '\0'; for (p = buf->p; p + name_len < e; p++) { if ((p == buf->p || p[-1] == '&') && p[name_len] == '=' && !ns_ncasecmp(name, p, name_len)) { p += name_len + 1; s = (const char *) memchr(p, '&', (size_t)(e - p)); if (s == NULL) { s = e; } len = ns_url_decode(p, (size_t)(s - p), dst, dst_len, 1); if (len == -1) { len = -2; } break; } } } return len; } void ns_serve_http(struct ns_connection *nc, struct http_message *hm, struct ns_serve_http_opts opts) { char path[NS_MAX_PATH]; ns_stat_t st; snprintf(path, sizeof(path), "%s/%.*s", opts.document_root, (int) hm->uri.len, hm->uri.p); remove_double_dots(path); if (ns_stat(path, &st) != 0) { ns_printf(nc, "%s", "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"); } else if (S_ISDIR(st.st_mode)) { strncat(path, "/index.html", sizeof(path) - (strlen(path) + 1)); if (ns_stat(path, &st) == 0) { ns_send_http_file(nc, path, &st); } else { ns_printf(nc, "%s", "HTTP/1.1 403 Access Denied\r\n" "Content-Length: 0\r\n\r\n"); } } else { ns_send_http_file(nc, path, &st); } } #endif /* NS_DISABLE_HTTP_WEBSOCKET */