diff --git a/examples/Makefile b/examples/Makefile index 497b1f7c237cc800a3f4e9aacd7eaea63a18d24e..863b80128085f271125ccba4e97df03da434bbb0 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -6,4 +6,5 @@ all: $(CC) $(CFLAGS) hello.c ../mongoose.c $$LIBS $(ADD) -o hello; $(CC) $(CFLAGS) upload.c ../mongoose.c $$LIBS $(ADD) -o upload; $(CC) $(CFLAGS) post.c ../mongoose.c $$LIBS $(ADD) -o post; + $(CC) $(CFLAGS) -DUSE_WEBSOCKET websocket.c ../mongoose.c $$LIBS $(ADD) -o websocket; $(CC) $(CFLAGS) chat.c ../mongoose.c $$LIBS $(ADD) -o chat diff --git a/mongoose.c b/mongoose.c index a0daeebb7d6d003c0e4aee16ccb2c957b334ed57..5488595a9ecb0b9312dedb610e3d763b42c55215 100644 --- a/mongoose.c +++ b/mongoose.c @@ -3494,6 +3494,243 @@ static void handle_propfind(struct mg_connection *conn, const char* path, conn->num_bytes_sent += mg_printf(conn, "%s\n", "</d:multistatus>"); } +#if defined(USE_WEBSOCKET) + +// START OF SHA-1 code +// Copyright(c) By Steve Reid <steve@edmweb.com> +#define SHA1HANDSOFF +#if defined(__sun) +#include "solarisfixes.h" +#endif +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) +#if BYTE_ORDER == LITTLE_ENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#elif BYTE_ORDER == BIG_ENDIAN +#define blk0(i) block->l[i] +#else +#error "Endianness not defined!" +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + +typedef struct { + uint32_t state[5]; + uint32_t count[2]; + unsigned char buffer[64]; +} SHA1_CTX; + +static void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]) { + uint32_t a, b, c, d, e; + typedef union { unsigned char c[64]; uint32_t l[16]; } CHAR64LONG16; + + CHAR64LONG16 block[1]; + memcpy(block, buffer, 64); + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + a = b = c = d = e = 0; + memset(block, '\0', sizeof(block)); +} + +static void SHA1Init(SHA1_CTX* context) { + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + +static void SHA1Update(SHA1_CTX* context, const unsigned char* data, + uint32_t len) { + uint32_t i, j; + + j = context->count[0]; + if ((context->count[0] += len << 3) < j) + context->count[1]++; + context->count[1] += (len>>29); + j = (j >> 3) & 63; + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + SHA1Transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + +static void SHA1Final(unsigned char digest[20], SHA1_CTX* context) { + unsigned i; + unsigned char finalcount[8], c; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); + } + c = 0200; + SHA1Update(context, &c, 1); + while ((context->count[0] & 504) != 448) { + c = 0000; + SHA1Update(context, &c, 1); + } + SHA1Update(context, finalcount, 8); + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + memset(context, '\0', sizeof(*context)); + memset(&finalcount, '\0', sizeof(finalcount)); +} +// END OF SHA1 CODE + +static void base64_encode(const unsigned char *src, int src_len, char *dst) { + static const char *b64 = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + int i, j, a, b, c; + + for (i = j = 0; i < src_len; i += 3) { + a = src[i]; + b = i + 1 >= src_len ? 0 : src[i + 1]; + c = i + 2 >= src_len ? 0 : src[i + 2]; + + dst[j++] = b64[a >> 2]; + dst[j++] = b64[((a & 3) << 4) | (b >> 4)]; + if (i + 1 < src_len) { + dst[j++] = b64[(b & 15) << 2 | (c >> 6)]; + } + if (i + 2 < src_len) { + dst[j++] = b64[c & 63]; + } + } + while (j % 4 != 0) { + dst[j++] = '='; + } + dst[j++] = '\0'; +} + +static void send_websocket_handshake(struct mg_connection *conn) { + static const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + char buf[100], sha[20], b64_sha[sizeof(sha) * 2]; + SHA1_CTX sha_ctx; + + mg_snprintf(conn, buf, sizeof(buf), "%s%s", + mg_get_header(conn, "Sec-WebSocket-Key"), magic); + SHA1Init(&sha_ctx); + SHA1Update(&sha_ctx, (unsigned char *) buf, strlen(buf)); + SHA1Final((unsigned char *) sha, &sha_ctx); + base64_encode((unsigned char *) sha, sizeof(sha), b64_sha); + mg_printf(conn, "%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 read_websocket(struct mg_connection *conn) { + unsigned char *mask, *buf = (unsigned char *) conn->body; + int n, len, mask_len, body_len; + + for (;;) { + if ((body_len = conn->data_len - conn->request_len) >= 2) { + len = buf[1] & 127; + mask_len = buf[1] & 128 ? 4 : 0; + if (len < 126) { + conn->content_len = 2 + mask_len + len; + mask = buf + 2; + } else if (len == 126 && body_len >= 4) { + conn->content_len = 2 + mask_len + ((((int) buf[2]) << 8) + buf[3]); + mask = buf + 4; + } else if (body_len >= 10) { + conn->content_len = 2 + mask_len + + ((uint64_t) htonl(* (uint32_t *) &buf[2])) << 32 | + htonl(* (uint32_t *) &buf[6]); + mask = buf + 10; + } + } + + if (conn->content_len > 0) { + conn->next_request = conn->buf + conn->data_len; + if (call_user(conn, MG_WEBSOCKET_MESSAGE) != NULL) { + break; // Callback signalled to exit + } + } else { + if (wait_until_socket_is_readable(conn) == 0) { + break; + } + n = pull(NULL, conn, conn->buf + conn->data_len, + conn->buf_size - conn->data_len); + if (n <= 0) { + break; + } + conn->data_len += n; + } + } +} + +static void handle_websocket_request(struct mg_connection *conn) { + if (strcmp(mg_get_header(conn, "Sec-WebSocket-Version"), "13") != 0) { + send_http_error(conn, 426, "Upgrade Required", "%s", "Upgrade Required"); + } else if (call_user(conn, MG_WEBSOCKET_CONNECT) != NULL) { + // Callback has returned non-NULL, do not proceed with handshake + } else { + send_websocket_handshake(conn); + call_user(conn, MG_WEBSOCKET_READY); + read_websocket(conn); + } +} + +static int is_websocket_request(const struct mg_connection *conn) { + const char *host, *upgrade, *connection, *version, *key; + + host = mg_get_header(conn, "Host"); + upgrade = mg_get_header(conn, "Upgrade"); + connection = mg_get_header(conn, "Connection"); + key = mg_get_header(conn, "Sec-WebSocket-Key"); + version = mg_get_header(conn, "Sec-WebSocket-Version"); + + return host != NULL && upgrade != NULL && connection != NULL && + key != NULL && version != NULL && + !mg_strcasecmp(upgrade, "websocket") && + !mg_strcasecmp(connection, "Upgrade"); +} +#endif // !USE_WEBSOCKET + // This is the heart of the Mongoose's logic. // This function is called when the request is read, parsed and validated, // and Mongoose must decide what action to take: serve a file, or @@ -3515,6 +3752,10 @@ static void handle_request(struct mg_connection *conn) { DEBUG_TRACE(("%s", ri->uri)); if (!check_authorization(conn, path)) { send_authorization_request(conn); +#if defined(USE_WEBSOCKET) + } else if (is_websocket_request(conn)) { + handle_websocket_request(conn); +#endif } else if (call_user(conn, MG_NEW_REQUEST) != NULL) { // Do nothing, callback has served the request } else if (!strcmp(ri->request_method, "OPTIONS")) { diff --git a/mongoose.h b/mongoose.h index 4923d9aa3395791887e04b0af0296dcfe78d58ce..52f3070dba21bfb6fa13bd7926beab15f42b0d44 100644 --- a/mongoose.h +++ b/mongoose.h @@ -49,16 +49,23 @@ struct mg_request_info { } http_headers[64]; // Maximum 64 headers }; + // Various events on which user-defined function is called by Mongoose. enum mg_event { MG_NEW_REQUEST, // New HTTP request has arrived from the client MG_REQUEST_COMPLETE, // Mongoose has finished handling the request MG_HTTP_ERROR, // HTTP error must be returned to the client MG_EVENT_LOG, // Mongoose logs an event, request_info.log_message - MG_INIT_SSL // Mongoose initializes SSL. Instead of mg_connection *, - // SSL context is passed to the callback function. + MG_INIT_SSL, // SSL initialization, sent before certificate setup + MG_WEBSOCKET_CONNECT, // Sent on HTTP connect, before websocket handshake. + // If user callback returns NULL, then mongoose proceeds + // with handshake, otherwise it closes the connection. + MG_WEBSOCKET_READY, // Handshake has been successfully completed. + MG_WEBSOCKET_MESSAGE, // Incoming message from the client + MG_WEBSOCKET_CLOSE, // Client has sent FIN frame }; + // Prototype for the user-defined function. Mongoose calls this function // on every MG_* event. // @@ -74,8 +81,7 @@ enum mg_event { // If handler returns NULL, that means that handler has not processed // the request. Handler must not send any data to the client in this case. // Mongoose proceeds with request handling as if nothing happened. -typedef void * (*mg_callback_t)(enum mg_event event, - struct mg_connection *conn); +typedef void *(*mg_callback_t)(enum mg_event event, struct mg_connection *conn); // Start web server. diff --git a/test/unit_test.c b/test/unit_test.c index ea2befdf20abd0da5b85beda66ad59b8ff6ede1b..f27c5b6ef81bab3d5c61e4946db73cb547f39f6d 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -196,7 +196,21 @@ static void test_mg_fetch(void) { mg_stop(ctx); } +static void test_base64_encode(void) { + const char *in[] = {"a", "ab", "abc", "abcd", NULL}; + const char *out[] = {"YQ==", "YWI=", "YWJj", "YWJjZA=="}; + char buf[100]; + int i; + + for (i = 0; in[i] != NULL; i++) { + base64_encode((unsigned char *) in[i], strlen(in[i]), buf); + printf("[%s] [%s]\n", out[i], buf); + ASSERT(!strcmp(buf, out[i])); + } +} + int main(void) { + test_base64_encode(); test_match_prefix(); test_remove_double_dots(); test_should_keep_alive();