From 6b9c8bf4436ccda62ca9ef3b841d1b9162abbb90 Mon Sep 17 00:00:00 2001
From: Sakari Kapanen <sakari.m.kapanen@gmail.com>
Date: Sun, 10 Dec 2017 22:38:33 +0200
Subject: [PATCH] httpd: integrate Websocket support

Original author: lujji
https://github.com/lujji/esp-httpd
Improvements by Sakari Kapanen
---
 src/Filelists.mk                   |   2 +
 src/apps/httpd/base64.c            | 134 +++++++++++++
 src/apps/httpd/base64.h            |  27 +++
 src/apps/httpd/httpd.c             | 380 ++++++++++++++++++++++++++++++++++++-
 src/apps/httpd/sha1.c              | 149 +++++++++++++++
 src/apps/httpd/sha1.h              |  35 ++++
 src/core/def.c                     |  28 ++-
 src/include/lwip/apps/httpd.h      |  39 ++++
 src/include/lwip/apps/httpd_opts.h |   8 +-
 src/include/lwip/def.h             |   4 +
 10 files changed, 797 insertions(+), 9 deletions(-)
 create mode 100644 src/apps/httpd/base64.c
 create mode 100644 src/apps/httpd/base64.h
 create mode 100644 src/apps/httpd/sha1.c
 create mode 100644 src/apps/httpd/sha1.h

diff --git a/src/Filelists.mk b/src/Filelists.mk
index e180e2a1..a0dcf0e3 100644
--- a/src/Filelists.mk
+++ b/src/Filelists.mk
@@ -156,6 +156,8 @@ SNMPFILES=$(LWIPDIR)/apps/snmp/snmp_asn1.c \
 # HTTPDFILES: HTTP server
 HTTPDFILES=$(LWIPDIR)/apps/httpd/fs.c \
 	$(LWIPDIR)/apps/httpd/httpd.c
+	$(LWIPDIR)/apps/httpd/base64.c
+	$(LWIPDIR)/apps/httpd/sha1.c
 
 # MAKEFSDATA: MAKEFSDATA HTTP server host utility
 MAKEFSDATAFILES=$(LWIPDIR)/apps/httpd/makefsdata/makefsdata.c
