HTTP_PROXY: make usable
[platform/upstream/libwebsockets.git] / test-server / test-server-http.c
index dacd8e8..ea852c2 100644 (file)
@@ -1,22 +1,21 @@
 /*
  * libwebsockets-test-server - libwebsockets test implementation
  *
- * Copyright (C) 2010-2015 Andy Green <andy@warmcat.com>
+ * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
  *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
  *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
+ * The person who associated a work with this deed has dedicated
+ * the work to the public domain by waiving all of his or her rights
+ * to the work worldwide under copyright law, including all related
+ * and neighboring rights, to the extent allowed by law. You can copy,
+ * modify, distribute and perform the work, even for commercial purposes,
+ * all without asking permission.
  *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
+ * The test apps are intended to be adapted for use in your code, which
+ * may be proprietary.  So unlike the library itself, they are licensed
+ * Public Domain.
  */
 #include "test-server.h"
 
  *                             using this protocol, including the sender
  */
 
+#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
+/* location of the certificate revocation list */
+extern char crl_path[1024];
+#endif
+
+extern int debug_level;
+
 enum demo_protocols {
        /* always first */
        PROTOCOL_HTTP = 0,
@@ -52,7 +58,7 @@ enum demo_protocols {
 struct serveable {
        const char *urlpath;
        const char *mimetype;
-}; 
+};
 
 /*
  * this is just an example of parsing handshake headers, you don't need this
@@ -60,9 +66,9 @@ struct serveable {
  * content
  */
 void
-dump_handshake_info(struct libwebsocket *wsi)
+dump_handshake_info(struct lws *wsi)
 {
-       int n = 0;
+       int n = 0, len;
        char buf[256];
        const unsigned char *c;
 
@@ -73,12 +79,14 @@ dump_handshake_info(struct libwebsocket *wsi)
                        continue;
                }
 
-               if (!lws_hdr_total_length(wsi, n)) {
+               len = lws_hdr_total_length(wsi, n);
+               if (!len || len > sizeof(buf) - 1) {
                        n++;
                        continue;
                }
 
                lws_hdr_copy(wsi, buf, sizeof buf, n);
+               buf[sizeof(buf) - 1] = '\0';
 
                fprintf(stderr, "    %s = %s\n", (char *)c, buf);
                n++;
@@ -101,55 +109,169 @@ const char * get_mimetype(const char *file)
        if (!strcmp(&file[n - 5], ".html"))
                return "text/html";
 
+       if (!strcmp(&file[n - 4], ".css"))
+               return "text/css";
+
+       if (!strcmp(&file[n - 3], ".js"))
+               return "text/javascript";
+
        return NULL;
 }
 
+
+static const char * const param_names[] = {
+       "text",
+       "send",
+       "file",
+       "upload",
+};
+
+enum enum_param_names {
+       EPN_TEXT,
+       EPN_SEND,
+       EPN_FILE,
+       EPN_UPLOAD,
+};
+
+static int
+file_upload_cb(void *data, const char *name, const char *filename,
+              char *buf, int len, enum lws_spa_fileupload_states state)
+{
+       struct per_session_data__http *pss =
+                       (struct per_session_data__http *)data;
+       int n;
+
+       switch (state) {
+       case LWS_UFS_OPEN:
+               strncpy(pss->filename, filename, sizeof(pss->filename) - 1);
+               /* we get the original filename in @filename arg, but for
+                * simple demo use a fixed name so we don't have to deal with
+                * attacks  */
+               pss->post_fd = (lws_filefd_type)open("/tmp/post-file",
+                              O_CREAT | O_TRUNC | O_RDWR, 0600);
+               break;
+       case LWS_UFS_FINAL_CONTENT:
+       case LWS_UFS_CONTENT:
+               if (len) {
+                       pss->file_length += len;
+
+                       /* if the file length is too big, drop it */
+                       if (pss->file_length > 100000)
+                               return 1;
+
+                       n = write((int)pss->post_fd, buf, len);
+                       lwsl_notice("%s: write %d says %d\n", __func__, len, n);
+               }
+               if (state == LWS_UFS_CONTENT)
+                       break;
+               close((int)pss->post_fd);
+               pss->post_fd = LWS_INVALID_FILE;
+               break;
+       }
+
+       return 0;
+}
+
 /* this protocol server (always the first one) handles HTTP,
- * 
+ *
  * Some misc callbacks that aren't associated with a protocol also turn up only
  * here on the first protocol server.
  */
 
-int callback_http(struct libwebsocket_context *context,
-               struct libwebsocket *wsi,
-               enum libwebsocket_callback_reasons reason, void *user,
-                                                          void *in, size_t len)
+int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
+                 void *in, size_t len)
 {
        struct per_session_data__http *pss =
                        (struct per_session_data__http *)user;
-       static unsigned char buffer[4096];
-       struct stat stat_buf;
+       unsigned char buffer[4096 + LWS_PRE];
+       lws_filepos_t amount, file_len, sent;
        char leaf_path[1024];
        const char *mimetype;
        char *other_headers;
-       unsigned char *end;
+       unsigned char *end, *start;
        struct timeval tv;
        unsigned char *p;
+#ifndef LWS_NO_CLIENT
+       struct per_session_data__http *pss1;
+       struct lws *wsi1;
+#endif
        char buf[256];
        char b64[64];
        int n, m;
-
 #ifdef EXTERNAL_POLL
-       struct libwebsocket_pollargs *pa = (struct libwebsocket_pollargs *)in;
+       struct lws_pollargs *pa = (struct lws_pollargs *)in;
 #endif
 
+
        switch (reason) {
        case LWS_CALLBACK_HTTP:
 
-               dump_handshake_info(wsi);
+               lwsl_info("lws_http_serve: %s\n", (const char *)in);
+
+               if (debug_level & LLL_INFO) {
+                       dump_handshake_info(wsi);
+
+                       /* dump the individual URI Arg parameters */
+                       n = 0;
+                       while (lws_hdr_copy_fragment(wsi, buf, sizeof(buf),
+                                                    WSI_TOKEN_HTTP_URI_ARGS, n) > 0) {
+                               lwsl_notice("URI Arg %d: %s\n", ++n, buf);
+                       }
+               }
+
+               {
+                       lws_get_peer_simple(wsi, buf, sizeof(buf));
+                       lwsl_info("HTTP connect from %s\n", buf);
+               }
 
                if (len < 1) {
-                       libwebsockets_return_http_status(context, wsi,
+                       lws_return_http_status(wsi,
                                                HTTP_STATUS_BAD_REQUEST, NULL);
                        goto try_to_reuse;
                }
 
+#if !defined(LWS_NO_CLIENT) && defined(LWS_OPENSSL_SUPPORT)
+               if (!strncmp(in, "/proxytest", 10)) {
+                       struct lws_client_connect_info i;
+                       char *rootpath = "/git/";
+                       const char *p = (const char *)in;
+
+                       if (lws_get_child(wsi))
+                               break;
+
+                       pss->client_finished = 0;
+                       memset(&i, 0, sizeof(i));
+                       i.context = lws_get_context(wsi);
+                       i.address = "libwebsockets.org";
+                       i.port = 443;
+                       i.ssl_connection = 1;
+                       if (p[10])
+                               i.path = (char *)in + 10;
+                       else
+                               i.path = rootpath;
+                       i.host = i.address;
+                       i.origin = NULL;
+                       i.method = "GET";
+                       i.parent_wsi = wsi;
+                       i.uri_replace_from = "libwebsockets.org/git/";
+                       i.uri_replace_to = "/proxytest/";
+
+                       if (!lws_client_connect_via_info(&i)) {
+                               lwsl_err("proxy connect fail\n");
+                               break;
+                       }
+
+                       break;
+               }
+#endif
+
+#if 1
                /* this example server has no concept of directories */
                if (strchr((const char *)in + 1, '/')) {
-                       libwebsockets_return_http_status(context, wsi,
-                                               HTTP_STATUS_FORBIDDEN, NULL);
+                       lws_return_http_status(wsi, HTTP_STATUS_NOT_ACCEPTABLE, NULL);
                        goto try_to_reuse;
                }
+#endif
 
                /* if a legal POST URL, let it continue and accept data */
                if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI))
@@ -158,52 +280,52 @@ int callback_http(struct libwebsocket_context *context,
                /* check for the "send a big file by hand" example case */
 
                if (!strcmp((const char *)in, "/leaf.jpg")) {
+                       lws_fop_flags_t flags = LWS_O_RDONLY;
+
                        if (strlen(resource_path) > sizeof(leaf_path) - 10)
                                return -1;
                        sprintf(leaf_path, "%s/leaf.jpg", resource_path);
 
                        /* well, let's demonstrate how to send the hard way */
 
-                       p = buffer + LWS_SEND_BUFFER_PRE_PADDING;
-                       end = p + sizeof(buffer) - LWS_SEND_BUFFER_PRE_PADDING;
-#ifdef _WIN32
-                       pss->fd = open(leaf_path, O_RDONLY | _O_BINARY);
-#else
-                       pss->fd = open(leaf_path, O_RDONLY);
-#endif
-
-                       if (pss->fd < 0)
-                               return -1;
+                       p = buffer + LWS_PRE;
+                       end = p + sizeof(buffer) - LWS_PRE;
 
-                       if (fstat(pss->fd, &stat_buf) < 0)
+                       pss->fop_fd = lws_vfs_file_open(
+                                       lws_get_fops(lws_get_context(wsi)),
+                                       leaf_path, &flags);
+                       if (!pss->fop_fd) {
+                               lwsl_err("failed to open file %s\n", leaf_path);
                                return -1;
+                       }
+                       file_len = lws_vfs_get_length(pss->fop_fd);
 
                        /*
                         * we will send a big jpeg file, but it could be
                         * anything.  Set the Content-Type: appropriately
                         * so the browser knows what to do with it.
-                        * 
+                        *
                         * Notice we use the APIs to build the header, which
                         * will do the right thing for HTTP 1/1.1 and HTTP2
                         * depending on what connection it happens to be working
                         * on
                         */
-                       if (lws_add_http_header_status(context, wsi, 200, &p, end))
+                       if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end))
                                return 1;
-                       if (lws_add_http_header_by_token(context, wsi,
-                                       WSI_TOKEN_HTTP_SERVER,
+                       if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER,
                                        (unsigned char *)"libwebsockets",
                                        13, &p, end))
                                return 1;
-                       if (lws_add_http_header_by_token(context, wsi,
+                       if (lws_add_http_header_by_token(wsi,
                                        WSI_TOKEN_HTTP_CONTENT_TYPE,
                                        (unsigned char *)"image/jpeg",
                                        10, &p, end))
                                return 1;
-                       if (lws_add_http_header_content_length(context, wsi,
-                                               stat_buf.st_size, &p, end))
+                       if (lws_add_http_header_content_length(wsi,
+                                                              file_len, &p,
+                                                              end))
                                return 1;
-                       if (lws_finalize_http_header(context, wsi, &p, end))
+                       if (lws_finalize_http_header(wsi, &p, end))
                                return 1;
 
                        /*
@@ -211,34 +333,40 @@ int callback_http(struct libwebsocket_context *context,
                         * this won't block since it's the first payload sent
                         * on the connection since it was established
                         * (too small for partial)
-                        * 
+                        *
                         * Notice they are sent using LWS_WRITE_HTTP_HEADERS
                         * which also means you can't send body too in one step,
                         * this is mandated by changes in HTTP2
                         */
 
-                       n = libwebsocket_write(wsi,
-                                       buffer + LWS_SEND_BUFFER_PRE_PADDING,
-                                       p - (buffer + LWS_SEND_BUFFER_PRE_PADDING),
-                                       LWS_WRITE_HTTP_HEADERS);
+                       *p = '\0';
+                       lwsl_info("%s\n", buffer + LWS_PRE);
 
+                       n = lws_write(wsi, buffer + LWS_PRE,
+                                     p - (buffer + LWS_PRE),
+                                     LWS_WRITE_HTTP_HEADERS);
                        if (n < 0) {
-                               close(pss->fd);
+                               lws_vfs_file_close(&pss->fop_fd);
                                return -1;
                        }
                        /*
                         * book us a LWS_CALLBACK_HTTP_WRITEABLE callback
                         */
-                       libwebsocket_callback_on_writable(context, wsi);
+                       lws_callback_on_writable(wsi);
                        break;
                }
 
                /* if not, send a file the easy way */
-               strcpy(buf, resource_path);
+               if (!strncmp(in, "/cgit-data/", 11)) {
+                       in = (char *)in + 11;
+                       strcpy(buf, "/usr/share/cgit");
+               } else
+                       strcpy(buf, resource_path);
+
                if (strcmp(in, "/")) {
                        if (*((const char *)in) != '/')
                                strcat(buf, "/");
-                       strncat(buf, in, sizeof(buf) - strlen(resource_path));
+                       strncat(buf, in, sizeof(buf) - strlen(buf) - 1);
                } else /* default file to serve */
                        strcat(buf, "/test.html");
                buf[sizeof(buf) - 1] = '\0';
@@ -247,15 +375,15 @@ int callback_http(struct libwebsocket_context *context,
                mimetype = get_mimetype(buf);
                if (!mimetype) {
                        lwsl_err("Unknown mimetype for %s\n", buf);
-                       libwebsockets_return_http_status(context, wsi,
+                       lws_return_http_status(wsi,
                                      HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL);
                        return -1;
                }
 
-               /* demostrates how to set a cookie on / */
+               /* demonstrates how to set a cookie on / */
 
-               other_headers = NULL;
-               n = 0;
+               other_headers = leaf_path;
+               p = (unsigned char *)leaf_path;
                if (!strcmp((const char *)in, "/") &&
                           !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) {
                        /* this isn't very unguessable but it'll do for us */
@@ -264,19 +392,24 @@ int callback_http(struct libwebsocket_context *context,
                                (unsigned int)tv.tv_sec,
                                (unsigned int)tv.tv_usec);
 
-                       p = (unsigned char *)leaf_path;
-
-                       if (lws_add_http_header_by_name(context, wsi, 
-                               (unsigned char *)"set-cookie:", 
+                       if (lws_add_http_header_by_name(wsi,
+                               (unsigned char *)"set-cookie:",
                                (unsigned char *)b64, n, &p,
                                (unsigned char *)leaf_path + sizeof(leaf_path)))
                                return 1;
-                       n = (char *)p - leaf_path;
-                       other_headers = leaf_path;
                }
+               if (lws_is_ssl(wsi) && lws_add_http_header_by_name(wsi,
+                                               (unsigned char *)
+                                               "Strict-Transport-Security:",
+                                               (unsigned char *)
+                                               "max-age=15768000 ; "
+                                               "includeSubDomains", 36, &p,
+                                               (unsigned char *)leaf_path +
+                                                       sizeof(leaf_path)))
+                       return 1;
+               n = (char *)p - leaf_path;
 
-               n = libwebsockets_serve_http_file(context, wsi, buf,
-                                               mimetype, other_headers, n);
+               n = lws_serve_http_file(wsi, buf, mimetype, other_headers, n);
                if (n < 0 || ((n > 0) && lws_http_transaction_completed(wsi)))
                        return -1; /* error or can't reuse connection: close the socket */
 
@@ -285,38 +418,145 @@ int callback_http(struct libwebsocket_context *context,
                 * we'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when
                 * it's done
                 */
-
                break;
 
+        case LWS_CALLBACK_CLIENT_RECEIVE:
+                ((char *)in)[len] = '\0';
+                lwsl_info("rx %d '%s'\n", (int)len, (char *)in);
+                break;
+
        case LWS_CALLBACK_HTTP_BODY:
-               strncpy(buf, in, 20);
-               buf[20] = '\0';
-               if (len < 20)
-                       buf[len] = '\0';
+               /* create the POST argument parser if not already existing */
+               if (!pss->spa) {
+                       pss->spa = lws_spa_create(wsi, param_names,
+                                       ARRAY_SIZE(param_names), 1024,
+                                       file_upload_cb, pss);
+                       if (!pss->spa)
+                               return -1;
 
-               lwsl_notice("LWS_CALLBACK_HTTP_BODY: %s... len %d\n",
-                               (const char *)buf, (int)len);
+                       pss->filename[0] = '\0';
+                       pss->file_length = 0;
+               }
 
+               /* let it parse the POST data */
+               if (lws_spa_process(pss->spa, in, len))
+                       return -1;
                break;
 
        case LWS_CALLBACK_HTTP_BODY_COMPLETION:
-               lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION\n");
-               /* the whole of the sent body arrived, close or reuse the connection */
-               libwebsockets_return_http_status(context, wsi,
-                                               HTTP_STATUS_OK, NULL);
+               lwsl_debug("LWS_CALLBACK_HTTP_BODY_COMPLETION\n");
+               /*
+                * the whole of the sent body arrived,
+                * respond to the client with a redirect to show the
+                * results
+                */
+
+               /* call to inform no more payload data coming */
+               lws_spa_finalize(pss->spa);
+
+               p = (unsigned char *)pss->result + LWS_PRE;
+               end = p + sizeof(pss->result) - LWS_PRE - 1;
+               p += sprintf((char *)p,
+                       "<html><body><h1>Form results (after urldecoding)</h1>"
+                       "<table><tr><td>Name</td><td>Length</td><td>Value</td></tr>");
+
+               for (n = 0; n < ARRAY_SIZE(param_names); n++)
+                       p += lws_snprintf((char *)p, end - p,
+                                   "<tr><td><b>%s</b></td><td>%d</td><td>%s</td></tr>",
+                                   param_names[n],
+                                   lws_spa_get_length(pss->spa, n),
+                                   lws_spa_get_string(pss->spa, n));
+
+               p += lws_snprintf((char *)p, end - p, "</table><br><b>filename:</b> %s, <b>length</b> %ld",
+                               pss->filename, pss->file_length);
+
+               p += lws_snprintf((char *)p, end - p, "</body></html>");
+               pss->result_len = p - (unsigned char *)(pss->result + LWS_PRE);
+
+               p = buffer + LWS_PRE;
+               start = p;
+               end = p + sizeof(buffer) - LWS_PRE;
+
+               if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end))
+                       return 1;
+
+               if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
+                               (unsigned char *)"text/html", 9, &p, end))
+                       return 1;
+               if (lws_add_http_header_content_length(wsi, pss->result_len, &p, end))
+                       return 1;
+               if (lws_finalize_http_header(wsi, &p, end))
+                       return 1;
+
+               n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
+               if (n < 0)
+                       return 1;
+
+               n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE,
+                             pss->result_len, LWS_WRITE_HTTP);
+               if (n < 0)
+                       return 1;
                goto try_to_reuse;
+       case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
+               lwsl_debug("LWS_CALLBACK_HTTP_DROP_PROTOCOL\n");
 
+               /* called when our wsi user_space is going to be destroyed */
+               if (pss->spa) {
+                       lws_spa_destroy(pss->spa);
+                       pss->spa = NULL;
+               }
+               break;
        case LWS_CALLBACK_HTTP_FILE_COMPLETION:
                goto try_to_reuse;
 
        case LWS_CALLBACK_HTTP_WRITEABLE:
+               lwsl_info("LWS_CALLBACK_HTTP_WRITEABLE\n");
+
+               if (pss->client_finished)
+                       return -1;
+
+               if (!lws_get_child(wsi) && !pss->fop_fd) {
+                       lwsl_notice("fop_fd NULL\n");
+                       goto try_to_reuse;
+               }
+
+#ifndef LWS_NO_CLIENT
+               if (pss->reason_bf & 2) {
+                       char *px = buf + LWS_PRE;
+                       int lenx = sizeof(buf) - LWS_PRE;
+                       /*
+                        * our sink is writeable and our source has something
+                        * to read.  So read a lump of source material of
+                        * suitable size to send or what's available, whichever
+                        * is the smaller.
+                        */
+
+
+                       pss->reason_bf &= ~2;
+                       wsi1 = lws_get_child(wsi);
+                       if (!wsi1)
+                               break;
+                       if (lws_http_client_read(wsi1, &px, &lenx) < 0)
+                               return -1;
+
+                       if (pss->client_finished)
+                               return -1;
+
+                       break;
+               }
+
+               if (lws_get_child(wsi))
+                       break;
+
+#endif
                /*
                 * we can send more of whatever it is we were sending
                 */
+               sent = 0;
                do {
                        /* we'd like the send this much */
-                       n = sizeof(buffer) - LWS_SEND_BUFFER_PRE_PADDING;
-                       
+                       n = sizeof(buffer) - LWS_PRE;
+
                        /* but if the peer told us he wants less, we can adapt */
                        m = lws_get_peer_write_allowance(wsi);
 
@@ -328,76 +568,130 @@ int callback_http(struct libwebsocket_context *context,
                        if (m != -1 && m < n)
                                /* he couldn't handle that much */
                                n = m;
-                       
-                       n = read(pss->fd, buffer + LWS_SEND_BUFFER_PRE_PADDING,
-                                                                       n);
+
+                       n = lws_vfs_file_read(pss->fop_fd,
+                                              &amount, buffer + LWS_PRE, n);
                        /* problem reading, close conn */
-                       if (n < 0)
+                       if (n < 0) {
+                               lwsl_err("problem reading file\n");
                                goto bail;
+                       }
+                       n = (int)amount;
                        /* sent it all, close conn */
                        if (n == 0)
-                               goto flush_bail;
+                               goto penultimate;
                        /*
                         * To support HTTP2, must take care about preamble space
-                        * 
+                        *
                         * identification of when we send the last payload frame
                         * is handled by the library itself if you sent a
                         * content-length header
                         */
-                       m = libwebsocket_write(wsi,
-                                              buffer + LWS_SEND_BUFFER_PRE_PADDING,
-                                              n, LWS_WRITE_HTTP);
-                       if (m < 0)
+                       m = lws_write(wsi, buffer + LWS_PRE, n, LWS_WRITE_HTTP);
+                       if (m < 0) {
+                               lwsl_err("write failed\n");
                                /* write failed, close conn */
                                goto bail;
-
-                       /*
-                        * http2 won't do this
-                        */
-                       if (m != n)
-                               /* partial write, adjust */
-                               if (lseek(pss->fd, m - n, SEEK_CUR) < 0)
-                                       goto bail;
-
+                       }
                        if (m) /* while still active, extend timeout */
-                               libwebsocket_set_timeout(wsi,
-                                       PENDING_TIMEOUT_HTTP_CONTENT, 5);
-                       
-                       /* if we have indigestion, let him clear it before eating more */
-                       if (lws_partial_buffered(wsi))
-                               break;
-
-               } while (!lws_send_pipe_choked(wsi));
+                               lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, 5);
+                       sent += m;
 
+               } while (!lws_send_pipe_choked(wsi) && (sent < 1024 * 1024));
 later:
-               libwebsocket_callback_on_writable(context, wsi);
+               lws_callback_on_writable(wsi);
                break;
-flush_bail:
-               /* true if still partial pending */
-               if (lws_partial_buffered(wsi)) {
-                       libwebsocket_callback_on_writable(context, wsi);
-                       break;
-               }
-               close(pss->fd);
+penultimate:
+               lws_vfs_file_close(&pss->fop_fd);
+               pss->fop_fd = NULL;
                goto try_to_reuse;
 
 bail:
-               close(pss->fd);
+               lws_vfs_file_close(&pss->fop_fd);
+
                return -1;
 
        /*
         * callback for confirming to continue with client IP appear in
         * protocol 0 callback since no websocket protocol has been agreed
         * yet.  You can just ignore this if you won't filter on client IP
-        * since the default uhandled callback return is 0 meaning let the
+        * since the default unhandled callback return is 0 meaning let the
         * connection continue.
         */
        case LWS_CALLBACK_FILTER_NETWORK_CONNECTION:
-
                /* if we returned non-zero from here, we kill the connection */
                break;
 
-#ifdef EXTERNAL_POLL
+#ifndef LWS_NO_CLIENT
+       case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: {
+               char ctype[64], ctlen = 0;
+               lwsl_err("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP\n");
+               p = buffer + LWS_PRE;
+               end = p + sizeof(buffer) - LWS_PRE;
+               if (lws_add_http_header_status(lws_get_parent(wsi), HTTP_STATUS_OK, &p, end))
+                       return 1;
+               if (lws_add_http_header_by_token(lws_get_parent(wsi),
+                               WSI_TOKEN_HTTP_SERVER,
+                               (unsigned char *)"libwebsockets",
+                               13, &p, end))
+                       return 1;
+
+               ctlen = lws_hdr_copy(wsi, ctype, sizeof(ctype), WSI_TOKEN_HTTP_CONTENT_TYPE);
+               if (ctlen > 0) {
+                       if (lws_add_http_header_by_token(lws_get_parent(wsi),
+                               WSI_TOKEN_HTTP_CONTENT_TYPE,
+                               (unsigned char *)ctype, ctlen, &p, end))
+                               return 1;
+               }
+#if 0
+               if (lws_add_http_header_content_length(lws_get_parent(wsi),
+                                                      file_len, &p, end))
+                       return 1;
+#endif
+               if (lws_finalize_http_header(lws_get_parent(wsi), &p, end))
+                       return 1;
+
+               *p = '\0';
+               lwsl_info("%s\n", buffer + LWS_PRE);
+
+               n = lws_write(lws_get_parent(wsi), buffer + LWS_PRE,
+                             p - (buffer + LWS_PRE),
+                             LWS_WRITE_HTTP_HEADERS);
+               if (n < 0)
+                       return -1;
+
+               break; }
+       case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
+               //lwsl_err("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n");
+               return -1;
+               break;
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
+               //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p\n", wsi);
+               assert(lws_get_parent(wsi));
+               if (!lws_get_parent(wsi))
+                       break;
+               pss1 = lws_wsi_user(lws_get_parent(wsi));
+               pss1->reason_bf |= 2;
+               lws_callback_on_writable(lws_get_parent(wsi));
+               break;
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
+               //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ len %d\n", (int)len);
+               assert(lws_get_parent(wsi));
+               m = lws_write(lws_get_parent(wsi), (unsigned char *)in,
+                               len, LWS_WRITE_HTTP);
+               if (m < 0)
+                       return -1;
+               break;
+       case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
+               //lwsl_err("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
+               assert(lws_get_parent(wsi));
+               if (!lws_get_parent(wsi))
+                       break;
+               pss1 = lws_wsi_user(lws_get_parent(wsi));
+               pss1->client_finished = 1;
+               break;
+#endif
+
        /*
         * callbacks for managing the external poll() array appear in
         * protocol 0 callback
@@ -407,16 +701,21 @@ bail:
                /*
                 * lock mutex to protect pollfd state
                 * called before any other POLL related callback
+                * if protecting wsi lifecycle change, len == 1
                 */
+               test_server_lock(len);
                break;
 
        case LWS_CALLBACK_UNLOCK_POLL:
                /*
                 * unlock mutex to protect pollfd state when
                 * called after any other POLL related callback
+                * if protecting wsi lifecycle change, len == 1
                 */
+               test_server_unlock(len);
                break;
 
+#ifdef EXTERNAL_POLL
        case LWS_CALLBACK_ADD_POLL_FD:
 
                if (count_pollfds >= max_poll_elements) {
@@ -446,7 +745,7 @@ bail:
 
        case LWS_CALLBACK_GET_THREAD_ID:
                /*
-                * if you will call "libwebsocket_callback_on_writable"
+                * if you will call "lws_callback_on_writable"
                 * from a different thread, return the caller thread ID
                 * here so lws can use this information to work out if it
                 * should signal the poll() loop to exit and restart early
@@ -456,6 +755,39 @@ bail:
 
                break;
 
+#if defined(LWS_OPENSSL_SUPPORT)
+       case LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION:
+               /* Verify the client certificate */
+               if (!len || (SSL_get_verify_result((SSL*)in) != X509_V_OK)) {
+                       int err = X509_STORE_CTX_get_error((X509_STORE_CTX*)user);
+                       int depth = X509_STORE_CTX_get_error_depth((X509_STORE_CTX*)user);
+                       const char* msg = X509_verify_cert_error_string(err);
+                       lwsl_err("LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION: SSL error: %s (%d), depth: %d\n", msg, err, depth);
+                       return 1;
+               }
+               break;
+#if defined(LWS_HAVE_SSL_CTX_set1_param)
+       case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS:
+               if (crl_path[0]) {
+                       /* Enable CRL checking */
+                       X509_VERIFY_PARAM *param = X509_VERIFY_PARAM_new();
+                       X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK);
+                       SSL_CTX_set1_param((SSL_CTX*)user, param);
+                       X509_STORE *store = SSL_CTX_get_cert_store((SSL_CTX*)user);
+                       X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
+                       n = X509_load_cert_crl_file(lookup, crl_path, X509_FILETYPE_PEM);
+                       X509_VERIFY_PARAM_free(param);
+                       if (n != 1) {
+                               char errbuf[256];
+                               n = ERR_get_error();
+                               lwsl_err("LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS: SSL error: %s (%d)\n", ERR_error_string(n, errbuf), n);
+                               return 1;
+                       }
+               }
+               break;
+#endif
+#endif
+
        default:
                break;
        }