diff --git a/src/apps/httpd/base64.c b/src/apps/httpd/base64.c
new file mode 100644
index 00000000..478a0e09
--- /dev/null
+++ b/src/apps/httpd/base64.c
@@ -0,0 +1,134 @@
+/*********************************************************************
+* Filename:   base64.c
+* Author:     Brad Conte (brad AT bradconte.com)
+* Copyright:
+* Disclaimer: This code is presented "as is" without any guarantees.
+* Details:    Implementation of the Base64 encoding algorithm.
+*********************************************************************/
+
+/*************************** HEADER FILES ***************************/
+#include <stdlib.h>
+#include "base64.h"
+
+/****************************** MACROS ******************************/
+#define NEWLINE_INVL 76
+
+/**************************** VARIABLES *****************************/
+// Note: To change the charset to a URL encoding, replace the '+' and '/' with '*' and '-'
+static const BYTE charset[]={"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"};
+
+/*********************** FUNCTION DEFINITIONS ***********************/
+BYTE revchar(char ch)
+{
+	if (ch >= 'A' && ch <= 'Z')
+		ch -= 'A';
+	else if (ch >= 'a' && ch <='z')
+		ch = ch - 'a' + 26;
+	else if (ch >= '0' && ch <='9')
+		ch = ch - '0' + 52;
+	else if (ch == '+')
+		ch = 62;
+	else if (ch == '/')
+		ch = 63;
+
+	return(ch);
+}
+
+size_t base64_encode(const BYTE in[], BYTE out[], size_t len, int newline_flag)
+{
+	size_t idx, idx2, blks, blk_ceiling, left_over, newline_count = 0;
+
+	blks = (len / 3);
+	left_over = len % 3;
+
+	if (out == NULL) {
+		idx2 = blks * 4 ;
+		if (left_over)
+			idx2 += 4;
+		if (newline_flag)
+			idx2 += len / 57;   // (NEWLINE_INVL / 4) * 3 = 57. One newline per 57 input bytes.
+	}
+	else {
+		// Since 3 input bytes = 4 output bytes, determine out how many even sets of
+		// 3 bytes the input has.
+		blk_ceiling = blks * 3;
+		for (idx = 0, idx2 = 0; idx < blk_ceiling; idx += 3, idx2 += 4) {
+			out[idx2]     = charset[in[idx] >> 2];
+			out[idx2 + 1] = charset[((in[idx] & 0x03) << 4) | (in[idx + 1] >> 4)];
+			out[idx2 + 2] = charset[((in[idx + 1] & 0x0f) << 2) | (in[idx + 2] >> 6)];
+			out[idx2 + 3] = charset[in[idx + 2] & 0x3F];
+			// The offical standard requires a newline every 76 characters.
+			// (Eg, first newline is character 77 of the output.)
+			if (((idx2 - newline_count + 4) % NEWLINE_INVL == 0) && newline_flag) {
+				out[idx2 + 4] = '\n';
+				idx2++;
+				newline_count++;
+			}
+		}
+
+		if (left_over == 1) {
+			out[idx2]     = charset[in[idx] >> 2];
+			out[idx2 + 1] = charset[(in[idx] & 0x03) << 4];
+			out[idx2 + 2] = '=';
+			out[idx2 + 3] = '=';
+			idx2 += 4;
+		}
+		else if (left_over == 2) {
+			out[idx2]     = charset[in[idx] >> 2];
+			out[idx2 + 1] = charset[((in[idx] & 0x03) << 4) | (in[idx + 1] >> 4)];
+			out[idx2 + 2] = charset[(in[idx + 1] & 0x0F) << 2];
+			out[idx2 + 3] = '=';
+			idx2 += 4;
+		}
+	}
+
+	return(idx2);
+}
+
+size_t base64_decode(const BYTE in[], BYTE out[], size_t len)
+{
+	size_t idx, idx2, blks, blk_ceiling, left_over;
+
+	if (in[len - 1] == '=')
+		len--;
+	if (in[len - 1] == '=')
+		len--;
+
+	blks = len / 4;
+	left_over = len % 4;
+
+	if (out == NULL) {
+		if (len >= 77 && in[NEWLINE_INVL] == '\n')   // Verify that newlines where used.
+			len -= len / (NEWLINE_INVL + 1);
+		blks = len / 4;
+		left_over = len % 4;
+
+		idx = blks * 3;
+		if (left_over == 2)
+			idx ++;
+		else if (left_over == 3)
+			idx += 2;
+	}
+	else {
+		blk_ceiling = blks * 4;
+		for (idx = 0, idx2 = 0; idx2 < blk_ceiling; idx += 3, idx2 += 4) {
+			if (in[idx2] == '\n')
+				idx2++;
+			out[idx]     = (revchar(in[idx2]) << 2) | ((revchar(in[idx2 + 1]) & 0x30) >> 4);
+			out[idx + 1] = (revchar(in[idx2 + 1]) << 4) | (revchar(in[idx2 + 2]) >> 2);
+			out[idx + 2] = (revchar(in[idx2 + 2]) << 6) | revchar(in[idx2 + 3]);
+		}
+
+		if (left_over == 2) {
+			out[idx]     = (revchar(in[idx2]) << 2) | ((revchar(in[idx2 + 1]) & 0x30) >> 4);
+			idx++;
+		}
+		else if (left_over == 3) {
+			out[idx]     = (revchar(in[idx2]) << 2) | ((revchar(in[idx2 + 1]) & 0x30) >> 4);
+			out[idx + 1] = (revchar(in[idx2 + 1]) << 4) | (revchar(in[idx2 + 2]) >> 2);
+			idx += 2;
+		}
+	}
+
+	return(idx);
+}
diff --git a/src/apps/httpd/base64.h b/src/apps/httpd/base64.h
new file mode 100644
index 00000000..e35c6c7d
--- /dev/null
+++ b/src/apps/httpd/base64.h
@@ -0,0 +1,27 @@
+/*********************************************************************
+* Filename:   base64.h
+* Author:     Brad Conte (brad AT bradconte.com)
+* Copyright:
+* Disclaimer: This code is presented "as is" without any guarantees.
+* Details:    Defines the API for the corresponding Base64 implementation.
+*********************************************************************/
+
+#ifndef BASE64_H
+#define BASE64_H
+
+/*************************** HEADER FILES ***************************/
+#include <stddef.h>
+
+/**************************** DATA TYPES ****************************/
+typedef unsigned char BYTE;             // 8-bit byte
+
+/*********************** FUNCTION DECLARATIONS **********************/
+// Returns the size of the output. If called with out = NULL, will just return
+// the size of what the output would have been (without a terminating NULL).
+size_t base64_encode(const BYTE in[], BYTE out[], size_t len, int newline_flag);
+
+// Returns the size of the output. If called with out = NULL, will just return
+// the size of what the output would have been (without a terminating NULL).
+size_t base64_decode(const BYTE in[], BYTE out[], size_t len);
+
+#endif   // BASE64_H
diff --git a/src/apps/httpd/httpd.c b/src/apps/httpd/httpd.c
index 0ac8d492..96434a3d 100644
--- a/src/apps/httpd/httpd.c
+++ b/src/apps/httpd/httpd.c
@@ -120,7 +120,6 @@
 #define CRLF "\r\n"
 #if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
 #define HTTP11_CONNECTIONKEEPALIVE  "Connection: keep-alive"
-#define HTTP11_CONNECTIONKEEPALIVE2 "Connection: Keep-Alive"
 #endif
 
 #if LWIP_HTTPD_DYNAMIC_FILE_READ
@@ -146,6 +145,42 @@
 #define HTTP_DATA_TO_SEND_CONTINUE 1
 #define HTTP_NO_DATA_TO_SEND       0
 
+#if LWIP_HTTPD_SUPPORT_WEBSOCKET
+
+#include "base64.h"
+#include "sha1.h"
+
+static const char WS_HEADER[] = "Upgrade: websocket" CRLF;
+static const char WS_GUID[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+static const char WS_KEY[] = "Sec-WebSocket-Key: ";
+static const char WS_RSP[] = "HTTP/1.1 101 Switching Protocols" CRLF \
+                             "Upgrade: websocket" CRLF \
+                             "Connection: Upgrade" CRLF \
+                             "Sec-WebSocket-Accept: ";
+
+/* base64 encoded key length */
+#define WS_BASE64_LEN        29
+
+/* Response buffer length */
+#define WS_RSP_LEN           (sizeof(WS_RSP) + sizeof(CRLF CRLF) - 2 + WS_BASE64_LEN)
+
+/* WebSocket timeout: X*(HTTPD_POLL_INTERVAL), default is 10*4*500ms = 20s */
+#ifndef WS_TIMEOUT
+#define WS_TIMEOUT           10
+#endif
+
+struct websocket_state {
+  struct websocket_state* next;
+  struct altcp_pcb* pcb;
+};
+
+/* Callback functions */
+static tWsHandler httpd_websocket_cb = NULL;
+static tWsOpenHandler httpd_websocket_open_cb = NULL;
+/* List of active WebSocket connections */
+static struct websocket_state* websocket_connections = NULL;
+#endif /* LWIP_HTTPD_SUPPORT_WEBSOCKET */
+
 typedef struct {
   const char *name;
   u8_t shtml;
@@ -238,7 +273,10 @@ struct http_state {
   struct fs_file file_handle;
   struct fs_file *handle;
   const char *file;       /* Pointer to first unsent byte in buf. */
-
+#if LWIP_HTTPD_SUPPORT_WEBSOCKET
+  u8_t is_websocket;
+  struct websocket_state* ws_state;
+#endif /* LWIP_HTTPD_SUPPORT_WEBSOCKET */
   struct altcp_pcb *pcb;
 #if LWIP_HTTPD_SUPPORT_REQUESTLIST
   struct pbuf *req;
@@ -287,6 +325,11 @@ LWIP_MEMPOOL_DECLARE(HTTPD_SSI_STATE, MEMP_NUM_PARALLEL_HTTPD_SSI_CONNS, sizeof(
 #define HTTP_FREE_SSI_STATE(x)  LWIP_MEMPOOL_FREE(HTTPD_SSI_STATE, (x))
 #define HTTP_ALLOC_SSI_STATE()  (struct http_ssi_state *)LWIP_MEMPOOL_ALLOC(HTTPD_SSI_STATE)
 #endif /* LWIP_HTTPD_SSI */
+#if LWIP_HTTPD_SUPPORT_WEBSOCKET
+LWIP_MEMPOOL_DECLARE(HTTPD_WEBSOCKET_STATE, MEMP_NUM_PARALLEL_HTTPD_WEBSOCKET_CONNS, sizeof(struct websocket_state), "HTTPD_WEBSOCKET_STATE")
+#define HTTP_FREE_WEBSOCKET_STATE(x)  LWIP_MEMPOOL_FREE(HTTPD_WEBSOCKET_STATE, (x))
+#define HTTP_ALLOC_WEBSOCKET_STATE()  (struct http_websocket_state *)LWIP_MEMPOOL_ALLOC(HTTPD_WEBSOCKET_STATE)
+#endif /* LWIP_HTTPD_SUPPORT_WEBSOCKET */
 #define HTTP_ALLOC_HTTP_STATE() (struct http_state *)LWIP_MEMPOOL_ALLOC(HTTPD_STATE)
 #define HTTP_FREE_HTTP_STATE(x) LWIP_MEMPOOL_FREE(HTTPD_STATE, (x))
 #else /* HTTPD_USE_MEM_POOL */
@@ -296,6 +339,10 @@ LWIP_MEMPOOL_DECLARE(HTTPD_SSI_STATE, MEMP_NUM_PARALLEL_HTTPD_SSI_CONNS, sizeof(
 #define HTTP_ALLOC_SSI_STATE()  (struct http_ssi_state *)mem_malloc(sizeof(struct http_ssi_state))
 #define HTTP_FREE_SSI_STATE(x)  mem_free(x)
 #endif /* LWIP_HTTPD_SSI */
+#if LWIP_HTTPD_SUPPORT_WEBSOCKET
+#define HTTP_ALLOC_WEBSOCKET_STATE()  (struct websocket_state *)mem_malloc(sizeof(struct websocket_state))
+#define HTTP_FREE_WEBSOCKET_STATE(x)  mem_free(x)
+#endif /* LWIP_HTTPD_SUPPORT_WEBSOCKET */
 #endif /* HTTPD_USE_MEM_POOL */
 
 static err_t http_close_conn(struct altcp_pcb *pcb, struct http_state *hs);
@@ -304,6 +351,12 @@ static err_t http_find_file(struct http_state *hs, const char *uri, int is_09);
 static err_t http_init_file(struct http_state *hs, struct fs_file *file, int is_09, const char *uri, u8_t tag_check, char *params);
 static err_t http_poll(void *arg, struct altcp_pcb *pcb);
 static u8_t http_check_eof(struct altcp_pcb *pcb, struct http_state *hs);
+
+#if LWIP_HTTPD_SUPPORT_WEBSOCKET
+static err_t httpd_websocket_send_close(struct altcp_pcb *pcb);
+static void httpd_websocket_add_connection(struct websocket_state *ws);
+static void httpd_websocket_remove_connection(struct websocket_state *ws);
+#endif /* LWIP_HTTPD_SUPPORT_WEBSOCKET */
 #if LWIP_HTTPD_FS_ASYNC_READ
 static void http_continue(void *connection);
 #endif /* LWIP_HTTPD_FS_ASYNC_READ */
@@ -506,6 +559,12 @@ http_state_free(struct http_state *hs)
   if (hs != NULL) {
     http_state_eof(hs);
     http_remove_connection(hs);
+#if LWIP_HTTPD_SUPPORT_WEBSOCKET
+    if (hs->is_websocket && hs->ws_state) {
+      httpd_websocket_remove_connection(hs->ws_state);
+      HTTP_FREE_WEBSOCKET_STATE(hs->ws_state);
+    }
+#endif /* LWIP_HTTPD_SUPPORT_WEBSOCKET */
     HTTP_FREE_HTTP_STATE(hs);
   }
 }
@@ -602,6 +661,19 @@ http_close_or_abort_conn(struct altcp_pcb *pcb, struct http_state *hs, u8_t abor
   }
 #endif /* LWIP_HTTPD_SUPPORT_POST*/
 
+  if (hs != NULL) {
+#if LWIP_HTTPD_SUPPORT_WEBSOCKET
+    if (hs->is_websocket)
+      httpd_websocket_send_close(pcb);
+#endif /* LWIP_HTTPD_SUPPORT_WEBSOCKET */
+
+    if (hs->req != NULL) {
+      /* this should not happen */
+      LWIP_DEBUGF(HTTPD_DEBUG, ("Freeing buffer (malformed request?)\n"));
+      pbuf_free(hs->req);
+      hs->req = NULL;
+    }
+  }
 
   altcp_arg(pcb, NULL);
   altcp_recv(pcb, NULL);
@@ -659,6 +731,13 @@ http_eof(struct altcp_pcb *pcb, struct http_state *hs)
     altcp_nagle_disable(pcb);
   } else
 #endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */
+#if LWIP_HTTPD_SUPPORT_WEBSOCKET
+  if (hs->is_websocket) {
+    http_state_eof(hs);
+    http_state_init(hs);
+    hs->is_websocket = 1;
+  } else
+#endif /* LWIP_HTTPD_SUPPORT_WEBSOCKET */
   {
     http_close_conn(pcb, hs);
   }
@@ -1764,7 +1843,7 @@ http_post_request(struct pbuf *inp, struct http_state *hs,
 #define HTTP_HDR_CONTENT_LEN                "Content-Length: "
 #define HTTP_HDR_CONTENT_LEN_LEN            16
 #define HTTP_HDR_CONTENT_LEN_DIGIT_MAX_LEN  10
-    char *scontent_len = lwip_strnstr(uri_end + 1, HTTP_HDR_CONTENT_LEN, crlfcrlf - (uri_end + 1));
+    char *scontent_len = lwip_strnistr(uri_end + 1, HTTP_HDR_CONTENT_LEN, crlfcrlf - (uri_end + 1));
     if (scontent_len != NULL) {
       char *scontent_len_end = lwip_strnstr(scontent_len + HTTP_HDR_CONTENT_LEN_LEN, CRLF, HTTP_HDR_CONTENT_LEN_DIGIT_MAX_LEN);
       if (scontent_len_end != NULL) {
@@ -1974,6 +2053,67 @@ http_parse_request(struct pbuf *inp, struct http_state *hs, struct altcp_pcb *pc
     }
   }
 
+#if LWIP_HTTPD_SUPPORT_WEBSOCKET
+  /* Parse WebSocket request */
+  hs->is_websocket = 0;
+  if (lwip_strnistr(data, WS_HEADER, data_len)) {
+    LWIP_DEBUGF(HTTPD_DEBUG, ("WebSocket opening handshake\n"));
+    char *key_start = lwip_strnistr(data, WS_KEY, data_len);
+    if (key_start) {
+      key_start += sizeof(WS_KEY) - 1;
+      char *key_end = lwip_strnstr(key_start, CRLF, data_len);
+      if (key_end) {
+        char key[64];
+        int len = sizeof(char) * (key_end - key_start);
+        if ((len + sizeof(WS_GUID) < sizeof(key)) && (len > 0)) {
+          /* Allocate response buffer */
+          unsigned char *retval = mem_malloc(WS_RSP_LEN);
+          if (retval == NULL) {
+            LWIP_DEBUGF(HTTPD_DEBUG, ("Out of memory\n"));
+            return ERR_MEM;
+          }
+          unsigned char *retval_ptr;
+          retval_ptr = memcpy(retval, WS_RSP, sizeof(WS_RSP) - 1);
+          retval_ptr += sizeof(WS_RSP) - 1;
+
+          /* Concatenate key */
+          memcpy(key, key_start, len);
+          strlcpy(&key[len], WS_GUID, sizeof(key));
+          LWIP_DEBUGF(HTTPD_DEBUG, ("Resulting key: %s\n", key));
+
+          /* Get SHA1 */
+          int key_len = sizeof(WS_GUID) - 1 + len;
+          unsigned char sha1sum[SHA1_BLOCK_SIZE];
+          SHA1_CTX ctx;
+          sha1_init(&ctx);
+          sha1_update(&ctx, (unsigned char *) key, key_len);
+          sha1_final(&ctx, sha1sum);
+
+          /* Base64 encode */
+          unsigned int olen = WS_BASE64_LEN;
+          olen = base64_encode(sha1sum, retval_ptr, SHA1_BLOCK_SIZE, 0);
+          if (olen >= 0) {
+            LWIP_DEBUGF(HTTPD_DEBUG, ("Base64 encoded: %s\n", retval_ptr));
+
+            /* Send response */
+            memcpy(&retval_ptr[olen], CRLF CRLF, sizeof(CRLF CRLF));
+            LWIP_DEBUGF(HTTPD_DEBUG, ("Sending:\n%s\n", retval));
+            altcp_write(pcb, retval, WS_RSP_LEN - 1, 0);
+            hs->is_websocket = 1;
+          }
+          mem_free(retval);
+        } else {
+          LWIP_DEBUGF(HTTPD_DEBUG, ("Key overflow"));
+          return ERR_MEM;
+        }
+      }
+    } else {
+      LWIP_DEBUGF(HTTPD_DEBUG, ("error: malformed packet\n"));
+      return ERR_ARG;
+    }
+  }
+#endif /* LWIP_HTTPD_SUPPORT_WEBSOCKET */
+
   /* received enough data for minimal request? */
   if (data_len >= MIN_REQ_LEN) {
     /* wait for CRLF before parsing anything */
@@ -2031,8 +2171,7 @@ http_parse_request(struct pbuf *inp, struct http_state *hs, struct altcp_pcb *pc
 #if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
           /* This is HTTP/1.0 compatible: for strict 1.1, a connection
              would always be persistent unless "close" was specified. */
-          if (!is_09 && (lwip_strnstr(data, HTTP11_CONNECTIONKEEPALIVE, data_len) ||
-                         lwip_strnstr(data, HTTP11_CONNECTIONKEEPALIVE2, data_len))) {
+          if (!is_09 && lwip_strnistr(data, HTTP11_CONNECTIONKEEPALIVE, data_len)) {
             hs->keepalive = 1;
           } else {
             hs->keepalive = 0;
@@ -2064,7 +2203,22 @@ http_parse_request(struct pbuf *inp, struct http_state *hs, struct altcp_pcb *pc
           } else
 #endif /* LWIP_HTTPD_SUPPORT_POST */
           {
-            return http_find_file(hs, uri, is_09);
+#if LWIP_HTTPD_SUPPORT_WEBSOCKET
+            if (hs->is_websocket) {
+              struct websocket_state* ws = HTTP_ALLOC_WEBSOCKET_STATE();
+              if (ws) {
+                ws->pcb = pcb;
+                httpd_websocket_add_connection(ws);
+                hs->ws_state = ws;
+              }
+              if(httpd_websocket_open_cb)
+                httpd_websocket_open_cb(pcb, uri);
+              return ERR_OK; /* We handled this */
+            } else
+#endif /* LWIP_HTTPD_SUPPORT_WEBSOCKET */
+            {
+              return http_find_file(hs, uri, is_09);
+            }
           }
         }
       } else {
@@ -2446,7 +2600,11 @@ http_poll(void *arg, struct altcp_pcb *pcb)
     return ERR_OK;
   } else {
     hs->retries++;
-    if (hs->retries == HTTPD_MAX_RETRIES) {
+    u8_t timeout = HTTPD_MAX_RETRIES;
+#if LWIP_HTTPD_SUPPORT_WEBSOCKET
+    if (hs->is_websocket) timeout = WS_TIMEOUT;
+#endif /* LWIP_HTTPD_SUPPORT_WEBSOCKET */
+    if (hs->retries == timeout) {
       LWIP_DEBUGF(HTTPD_DEBUG, ("http_poll: too many retries, close\n"));
       http_close_conn(pcb, hs);
       return ERR_OK;
@@ -2468,6 +2626,188 @@ http_poll(void *arg, struct altcp_pcb *pcb)
   return ERR_OK;
 }
 
+#if LWIP_HTTPD_SUPPORT_WEBSOCKET
+void
+httpd_websocket_register_callbacks(tWsOpenHandler ws_open_cb, tWsHandler ws_cb)
+{
+  httpd_websocket_open_cb = ws_open_cb;
+  httpd_websocket_cb = ws_cb;
+}
+
+static uint8_t*
+httpd_websocket_prepare_packet(const uint8_t *data, uint16_t *len, uint8_t mode)
+{
+  uint8_t *buf = mem_malloc(*len + 4);
+  if (buf == NULL) {
+    return NULL;
+  }
+
+  int offset = 2;
+  buf[0] = 0x80 | mode;
+  if (*len > 125) {
+    offset = 4;
+    buf[1] = 126;
+    buf[2] = (*len) >> 8;
+    buf[3] = *len;
+  } else {
+    buf[1] = *len;
+  }
+
+  memcpy(&buf[offset], data, *len);
+  *len += offset;
+
+  return buf;
+}
+
+err_t
+httpd_websocket_write(struct altcp_pcb *pcb, const uint8_t *data, uint16_t len, uint8_t mode)
+{
+  uint8_t *buf = httpd_websocket_prepare_packet(data, &len, mode);
+  if (buf == NULL) {
+    LWIP_DEBUGF(HTTPD_DEBUG, ("[websocket_write] out of memory\n"));
+    return ERR_MEM;
+  }
+  LWIP_DEBUGF(HTTPD_DEBUG, ("[websocket_write] sending packet\n"));
+  err_t retval = http_write(pcb, buf, &len, TCP_WRITE_FLAG_COPY);
+  mem_free(buf);
+
+  return retval;
+}
+
+err_t
+httpd_websocket_broadcast(const uint8_t *data, uint16_t len, uint8_t mode)
+{
+  uint8_t *buf = httpd_websocket_prepare_packet(data, &len, mode);
+  if (buf == NULL) {
+    LWIP_DEBUGF(HTTPD_DEBUG, ("[websocket_broadcast] out of memory\n"));
+    return ERR_MEM;
+  }
+
+  struct websocket_state* ws = websocket_connections;
+  while (ws) {
+    LWIP_DEBUGF(HTTPD_DEBUG, ("[websocket_broadcast] sending packet\n"));
+    err_t write_ret = http_write(ws->pcb, buf, &len, TCP_WRITE_FLAG_COPY);
+    if (write_ret) {
+      LWIP_DEBUGF(HTTPD_DEBUG, ("[websocket_broadcast] error sending packet\n"));
+    }
+    ws = ws->next;
+  }
+  mem_free(buf);
+  return ERR_OK;
+}
+
+/**
+ * Send status code 1000 (normal closure).
+ */
+static err_t
+httpd_websocket_send_close(struct altcp_pcb *pcb)
+{
+  const u8_t buf[] = {0x88, 0x02, 0x03, 0xe8};
+  u16_t len = sizeof (buf);
+  LWIP_DEBUGF(HTTPD_DEBUG, ("[wsoc] closing connection\n"));
+  return altcp_write(pcb, buf, len, TCP_WRITE_FLAG_COPY);
+}
+
+/**
+ * Parse websocket frame.
+ *
+ * @return ERR_OK: frame parsed
+ *         ERR_CLSD: close request from client
+ *         ERR_VAL: invalid frame.
+ */
+static err_t
+httpd_websocket_parse(struct altcp_pcb *pcb, struct pbuf *p)
+{
+  u8_t *data = (u8_t *) p->payload;
+  u16_t data_len = p->len;
+
+  if (data != NULL && data_len > 1) {
+    LWIP_DEBUGF(HTTPD_DEBUG, ("[wsoc] frame received\n"));
+    if ((data[0] & 0x80) == 0) {
+      LWIP_DEBUGF(HTTPD_DEBUG, ("Warning: continuation frames not supported\n"));
+      return ERR_OK;
+    }
+    u8_t opcode = data[0] & 0x0F;
+    switch (opcode) {
+      case WS_TEXT_MODE:
+      case WS_BIN_MODE:
+        LWIP_DEBUGF(HTTPD_DEBUG, ("Opcode: 0x%hX, frame length: %d\n", opcode, data_len));
+        if (data_len > 6 && httpd_websocket_cb != NULL) {
+          int data_offset = 6;
+          u8_t *dptr = &data[6];
+          u8_t *kptr = &data[2];
+          u16_t len = data[1] & 0x7F;
+
+          if (len == 127) {
+            /* most likely won't happen inside non-fragmented frame */
+            LWIP_DEBUGF(HTTPD_DEBUG, ("Warning: frame is too long\n"));
+            return ERR_OK;
+          } else if (len == 126) {
+            /* extended length */
+            dptr += 2;
+            kptr += 2;
+            data_offset += 2;
+            len = (data[2] << 8) | data[3];
+          }
+
+          data_len -= data_offset;
+
+          if (len > data_len) {
+            LWIP_DEBUGF(HTTPD_DEBUG, ("Error: incorrect frame size\n"));
+            return ERR_VAL;
+          }
+
+          if (data_len != len)
+            LWIP_DEBUGF(HTTPD_DEBUG, ("Warning: segmented frame received\n"));
+
+          /* unmask */
+          for (int i = 0; i < len; i++)
+            *(dptr++) ^= kptr[i % 4];
+
+          /* user callback */
+          httpd_websocket_cb(pcb, &data[data_offset], len, opcode);
+        }
+        break;
+      case 0x08: // close
+        LWIP_DEBUGF(HTTPD_DEBUG, ("Close request\n"));
+        return ERR_CLSD;
+      default:
+        LWIP_DEBUGF(HTTPD_DEBUG, ("Unsupported opcode 0x%hX\n", opcode));
+        break;
+    }
+    return ERR_OK;
+  }
+  return ERR_VAL;
+}
+
+static void
+httpd_websocket_add_connection(struct websocket_state *ws)
+{
+  /* add the connection to the list */
+  ws->next = websocket_connections;
+  websocket_connections = ws;
+}
+
+static void
+httpd_websocket_remove_connection(struct websocket_state *ws)
+{
+  /* take the connection off the list */
+  if (websocket_connections) {
+    if (websocket_connections == ws) {
+      websocket_connections = ws->next;
+    } else {
+      struct websocket_state *last;
+      for (last = websocket_connections; last->next != NULL; last = last->next) {
+        if (last->next == ws) {
+          last->next = ws->next;
+          break;
+        }
+      }
+    }
+  }
+}
+#endif /* LWIP_HTTPD_SUPPORT_WEBSOCKET */
+
 /**
  * Data has been received on this pcb.
  * For HTTP 1.0, this should normally only happen once (if the request fits in one packet).
@@ -2479,6 +2819,29 @@ http_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err)
   LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_recv: pcb=%p pbuf=%p err=%s\n", (void *)pcb,
               (void *)p, lwip_strerr(err)));
 
+#if LWIP_HTTPD_SUPPORT_WEBSOCKET
+  if (hs != NULL && hs->is_websocket) {
+    if (p == NULL) {
+      LWIP_DEBUGF(HTTPD_DEBUG, ("http_recv: buffer error\n"));
+      http_close_or_abort_conn(pcb, hs, 0);
+      return ERR_BUF;
+    }
+    tcp_recved(pcb, p->tot_len);
+    err_t err = httpd_websocket_parse(pcb, p);
+    if (p != NULL) {
+      /* otherwise tcp buffer hogs */
+      LWIP_DEBUGF(HTTPD_DEBUG, ("[wsoc] freeing buffer\n"));
+      pbuf_free(p);
+    }
+    if (err == ERR_CLSD) {
+      http_close_conn(pcb, hs);
+    }
+    /* reset timeout */
+    hs->retries = 0;
+    return ERR_OK;
+  }
+#endif /* LWIP_HTTPD_SUPPORT_WEBSOCKET */
+
   if ((err != ERR_OK) || (p == NULL) || (hs == NULL)) {
     /* error or closed by other side? */
     if (p != NULL) {
@@ -2551,6 +2914,9 @@ http_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err)
       pbuf_free(p);
     }
   }
+
+  /* reset timeout */
+  hs->retries = 0;
   return ERR_OK;
 }
 
diff --git a/src/apps/httpd/sha1.c b/src/apps/httpd/sha1.c
new file mode 100644
index 00000000..2f9622d4
--- /dev/null
+++ b/src/apps/httpd/sha1.c
@@ -0,0 +1,149 @@
+/*********************************************************************
+* Filename:   sha1.c
+* Author:     Brad Conte (brad AT bradconte.com)
+* Copyright:
+* Disclaimer: This code is presented "as is" without any guarantees.
+* Details:    Implementation of the SHA1 hashing algorithm.
+              Algorithm specification can be found here:
+               * http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf
+              This implementation uses little endian byte order.
+*********************************************************************/
+
+/*************************** HEADER FILES ***************************/
+#include <stdlib.h>
+#include <memory.h>
+#include "sha1.h"
+
+/****************************** MACROS ******************************/
+#define ROTLEFT(a, b) ((a << b) | (a >> (32 - b)))
+
+/*********************** FUNCTION DEFINITIONS ***********************/
+void sha1_transform(SHA1_CTX *ctx, const BYTE data[])
+{
+	WORD a, b, c, d, e, i, j, t, m[80];
+
+	for (i = 0, j = 0; i < 16; ++i, j += 4)
+		m[i] = (data[j] << 24) + (data[j + 1] << 16) + (data[j + 2] << 8) + (data[j + 3]);
+	for ( ; i < 80; ++i) {
+		m[i] = (m[i - 3] ^ m[i - 8] ^ m[i - 14] ^ m[i - 16]);
+		m[i] = (m[i] << 1) | (m[i] >> 31);
+	}
+
+	a = ctx->state[0];
+	b = ctx->state[1];
+	c = ctx->state[2];
+	d = ctx->state[3];
+	e = ctx->state[4];
+
+	for (i = 0; i < 20; ++i) {
+		t = ROTLEFT(a, 5) + ((b & c) ^ (~b & d)) + e + ctx->k[0] + m[i];
+		e = d;
+		d = c;
+		c = ROTLEFT(b, 30);
+		b = a;
+		a = t;
+	}
+	for ( ; i < 40; ++i) {
+		t = ROTLEFT(a, 5) + (b ^ c ^ d) + e + ctx->k[1] + m[i];
+		e = d;
+		d = c;
+		c = ROTLEFT(b, 30);
+		b = a;
+		a = t;
+	}
+	for ( ; i < 60; ++i) {
+		t = ROTLEFT(a, 5) + ((b & c) ^ (b & d) ^ (c & d))  + e + ctx->k[2] + m[i];
+		e = d;
+		d = c;
+		c = ROTLEFT(b, 30);
+		b = a;
+		a = t;
+	}
+	for ( ; i < 80; ++i) {
+		t = ROTLEFT(a, 5) + (b ^ c ^ d) + e + ctx->k[3] + m[i];
+		e = d;
+		d = c;
+		c = ROTLEFT(b, 30);
+		b = a;
+		a = t;
+	}
+
+	ctx->state[0] += a;
+	ctx->state[1] += b;
+	ctx->state[2] += c;
+	ctx->state[3] += d;
+	ctx->state[4] += e;
+}
+
+void sha1_init(SHA1_CTX *ctx)
+{
+	ctx->datalen = 0;
+	ctx->bitlen = 0;
+	ctx->state[0] = 0x67452301;
+	ctx->state[1] = 0xEFCDAB89;
+	ctx->state[2] = 0x98BADCFE;
+	ctx->state[3] = 0x10325476;
+	ctx->state[4] = 0xc3d2e1f0;
+	ctx->k[0] = 0x5a827999;
+	ctx->k[1] = 0x6ed9eba1;
+	ctx->k[2] = 0x8f1bbcdc;
+	ctx->k[3] = 0xca62c1d6;
+}
+
+void sha1_update(SHA1_CTX *ctx, const BYTE data[], size_t len)
+{
+	size_t i;
+
+	for (i = 0; i < len; ++i) {
+		ctx->data[ctx->datalen] = data[i];
+		ctx->datalen++;
+		if (ctx->datalen == 64) {
+			sha1_transform(ctx, ctx->data);
+			ctx->bitlen += 512;
+			ctx->datalen = 0;
+		}
+	}
+}
+
+void sha1_final(SHA1_CTX *ctx, BYTE hash[])
+{
+	WORD i;
+
+	i = ctx->datalen;
+
+	// Pad whatever data is left in the buffer.
+	if (ctx->datalen < 56) {
+		ctx->data[i++] = 0x80;
+		while (i < 56)
+			ctx->data[i++] = 0x00;
+	}
+	else {
+		ctx->data[i++] = 0x80;
+		while (i < 64)
+			ctx->data[i++] = 0x00;
+		sha1_transform(ctx, ctx->data);
+		memset(ctx->data, 0, 56);
+	}
+
+	// Append to the padding the total message's length in bits and transform.
+	ctx->bitlen += ctx->datalen * 8;
+	ctx->data[63] = ctx->bitlen;
+	ctx->data[62] = ctx->bitlen >> 8;
+	ctx->data[61] = ctx->bitlen >> 16;
+	ctx->data[60] = ctx->bitlen >> 24;
+	ctx->data[59] = ctx->bitlen >> 32;
+	ctx->data[58] = ctx->bitlen >> 40;
+	ctx->data[57] = ctx->bitlen >> 48;
+	ctx->data[56] = ctx->bitlen >> 56;
+	sha1_transform(ctx, ctx->data);
+
+	// Since this implementation uses little endian byte ordering and MD uses big endian,
+	// reverse all the bytes when copying the final state to the output hash.
+	for (i = 0; i < 4; ++i) {
+		hash[i]      = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff;
+		hash[i + 4]  = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff;
+		hash[i + 8]  = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff;
+		hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff;
+		hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff;
+	}
+}
diff --git a/src/apps/httpd/sha1.h b/src/apps/httpd/sha1.h
new file mode 100644
index 00000000..f32bb7c0
--- /dev/null
+++ b/src/apps/httpd/sha1.h
@@ -0,0 +1,35 @@
+/*********************************************************************
+* Filename:   sha1.h
+* Author:     Brad Conte (brad AT bradconte.com)
+* Copyright:
+* Disclaimer: This code is presented "as is" without any guarantees.
+* Details:    Defines the API for the corresponding SHA1 implementation.
+*********************************************************************/
+
+#ifndef SHA1_H
+#define SHA1_H
+
+/*************************** HEADER FILES ***************************/
+#include <stddef.h>
+
+/****************************** MACROS ******************************/
+#define SHA1_BLOCK_SIZE 20              // SHA1 outputs a 20 byte digest
+
+/**************************** DATA TYPES ****************************/
+typedef unsigned char BYTE;             // 8-bit byte
+typedef unsigned int  WORD;             // 32-bit word, change to "long" for 16-bit machines
+
+typedef struct {
+	BYTE data[64];
+	WORD datalen;
+	unsigned long long bitlen;
+	WORD state[5];
+	WORD k[4];
+} SHA1_CTX;
+
+/*********************** FUNCTION DECLARATIONS **********************/
+void sha1_init(SHA1_CTX *ctx);
+void sha1_update(SHA1_CTX *ctx, const BYTE data[], size_t len);
+void sha1_final(SHA1_CTX *ctx, BYTE hash[]);
+
+#endif   // SHA1_H
diff --git a/src/core/def.c b/src/core/def.c
index c483aa68..288635a4 100644
--- a/src/core/def.c
+++ b/src/core/def.c
@@ -118,6 +118,32 @@ lwip_strnstr(const char *buffer, const char *token, size_t n)
 }
 #endif
 
+#ifndef lwip_strnistr
+/**
+ * @ingroup sys_nonstandard
+ * lwIP default implementation for strnistr() non-standard function.
+ * This can be \#defined to strnistr() depending on your platform port.
+ */
+char *
+lwip_strnistr(const char *buffer, const char *token, size_t n)
+{
+  const char *p;
+  size_t tokenlen = strlen(token);
+  if (tokenlen == 0) {
+    return LWIP_CONST_CAST(char *, buffer);
+  }
+  char token_upc = *token | 0x20;
+  if (token_upc < 'a' || token_upc > 'z') token_upc = '\0';
+  for (p = buffer; *p && (p + tokenlen <= buffer + n); p++) {
+    if ((*p == *token || (token_upc && (*p | 0x20) == token_upc)) &&
+        (lwip_strnicmp(p, token, tokenlen) == 0)) {
+      return LWIP_CONST_CAST(char *, p);
+    }
+  }
+  return NULL;
+}
+#endif
+
 #ifndef lwip_stricmp
 /**
  * @ingroup sys_nonstandard
@@ -183,7 +209,7 @@ lwip_strnicmp(const char *str1, const char *str2, size_t len)
         return 1;
       }
     }
-  } while (len-- && c1 != 0);
+  } while (--len && c1 != 0);
   return 0;
 }
 #endif
diff --git a/src/include/lwip/apps/httpd.h b/src/include/lwip/apps/httpd.h
index 68f57761..aac0a664 100644
--- a/src/include/lwip/apps/httpd.h
+++ b/src/include/lwip/apps/httpd.h
@@ -237,6 +237,45 @@ void httpd_post_data_recved(void *connection, u16_t recved_len);
 
 #endif /* LWIP_HTTPD_SUPPORT_POST */
 
+#if LWIP_HTTPD_SUPPORT_WEBSOCKET
+typedef enum {
+  WS_TEXT_MODE = 0x01,
+  WS_BIN_MODE  = 0x02,
+} WS_MODE;
+
+typedef void (*tWsHandler)(struct tcp_pcb *pcb, uint8_t *data, u16_t data_len, uint8_t mode);
+typedef void (*tWsOpenHandler)(struct tcp_pcb *pcb, const char *uri);
+
+/**
+ * Write data into a websocket.
+ *
+ * @param pcb tcp_pcb to send.
+ * @param data data to send.
+ * @param len data length.
+ * @param mode WS_TEXT_MODE or WS_BIN_MODE.
+ * @return ERR_OK if write succeeded.
+ */
+err_t httpd_websocket_write(struct tcp_pcb *pcb, const uint8_t *data, uint16_t len, uint8_t mode);
+
+/**
+ * Broadcast a message to all connected WebSocket clients.
+ * 
+ * @param data data to send.
+ * @param len data length.
+ * @param mode WS_TEXT_MODE or WS_BIN_MODE.
+ * @return ERR_OK if write succeeded.
+ */
+err_t httpd_websocket_broadcast(const uint8_t *data, uint16_t len, uint8_t mode);
+
+/**
+ * Register websocket callback functions. Use NULL if callback is not needed.
+ *
+ * @param ws_open_cb called when new websocket is opened.
+ * @param ws_cb called when data is received from client.
+ */
+void httpd_websocket_register_callbacks(tWsOpenHandler ws_open_cb, tWsHandler ws_cb);
+#endif /* LWIP_HTTPD_SUPPORT_WEBSOCKET */
+
 void httpd_init(void);
 
 #if HTTPD_ENABLE_HTTPS
diff --git a/src/include/lwip/apps/httpd_opts.h b/src/include/lwip/apps/httpd_opts.h
index 8723961f..4d60fb4e 100644
--- a/src/include/lwip/apps/httpd_opts.h
+++ b/src/include/lwip/apps/httpd_opts.h
@@ -126,6 +126,11 @@
 #define LWIP_HTTPD_SUPPORT_POST   0
 #endif
 
+/** Set this to 1 to support Websockets */
+#if !defined LWIP_HTTPD_SUPPORT_WEBSOCKET || defined __DOXYGEN__
+#define LWIP_HTTPD_SUPPORT_WEBSOCKET 0
+#endif
+
 /* The maximum number of parameters that the CGI handler can be sent. */
 #if !defined LWIP_HTTPD_MAX_CGI_PARAMETERS || defined __DOXYGEN__
 #define LWIP_HTTPD_MAX_CGI_PARAMETERS 16
@@ -178,7 +183,8 @@
 /** Set this to 1 to use a memp pool for allocating 
  * struct http_state instead of the heap.
  * If enabled, you'll need to define MEMP_NUM_PARALLEL_HTTPD_CONNS
- * (and MEMP_NUM_PARALLEL_HTTPD_SSI_CONNS for SSI) to set the size of
+ * (and MEMP_NUM_PARALLEL_HTTPD_SSI_CONNS for SSI,
+ * MEMP_NUM_PARALLEL_HTTPD_WEBSOCKET_CONNS for WebSockets) to set the size of
  * the pool(s).
  */
 #if !defined HTTPD_USE_MEM_POOL || defined __DOXYGEN__
diff --git a/src/include/lwip/def.h b/src/include/lwip/def.h
index 3d6e107e..004f34f2 100644
--- a/src/include/lwip/def.h
+++ b/src/include/lwip/def.h
@@ -144,6 +144,10 @@ int   lwip_stricmp(const char* str1, const char* str2);
 /* This can be #defined to strnstr() depending on your platform */
 char* lwip_strnstr(const char* buffer, const char* token, size_t n);
 #endif
+#ifndef lwip_strnistr
+/* This can be #defined to strnistr() depending on your platform */
+char* lwip_strnistr(const char* buffer, const char* token, size_t n);
+#endif
 
 #ifdef __cplusplus
 }
-- 
2.15.1

