diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6429374bff33de6d28df40392e5e7cc2cada997c..b2d3c03fb7521010c4cd513dac74fe2dd3b7f62a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -655,7 +655,7 @@ endif()
 
 if (LWS_WITH_ZIP_FOPS)
 	list(APPEND SOURCES
-		lib/junzip.c)
+		lib/fops-zip.c)
 endif()
 
 # Add helper files for Windows.
@@ -1174,6 +1174,7 @@ if (NOT LWS_WITHOUT_TESTAPPS)
 		set(TEST_SERVER_DATA
 			"${PROJECT_SOURCE_DIR}/test-server/favicon.ico"
 			"${PROJECT_SOURCE_DIR}/test-server/leaf.jpg"
+			"${PROJECT_SOURCE_DIR}/test-server/candide.zip"
 			"${PROJECT_SOURCE_DIR}/test-server/libwebsockets.org-logo.png"
 			"${PROJECT_SOURCE_DIR}/test-server/lws-common.js"
 			"${PROJECT_SOURCE_DIR}/test-server/test.html")
diff --git a/README.coding.md b/README.coding.md
index 342aa3def0b95fe635db9e1b81a03271c3305518..15d4c4a99c3a946f903643dae7649c9562b6eb43 100644
--- a/README.coding.md
+++ b/README.coding.md
@@ -120,6 +120,34 @@ the send pipe on the connection is choked but no ack will ever come, so the
 dead connection will never become writeable.  To cover that, you can use TCP
 keepalives (see later in this document) or pings.
 
+@section gzip Serving from inside a zip file
+
+Lws now supports serving gzipped files from inside a zip container.  Thanks to
+Per Bothner for contributing the code.
+
+This has the advtantage that if the client can accept GZIP encoding, lws can
+simply send the gzip-compressed file from inside the zip file with no further
+processing, saving time and bandwidth.
+
+In the case the client can't understand gzip compression, lws automatically
+decompressed the file and sends it normally.
+
+Clients with limited storage and RAM will find this useful; the memory needed
+for the inflate case is constrained so that only one input buffer at a time
+is ever in memory.
+
+To use this feature, ensure LWS_WITH_ZIP_FOPS is enabled at CMake (it is by
+default).
+
+`libwebsockets-test-server-v2.0` includes a mount using this technology
+already, run that test server and navigate to http://localhost:7681/ziptest/candide.html
+
+This will serve the book Candide in html, together with two jpgs, all from
+inside a .zip file in /usr/[local/]share-libwebsockets-test-server/candide.zip
+
+Usage is otherwise automatic, if you arrange a mount that points to the zipfile,
+eg, "/ziptest" -> "mypath/test.zip", then URLs like `/ziptest/index.html` will be
+servied from `index.html` inside `mypath/test.zip`
 
 @section frags Fragmented messages
 
@@ -347,30 +375,51 @@ and then can use helpers to also leverage these platform-independent
 file handling apis
 
 ```
-	static inline lws_fop_fd_t
+	lws_fop_fd_t
 	`lws_plat_file_open`(struct lws_plat_file_ops *fops, const char *filename,
-			   lws_filepos_t *filelen, lws_fop_flags_t *flags)
-	static inline int
+			   lws_fop_flags_t *flags)
+	int
 	`lws_plat_file_close`(lws_fop_fd_t fop_fd)
 
-	static inline unsigned long
+	unsigned long
 	`lws_plat_file_seek_cur`(lws_fop_fd_t fop_fd, lws_fileofs_t offset)
 
-	static inline int
+	int
 	`lws_plat_file_read`(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
 		   uint8_t *buf, lws_filepos_t len)
 
-	static inline int
+	int
 	`lws_plat_file_write`(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
 		   uint8_t *buf, lws_filepos_t len )
 ```
 
+Generic helpers are provided which provide access to generic fops information or
+call through to the above fops
+
+```
+lws_filepos_t
+lws_vfs_tell(lws_fop_fd_t fop_fd);
+
+lws_filepos_t
+lws_vfs_get_length(lws_fop_fd_t fop_fd);
+
+uint32_t
+lws_vfs_get_mod_time(lws_fop_fd_t fop_fd);
+
+lws_fileofs_t
+lws_vfs_file_seek_set(lws_fop_fd_t fop_fd, lws_fileofs_t offset);
+
+lws_fileofs_t
+lws_vfs_file_seek_end(lws_fop_fd_t fop_fd, lws_fileofs_t offset);
+```
+
+
 The user code can also override or subclass the file operations, to either
 wrap or replace them.  An example is shown in test server.
 
 ### Changes from v2.1 and before fops
 
-There are three changes:
+There are several changes:
 
 1) Pre-2.2 fops directly used platform file descriptors.  Current fops returns and accepts a wrapper type lws_fop_fd_t which is a pointer to a malloc'd struct containing information specific to the filesystem implementation.
 
@@ -378,6 +427,19 @@ There are three changes:
 
 3) Everything is wrapped in typedefs.  See lws-plat-unix.c for examples of how to implement.
 
+4) Position in the file, File Length, and a copy of Flags left after open are now generically held in the fop_fd.
+VFS implementation must set and manage this generic information now.  See the implementations in lws-plat-unix.c for
+examples.
+
+5) The file length is no longer set at a pointer provided by the open() fop.  The api `lws_vfs_get_length()` is provided to
+get the file length after open.
+
+6) If your file namespace is virtual, ie, is not reachable by platform fops directly, you must set LWS_FOP_FLAG_VIRTUAL
+on the flags during open.
+
+7) There is an optional `mod_time` uint32_t member in the generic fop_fd.  If you are able to set it during open, you
+should indicate it by setting `LWS_FOP_FLAG_MOD_TIME_VALID` on the flags.
+
 @section ecdh ECDH Support
 
 ECDH Certs are now supported.  Enable the CMake option
diff --git a/lib/context.c b/lib/context.c
index 668d16b92271e68395d886009b3176d7b7381ded..024cc815f2fb7e8a106c6077fe84a287ab4cb41e 100644
--- a/lib/context.c
+++ b/lib/context.c
@@ -150,7 +150,8 @@ lws_protocol_init(struct lws_context *context)
 
 		for (n = 0; n < vh->count_protocols; n++) {
 			wsi.protocol = &vh->protocols[n];
-
+			if (!vh->protocols[n].name)
+				continue;
 			pvo = lws_vhost_protocol_options(vh,
 							 vh->protocols[n].name);
 			if (pvo) {
@@ -403,7 +404,11 @@ lws_create_vhost(struct lws_context *context,
 	}
 #endif
 
-	if (info->options & LWS_SERVER_OPTION_EXPLICIT_VHOSTS)
+	if (
+#ifdef LWS_WITH_PLUGINS
+	    (context->plugin_list) ||
+#endif
+	    info->options & LWS_SERVER_OPTION_EXPLICIT_VHOSTS)
 		vh->protocols = lwsp;
 	else {
 		vh->protocols = info->protocols;
@@ -571,6 +576,7 @@ LWS_VISIBLE struct lws_context *
 lws_create_context(struct lws_context_creation_info *info)
 {
 	struct lws_context *context = NULL;
+	struct lws_plat_file_ops *prev;
 #ifndef LWS_NO_DAEMONIZE
 	int pid_daemon = get_daemonize_pid();
 #endif
@@ -621,20 +627,34 @@ lws_create_context(struct lws_context_creation_info *info)
 
 	/* default to just the platform fops implementation */
 
-	context->fops_default[0].open		= _lws_plat_file_open;
-	context->fops_default[0].close		= _lws_plat_file_close;
-	context->fops_default[0].seek_cur	= _lws_plat_file_seek_cur;
-	context->fops_default[0].read		= _lws_plat_file_read;
-	context->fops_default[0].write		= _lws_plat_file_write;
-	context->fops_default[0].path_prefix	= "/";
+	context->fops_platform.open		= _lws_plat_file_open;
+	context->fops_platform.close		= _lws_plat_file_close;
+	context->fops_platform.seek_cur		= _lws_plat_file_seek_cur;
+	context->fops_platform.read		= _lws_plat_file_read;
+	context->fops_platform.write		= _lws_plat_file_write;
+	context->fops_platform.fi[0].sig	= NULL;
+
+	/*
+	 *  arrange a linear linked-list of fops starting from context->fops
+	 *
+	 * platform fops
+	 * [ -> fops_zip (copied into context so .next settable) ]
+	 * [ -> info->fops ]
+	 */
 
-	// context->fops_default[1].open is already NULL from zalloc
+	context->fops = &context->fops_platform;
+	prev = (struct lws_plat_file_ops *)context->fops;
 
-	/* it can be overridden from context creation info */
+#if defined(LWS_WITH_ZIP_FOPS)
+	/* make a soft copy so we can set .next */
+	context->fops_zip = fops_zip;
+	prev->next = &context->fops_zip;
+	prev = (struct lws_plat_file_ops *)prev->next;
+#endif
+
+	/* if user provided fops, tack them on the end of the list */
 	if (info->fops)
-		context->fops = info->fops;
-	else
-		context->fops = &context->fops_default[0];
+		prev->next = info->fops;
 
 	context->reject_service_keywords = info->reject_service_keywords;
 	if (info->external_baggage_free_on_destroy)
diff --git a/lib/fops-zip.c b/lib/fops-zip.c
new file mode 100644
index 0000000000000000000000000000000000000000..02f499960e80362156eb8f165b9643949b88b980
--- /dev/null
+++ b/lib/fops-zip.c
@@ -0,0 +1,667 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Original code used in this source file:
+ *
+ * https://github.com/PerBothner/DomTerm.git @912add15f3d0aec
+ *
+ * ./lws-term/io.c
+ * ./lws-term/junzip.c
+ *
+ * Copyright (C) 2017  Per Bothner <per@bothner.com>
+ *
+ * MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * ( copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ *
+ * lws rewrite:
+ *
+ * Copyright (C) 2017  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 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.
+ *
+ *  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
+ */
+
+#include "private-libwebsockets.h"
+
+#include <zlib.h>
+
+/*
+ * This code works with zip format containers which may have files compressed
+ * with gzip deflate (type 8) or store uncompressed (type 0).
+ *
+ * Linux zip produces such zipfiles by default, eg
+ *
+ *  $ zip ../myzip.zip file1 file2 file3
+ */
+
+#define ZIP_COMPRESSION_METHOD_STORE 0
+#define ZIP_COMPRESSION_METHOD_DEFLATE 8
+
+typedef struct {
+	lws_filepos_t		filename_start;
+	uint32_t		crc32;
+	uint32_t		comp_size;
+	uint32_t		uncomp_size;
+	uint32_t		offset;
+	uint32_t		mod_time;
+	uint16_t		filename_len;
+	uint16_t		extra;
+	uint16_t		method;
+	uint16_t		file_com_len;
+} lws_fops_zip_hdr_t;
+
+typedef struct {
+	struct lws_fop_fd	fop_fd; /* MUST BE FIRST logical fop_fd into
+	 	 	 	 	 * file inside zip: fops_zip fops */
+	lws_fop_fd_t		zip_fop_fd; /* logical fop fd on to zip file
+	 	 	 	 	     * itself: using platform fops */
+	lws_fops_zip_hdr_t	hdr;
+	z_stream		inflate;
+	lws_filepos_t		content_start;
+	lws_filepos_t		exp_uncomp_pos;
+	union {
+		uint8_t		trailer8[8];
+		uint32_t	trailer32[2];
+	} u;
+	uint8_t			rbuf[128]; /* decompression chunk size */
+	int			entry_count;
+
+	unsigned int		decompress:1; /* 0 = direct from file */
+	unsigned int		add_gzip_container:1;
+} *lws_fops_zip_t;
+
+struct lws_plat_file_ops fops_zip;
+#define fop_fd_to_priv(FD) ((lws_fops_zip_t)(FD))
+
+static const uint8_t hd[] = { 31, 139, 8, 0, 0, 0, 0, 0, 0, 3 };
+
+enum {
+	ZC_SIGNATURE				= 0,
+	ZC_VERSION_MADE_BY 			= 4,
+	ZC_VERSION_NEEDED_TO_EXTRACT 		= 6,
+	ZC_GENERAL_PURPOSE_BIT_FLAG 		= 8,
+	ZC_COMPRESSION_METHOD 			= 10,
+	ZC_LAST_MOD_FILE_TIME 			= 12,
+	ZC_LAST_MOD_FILE_DATE 			= 14,
+	ZC_CRC32 				= 16,
+	ZC_COMPRESSED_SIZE 			= 20,
+	ZC_UNCOMPRESSED_SIZE 			= 24,
+	ZC_FILE_NAME_LENGTH 			= 28,
+	ZC_EXTRA_FIELD_LENGTH 			= 30,
+
+	ZC_FILE_COMMENT_LENGTH 			= 32,
+	ZC_DISK_NUMBER_START 			= 34,
+	ZC_INTERNAL_FILE_ATTRIBUTES 		= 36,
+	ZC_EXTERNAL_FILE_ATTRIBUTES 		= 38,
+	ZC_REL_OFFSET_LOCAL_HEADER 		= 42,
+	ZC_DIRECTORY_LENGTH 			= 46,
+
+	ZE_SIGNATURE_OFFSET 			= 0,
+	ZE_DESK_NUMBER 				= 4,
+	ZE_CENTRAL_DIRECTORY_DISK_NUMBER 	= 6,
+	ZE_NUM_ENTRIES_THIS_DISK 		= 8,
+	ZE_NUM_ENTRIES 				= 10,
+	ZE_CENTRAL_DIRECTORY_SIZE 		= 12,
+	ZE_CENTRAL_DIR_OFFSET 			= 16,
+	ZE_ZIP_COMMENT_LENGTH 			= 20,
+	ZE_DIRECTORY_LENGTH 			= 22,
+
+	ZL_REL_OFFSET_CONTENT			= 28,
+	ZL_HEADER_LENGTH			= 30,
+
+	LWS_FZ_ERR_SEEK_END_RECORD		= 1,
+	LWS_FZ_ERR_READ_END_RECORD,
+	LWS_FZ_ERR_END_RECORD_MAGIC,
+	LWS_FZ_ERR_END_RECORD_SANITY,
+	LWS_FZ_ERR_CENTRAL_SEEK,
+	LWS_FZ_ERR_CENTRAL_READ,
+	LWS_FZ_ERR_CENTRAL_SANITY,
+	LWS_FZ_ERR_NAME_TOO_LONG,
+	LWS_FZ_ERR_NAME_READ,
+	LWS_FZ_ERR_CONTENT_SANITY,
+	LWS_FZ_ERR_CONTENT_SEEK,
+	LWS_FZ_ERR_SCAN_SEEK,
+	LWS_FZ_ERR_NOT_FOUND,
+	LWS_FZ_ERR_ZLIB_INIT,
+	LWS_FZ_ERR_READ_CONTENT,
+	LWS_FZ_ERR_SEEK_COMPRESSED,
+};
+
+static uint16_t
+get_u16(void *p)
+{
+	const uint8_t *c = (const uint8_t *)p;
+
+	return (uint16_t)((c[0] | (c[1] << 8)));
+}
+
+static uint32_t
+get_u32(void *p)
+{
+	const uint8_t *c = (const uint8_t *)p;
+
+	return (uint32_t)((c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24)));
+}
+
+int
+lws_fops_zip_scan(lws_fops_zip_t priv, const char *name, int len)
+{
+	lws_filepos_t amount;
+	uint8_t buf[64];
+	int i;
+
+	if (lws_vfs_file_seek_end(priv->zip_fop_fd, -ZE_DIRECTORY_LENGTH) < 0)
+		return LWS_FZ_ERR_SEEK_END_RECORD;
+
+	if (lws_vfs_file_read(priv->zip_fop_fd, &amount, buf,
+			      ZE_DIRECTORY_LENGTH))
+		return LWS_FZ_ERR_READ_END_RECORD;
+
+	if (amount != ZE_DIRECTORY_LENGTH)
+		return LWS_FZ_ERR_READ_END_RECORD;
+
+	/*
+	 * We require the zip to have the last record right at the end
+	 * Linux zip always does this if no zip comment.
+	 */
+	if (buf[0] != 'P' || buf[1] != 'K' || buf[2] != 5 || buf[3] != 6)
+		return LWS_FZ_ERR_END_RECORD_MAGIC;
+
+	i = get_u16(buf + ZE_NUM_ENTRIES);
+
+	if (get_u16(buf + ZE_DESK_NUMBER) ||
+	    get_u16(buf + ZE_CENTRAL_DIRECTORY_DISK_NUMBER) ||
+	    i != get_u16(buf + ZE_NUM_ENTRIES_THIS_DISK))
+		return LWS_FZ_ERR_END_RECORD_SANITY;
+
+	/* end record is OK... look for our file in the central dir */
+
+	if (lws_vfs_file_seek_set(priv->zip_fop_fd,
+				  get_u32(buf + ZE_CENTRAL_DIR_OFFSET)) < 0)
+		return LWS_FZ_ERR_CENTRAL_SEEK;
+
+	while (i--) {
+		priv->content_start = lws_vfs_tell(priv->zip_fop_fd);
+
+		if (lws_vfs_file_read(priv->zip_fop_fd, &amount, buf,
+				      ZC_DIRECTORY_LENGTH))
+			return LWS_FZ_ERR_CENTRAL_READ;
+
+		if (amount != ZC_DIRECTORY_LENGTH)
+			return LWS_FZ_ERR_CENTRAL_READ;
+
+		if (get_u32(buf + ZC_SIGNATURE) != 0x02014B50)
+			return LWS_FZ_ERR_CENTRAL_SANITY;
+
+		lwsl_debug("cstart 0x%lx\n", priv->content_start);
+
+		priv->hdr.filename_len = get_u16(buf + ZC_FILE_NAME_LENGTH);
+		priv->hdr.extra = get_u16(buf + ZC_EXTRA_FIELD_LENGTH);
+		priv->hdr.filename_start = lws_vfs_tell(priv->zip_fop_fd);
+
+		priv->hdr.method = get_u16(buf + ZC_COMPRESSION_METHOD);
+		priv->hdr.crc32 = get_u32(buf + ZC_CRC32);
+		priv->hdr.comp_size = get_u32(buf + ZC_COMPRESSED_SIZE);
+		priv->hdr.uncomp_size = get_u32(buf + ZC_UNCOMPRESSED_SIZE);
+		priv->hdr.offset = get_u32(buf + ZC_REL_OFFSET_LOCAL_HEADER);
+		priv->hdr.mod_time = get_u32(buf + ZC_LAST_MOD_FILE_TIME);
+		priv->hdr.file_com_len = get_u16(buf + ZC_FILE_COMMENT_LENGTH);
+
+		if (priv->hdr.filename_len != len)
+			goto next;
+
+		if (len >= sizeof(buf) - 1)
+			return LWS_FZ_ERR_NAME_TOO_LONG;
+
+		if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd,
+							&amount, buf, len))
+			return LWS_FZ_ERR_NAME_READ;
+		if (amount != len)
+			return LWS_FZ_ERR_NAME_READ;
+
+		buf[len] = '\0';
+		lwsl_debug("check %s vs %s\n", buf, name);
+
+		if (strcmp((const char *)buf, name))
+			goto next;
+
+		/* we found a match */
+		lws_vfs_file_seek_set(priv->zip_fop_fd, priv->hdr.offset);
+		if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd,
+							&amount, buf,
+							ZL_HEADER_LENGTH))
+			return LWS_FZ_ERR_NAME_READ;
+		if (amount != ZL_HEADER_LENGTH)
+			return LWS_FZ_ERR_NAME_READ;
+
+		priv->content_start = priv->hdr.offset +
+				      ZL_HEADER_LENGTH +
+				      priv->hdr.filename_len +
+				      get_u16(buf + ZL_REL_OFFSET_CONTENT);
+
+		lwsl_debug("content supposed to start at 0x%lx\n",
+			   priv->content_start);
+
+		if (priv->content_start > priv->zip_fop_fd->len)
+			return LWS_FZ_ERR_CONTENT_SANITY;
+
+		if (lws_vfs_file_seek_set(priv->zip_fop_fd,
+					  priv->content_start) < 0)
+			return LWS_FZ_ERR_CONTENT_SEEK;
+
+		/* we are aligned at the start of the content */
+
+		priv->exp_uncomp_pos = 0;
+
+		return 0;
+
+next:
+		if (i && lws_vfs_file_seek_set(priv->zip_fop_fd,
+					       priv->content_start +
+					       ZC_DIRECTORY_LENGTH +
+					       priv->hdr.filename_len +
+					       priv->hdr.extra +
+					       priv->hdr.file_com_len) < 0)
+			return LWS_FZ_ERR_SCAN_SEEK;
+	}
+
+	return LWS_FZ_ERR_NOT_FOUND;
+}
+
+static int
+lws_fops_zip_reset_inflate(lws_fops_zip_t priv)
+{
+	if (priv->decompress)
+		inflateEnd(&priv->inflate);
+
+	priv->inflate.zalloc = Z_NULL;
+	priv->inflate.zfree = Z_NULL;
+	priv->inflate.opaque = Z_NULL;
+	priv->inflate.avail_in = 0;
+	priv->inflate.next_in = Z_NULL;
+
+	if (inflateInit2(&priv->inflate, -MAX_WBITS) != Z_OK) {
+		lwsl_err("inflate init failed\n");
+		return LWS_FZ_ERR_ZLIB_INIT;
+	}
+
+	if (lws_vfs_file_seek_set(priv->zip_fop_fd, priv->content_start) < 0)
+		return LWS_FZ_ERR_CONTENT_SEEK;
+
+	priv->exp_uncomp_pos = 0;
+
+	return 0;
+}
+
+static lws_fop_fd_t
+lws_fops_zip_open(const struct lws_plat_file_ops *fops, const char *vfs_path,
+		  const char *vpath, lws_fop_flags_t *flags)
+{
+	lws_fop_flags_t local_flags = 0;
+	lws_fops_zip_t priv;
+	char rp[192];
+	int m;
+
+	/*
+	 * vpath points at the / after the fops signature in vfs_path, eg
+	 * with a vfs_path "/var/www/docs/manual.zip/index.html", vpath
+	 * will come pointing at "/index.html"
+	 */
+
+	priv = lws_zalloc(sizeof(*priv));
+	if (!priv)
+		return NULL;
+
+	priv->fop_fd.fops = &fops_zip;
+
+	m = sizeof(rp) - 1;
+	if ((vpath - vfs_path - 1) < m)
+		m = vpath - vfs_path - 1;
+	strncpy(rp, vfs_path, m);
+	rp[m] = '\0';
+
+	/* open the zip file itself using the incoming fops, not fops_zip */
+
+	priv->zip_fop_fd = fops->LWS_FOP_OPEN(fops, rp, NULL, &local_flags);
+	if (!priv->zip_fop_fd) {
+		lwsl_err("unable to open zip %s\n", rp);
+		goto bail1;
+	}
+
+	if (*vpath == '/')
+		vpath++;
+
+	m = lws_fops_zip_scan(priv, vpath, strlen(vpath));
+	if (m) {
+		lwsl_err("unable to find record matching '%s' %d\n", vpath, m);
+		goto bail2;
+	}
+
+	/* the directory metadata tells us modification time, so pass it on */
+	priv->fop_fd.mod_time = priv->hdr.mod_time;
+	*flags |= LWS_FOP_FLAG_MOD_TIME_VALID | LWS_FOP_FLAG_VIRTUAL;
+	priv->fop_fd.flags = *flags;
+
+	/* The zip fop_fd is left pointing at the start of the content.
+	 *
+	 * 1) Content could be uncompressed (STORE), and we can always serve
+	 *    that directly
+	 *
+	 * 2) Content could be compressed (GZIP), and the client can handle
+	 *    receiving GZIP... we can wrap it in a GZIP header and trailer
+	 *    and serve the content part directly.  The flag indicating we
+	 *    are providing GZIP directly is set so lws will send the right
+	 *    headers.
+	 *
+	 * 3) Content could be compressed (GZIP) but the client can't handle
+	 *    receiving GZIP... we can decompress it and serve as it is
+	 *    inflated piecemeal.
+	 *
+	 * 4) Content may be compressed some unknown way... fail
+	 *
+	 */
+	if (priv->hdr.method == ZIP_COMPRESSION_METHOD_STORE) {
+		/*
+		 * it is stored uncompressed, leave it indicated as
+		 * uncompressed, and just serve it from inside the
+		 * zip with no gzip container;
+		 */
+
+		lwsl_info("direct zip serving (stored)\n");
+
+		priv->fop_fd.len = priv->hdr.uncomp_size;
+
+		return &priv->fop_fd;
+	}
+
+	if ((*flags & LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP) &&
+	    priv->hdr.method == ZIP_COMPRESSION_METHOD_DEFLATE) {
+
+		/*
+		 * We can serve the gzipped file contents directly as gzip
+		 * from inside the zip container; client says it is OK.
+		 *
+		 * To convert to standalone gzip, we have to add a 10-byte
+		 * constant header and a variable 8-byte trailer around the
+		 * content.
+		 *
+		 * The 8-byte trailer is prepared now and held in the priv.
+		 */
+
+		lwsl_info("direct zip serving (gzipped)\n");
+
+		priv->fop_fd.len = sizeof(hd) + priv->hdr.comp_size +
+				   sizeof(priv->u);
+
+		if (lws_is_be()) {
+			uint8_t *p = priv->u.trailer8;
+
+			*p++ = (uint8_t)priv->hdr.crc32;
+			*p++ = (uint8_t)(priv->hdr.crc32 >> 8);
+			*p++ = (uint8_t)(priv->hdr.crc32 >> 16);
+			*p++ = (uint8_t)(priv->hdr.crc32 >> 24);
+			*p++ = (uint8_t)priv->hdr.uncomp_size;
+			*p++ = (uint8_t)(priv->hdr.uncomp_size >> 8);
+			*p++ = (uint8_t)(priv->hdr.uncomp_size >> 16);
+			*p   = (uint8_t)(priv->hdr.uncomp_size >> 24);
+		} else {
+			priv->u.trailer32[0] = priv->hdr.crc32;
+			priv->u.trailer32[1] = priv->hdr.uncomp_size;
+		}
+
+		*flags |= LWS_FOP_FLAG_COMPR_IS_GZIP;
+		priv->fop_fd.flags = *flags;
+		priv->add_gzip_container = 1;
+
+		return &priv->fop_fd;
+	}
+
+	if (priv->hdr.method == ZIP_COMPRESSION_METHOD_DEFLATE) {
+
+		/* we must decompress it to serve it */
+
+		lwsl_info("decompressed zip serving\n");
+
+		priv->fop_fd.len = priv->hdr.uncomp_size;
+
+		if (lws_fops_zip_reset_inflate(priv)) {
+			lwsl_err("inflate init failed\n");
+			goto bail2;
+		}
+
+		priv->decompress = 1;
+
+		return &priv->fop_fd;
+	}
+
+	/* we can't handle it ... */
+
+	lwsl_err("zipped file %s compressed in unknown way (%d)\n", vfs_path,
+		 priv->hdr.method);
+
+bail2:
+	lws_vfs_file_close(&priv->zip_fop_fd);
+bail1:
+	free(priv);
+
+	return NULL;
+}
+
+/* ie, we are closing the fop_fd for the file inside the gzip */
+
+static int
+lws_fops_zip_close(lws_fop_fd_t *fd)
+{
+	lws_fops_zip_t priv = fop_fd_to_priv(*fd);
+
+	if (priv->decompress)
+		inflateEnd(&priv->inflate);
+
+	lws_vfs_file_close(&priv->zip_fop_fd); /* close the gzip fop_fd */
+
+	free(priv);
+	*fd = NULL;
+
+	return 0;
+}
+
+static lws_fileofs_t
+lws_fops_zip_seek_cur(lws_fop_fd_t fd, lws_fileofs_t offset_from_cur_pos)
+{
+	fd->pos += offset_from_cur_pos;
+
+	return fd->pos;
+}
+
+static int
+lws_fops_zip_read(lws_fop_fd_t fd, lws_filepos_t *amount, uint8_t *buf,
+		  lws_filepos_t len)
+{
+	lws_fops_zip_t priv = fop_fd_to_priv(fd);
+	lws_filepos_t ramount, rlen, cur = lws_vfs_tell(fd);
+	int ret;
+
+	if (priv->decompress) {
+
+		if (priv->exp_uncomp_pos != fd->pos) {
+			/*
+			 *  there has been a seek in the uncompressed fop_fd
+			 * we have to restart the decompression and loop eating
+			 * the decompressed data up to the seek point
+			 */
+			lwsl_info("seek in decompressed\n");
+
+			lws_fops_zip_reset_inflate(priv);
+
+			while (priv->exp_uncomp_pos != fd->pos) {
+				rlen = len;
+				if (rlen > fd->pos - priv->exp_uncomp_pos)
+					rlen = fd->pos - priv->exp_uncomp_pos;
+				if (lws_fops_zip_read(fd, amount, buf, rlen))
+					return LWS_FZ_ERR_SEEK_COMPRESSED;
+			}
+			*amount = 0;
+		}
+
+		priv->inflate.avail_out = len;
+		priv->inflate.next_out = buf;
+
+spin:
+		if (!priv->inflate.avail_in) {
+			rlen = sizeof(priv->rbuf);
+			if (rlen > priv->hdr.comp_size -
+				   (cur - priv->content_start))
+				rlen = priv->hdr.comp_size -
+				       (priv->hdr.comp_size -
+					priv->content_start);
+
+			if (priv->zip_fop_fd->fops->LWS_FOP_READ(
+					priv->zip_fop_fd, &ramount, priv->rbuf,
+					rlen))
+				return LWS_FZ_ERR_READ_CONTENT;
+
+			cur += ramount;
+
+			priv->inflate.avail_in = ramount;
+			priv->inflate.next_in = priv->rbuf;
+		}
+
+		ret = inflate(&priv->inflate, Z_NO_FLUSH);
+		if (ret == Z_STREAM_ERROR)
+			return ret;
+
+		switch (ret) {
+		case Z_NEED_DICT:
+			ret = Z_DATA_ERROR;
+			/* and fall through */
+		case Z_DATA_ERROR:
+		case Z_MEM_ERROR:
+
+			return ret;
+		}
+
+		if (!priv->inflate.avail_in && priv->inflate.avail_out &&
+		     cur != priv->content_start + priv->hdr.comp_size)
+			goto spin;
+
+		*amount = len - priv->inflate.avail_out;
+
+		priv->exp_uncomp_pos += *amount;
+		fd->pos += *amount;
+
+		return 0;
+	}
+
+	if (priv->add_gzip_container) {
+
+		lwsl_info("%s: gzip + container\n", __func__);
+		*amount = 0;
+
+		/* place the canned header at the start */
+
+		if (len && fd->pos < sizeof(hd)) {
+			rlen = sizeof(hd) - fd->pos;
+			if (rlen > len)
+				rlen = len;
+			/* provide stuff from canned header */
+			memcpy(buf, hd + fd->pos, rlen);
+			fd->pos += rlen;
+			buf += rlen;
+			len -= rlen;
+			*amount += rlen;
+		}
+
+		/* serve gzipped data direct from zipfile */
+
+		if (len && fd->pos >= sizeof(hd) &&
+		    fd->pos < priv->hdr.comp_size + sizeof(hd)) {
+
+			rlen = priv->hdr.comp_size - (priv->zip_fop_fd->pos -
+						      priv->content_start);
+			if (rlen > len)
+				rlen = len;
+
+			if (rlen &&
+			    priv->zip_fop_fd->pos < (priv->hdr.comp_size +
+					    	     priv->content_start)) {
+				if (lws_vfs_file_read(priv->zip_fop_fd,
+						      &ramount, buf, rlen))
+					return LWS_FZ_ERR_READ_CONTENT;
+				*amount += ramount;
+				fd->pos += ramount; // virtual pos
+				buf += ramount;
+				len -= ramount;
+			}
+		}
+
+		/* place the prepared trailer at the end */
+
+		if (len && fd->pos >= priv->hdr.comp_size + sizeof(hd) &&
+		    fd->pos < priv->hdr.comp_size + sizeof(hd) +
+		    	      sizeof(priv->u)) {
+			cur = fd->pos - priv->hdr.comp_size - sizeof(hd);
+			rlen = sizeof(priv->u) - cur;
+			if (rlen > len)
+				rlen = len;
+
+			memcpy(buf, priv->u.trailer8 + cur, rlen);
+
+			*amount += rlen;
+			fd->pos += rlen;
+		}
+
+		return 0;
+	}
+
+	lwsl_info("%s: store\n", __func__);
+
+	if (len > priv->hdr.uncomp_size - (cur - priv->content_start))
+		len = priv->hdr.comp_size - (priv->hdr.comp_size -
+					     priv->content_start);
+
+	if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd,
+						 amount, buf, len))
+		return LWS_FZ_ERR_READ_CONTENT;
+
+	return 0;
+}
+
+struct lws_plat_file_ops fops_zip = {
+	lws_fops_zip_open,
+	lws_fops_zip_close,
+	lws_fops_zip_seek_cur,
+	lws_fops_zip_read,
+	NULL,
+	{ { ".zip/", 5 }, { ".jar/", 5 }, { ".war/", 5 } },
+	NULL,
+};
diff --git a/lib/junzip.c b/lib/junzip.c
deleted file mode 100644
index b20d534ec4ca54c67ce9a8fac1f47642913c42ce..0000000000000000000000000000000000000000
--- a/lib/junzip.c
+++ /dev/null
@@ -1,267 +0,0 @@
-// Unzip library by Per Bothner and Joonas Pihlajamaa.
-// See junzip.h for license and details.
-
-#include <stdlib.h>
-#include <string.h>
-
-#include <zlib.h>
-
-#include "private-libwebsockets.h"
-
-enum {
-	ZC_SIGNATURE				= 0,
-	ZC_VERSION_MADE_BY 			= 4,
-	ZC_VERSION_NEEDED_TO_EXTRACT 		= 6,
-	ZC_GENERAL_PURPOSE_BIT_FLAG 		= 8,
-	ZC_COMPRESSION_METHOD 			= 10,
-	ZC_LAST_MOD_FILE_TIME 			= 12,
-	ZC_LAST_MOD_FILE_DATE 			= 14,
-	ZC_CRC32 				= 16,
-	ZC_COMPRESSED_SIZE 			= 20,
-	ZC_UNCOMPRESSED_SIZE 			= 24,
-	ZC_FILE_NAME_LENGTH 			= 28,
-	ZC_EXTRA_FIELD_LENGTH 			= 30,
-	ZC_FILE_COMMENT_LENGTH 			= 32,
-	ZC_DISK_NUMBER_START 			= 34,
-	ZC_INTERNAL_FILE_ATTRIBUTES 		= 36,
-	ZC_EXTERNAL_FILE_ATTRIBUTES 		= 38,
-	ZC_RELATIVE_OFFSET_OF_LOCAL_HEADER 	= 42,
-	ZC_DIRECTORY_LENGTH 			= 46,
-
-	ZE_SIGNATURE_OFFSET 			= 0,
-	ZE_DESK_NUMBER 				= 4,
-	ZE_CENTRAL_DIRECTORY_DISK_NUMBER 	= 6,
-	ZE_NUM_ENTRIES_THIS_DISK 		= 8,
-	ZE_NUM_ENTRIES 				= 10,
-	ZE_CENTRAL_DIRECTORY_SIZE 		= 12,
-	ZE_CENTRAL_DIRECTORY_OFFSET 		= 16,
-	ZE_ZIP_COMMENT_LENGTH 			= 20,
-	ZE_DIRECTORY_LENGTH 			= 22,
-};
-
-static uint16_t
-get_u16(void *p)
-{
-	const uint8_t *c = (const uint8_t *)p;
-
-	return (uint16_t)((c[0] | (c[1] << 8)));
-}
-
-static uint32_t
-get_u32(void *p)
-{
-	const uint8_t *c = (const uint8_t *)p;
-
-	return (uint32_t)((c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24)));
-}
-
-static int
-zf_seek_set(jzfile_t *zfile, size_t offset)
-{
-	int new_position = offset;
-
-	if (new_position < 0 || new_position > zfile->length)
-		return -1;
-	zfile->position = new_position;
-
-	return 0;
-}
-
-static int
-zf_seek_cur(jzfile_t *zfile, size_t offset)
-{
-	int new_position = zfile->position + offset;
-
-	if (new_position < 0 || new_position > zfile->length)
-		return -1;
-	zfile->position = new_position;
-
-	return 0;
-}
-
-static int
-zf_seek_end(jzfile_t *zfile, size_t offset)
-{
-	int new_position = zfile->length + offset;
-
-	if (new_position < 0 || new_position > zfile->length)
-		return -1;
-	zfile->position = new_position;
-
-	return 0;
-}
-
-size_t
-zf_read(jzfile_t *zfile, void *buf, size_t size)
-{
-	size_t avail = zfile->length - zfile->position;
-
-	if (size > avail)
-		size = avail;
-	memcpy(buf, zfile->start + zfile->position, size);
-	zfile->position += size;
-
-	return size;
-}
-
-/* Read ZIP file end record. Will move within file. */
-int
-jzReadEndRecord(jzfile_t *zip)
-{
-	unsigned char *ptr = zf_current(zip);
-
-	if (zf_seek_end(zip, -ZE_DIRECTORY_LENGTH))
-		return Z_ERRNO;
-
-	while (ptr[0] != 0x50 || ptr[1] != 0x4B || ptr[2] != 5 || ptr[3] != 6)
-		if (ptr-- == zip->start)
-			return Z_ERRNO;
-
-	zip->numEntries = get_u16(ptr + ZE_NUM_ENTRIES);
-	zip->centralDirectoryOffset= get_u32(ptr + ZE_CENTRAL_DIRECTORY_OFFSET);
-
-	if (get_u16(ptr + ZE_DESK_NUMBER) ||
-	    get_u16(ptr + ZE_CENTRAL_DIRECTORY_DISK_NUMBER) ||
-	    zip->numEntries != get_u16(ptr + ZE_NUM_ENTRIES_THIS_DISK))
-		return Z_ERRNO;
-
-	return Z_OK;
-}
-
-/* Read ZIP file global directory. Will move within file. */
-int
-jzReadCentralDirectory(jzfile_t *zip, jzcb_t callback)
-{
-	jzfile_hdr_t h;
-	int i;
-
-	if (zf_seek_set(zip, zip->centralDirectoryOffset))
-		return Z_ERRNO;
-
-	for (i = 0; i < zip->numEntries; i++) {
-		unsigned char *ptr = zf_current(zip);
-
-		if (zf_available(zip) < ZC_DIRECTORY_LENGTH)
-			return Z_ERRNO;
-
-		zf_seek_cur(zip, ZC_DIRECTORY_LENGTH);
-		if (get_u32(ptr + ZC_SIGNATURE) != 0x02014B50)
-			return Z_ERRNO;
-
-		// Construct jzfile_hdr_t from global file h
-		h.compressionMethod = get_u16(ptr + ZC_COMPRESSION_METHOD);
-		h.crc32 = get_u32(ptr + ZC_CRC32);
-		h.compressedSize = get_u32(ptr + ZC_COMPRESSED_SIZE);
-		h.uncompressedSize = get_u32(ptr + ZC_UNCOMPRESSED_SIZE);
-		h.fileNameLength = get_u16(ptr + ZC_FILE_NAME_LENGTH);
-		h.extraFieldLength = get_u16(ptr + ZC_EXTRA_FIELD_LENGTH);
-		h.offset = get_u32(ptr + ZC_RELATIVE_OFFSET_OF_LOCAL_HEADER);
-
-		h.fileNameStart = zf_tell(zip);
-		if (zf_seek_cur(zip, h.fileNameLength + h.extraFieldLength +
-				     get_u16(ptr + ZC_FILE_COMMENT_LENGTH)))
-			return Z_ERRNO;
-
-		if (!callback(zip, i, &h))
-			break; // end if callback returns zero
-	}
-
-	return Z_OK;
-}
-
-int jzSeekData(jzfile_t *zip, jzfile_hdr_t *entry)
-{
-	size_t offset = entry->offset;
-
-	offset += ZIP_LOCAL_FILE_HEADER_LENGTH;
-	offset += entry->fileNameLength + entry->extraFieldLength;
-
-	if (offset < 0 || offset > zip->length)
-		return Z_STREAM_END;
-
-	zip->position = offset;
-
-	return Z_OK;
-}
-
-/* Read data from file stream, described by h, to preallocated buffer */
-int
-jzReadData(jzfile_t *zip, jzfile_hdr_t *h, void *buffer)
-{
-	unsigned char *bytes = (unsigned char *)buffer;
-	long compressedLeft, uncompressedLeft;
-	z_stream strm;
-	int ret;
-
-	switch (h->compressionMethod) {
-	case 0: /* Store - just read it */
-		if (zf_read(zip, buffer, h->uncompressedSize) <
-					 h->uncompressedSize)
-			return Z_ERRNO;
-		break;
-	case 8: /* Deflate - using zlib */
-		strm.zalloc = Z_NULL;
-		strm.zfree = Z_NULL;
-		strm.opaque = Z_NULL;
-
-		strm.avail_in = 0;
-		strm.next_in = Z_NULL;
-
-		/*
-		 * Use inflateInit2 with negative window bits to
-		 * indicate raw data
-		 */
-		if ((ret = inflateInit2(&strm, -MAX_WBITS)) != Z_OK)
-			return ret; /* Zlib errors are negative */
-
-		/* Inflate compressed data */
-		for (compressedLeft = h->compressedSize,
-				      uncompressedLeft = h->uncompressedSize;
-		     compressedLeft && uncompressedLeft && ret != Z_STREAM_END;
-		     compressedLeft -= strm.avail_in) {
-			/* Read next chunk */
-			unsigned char *ptr = zf_current(zip);
-
-			strm.avail_in = compressedLeft;
-			zf_seek_cur(zip, compressedLeft);
-			if (strm.avail_in == 0) {
-				inflateEnd(&strm);
-
-				return Z_ERRNO;
-			}
-
-			strm.next_in = ptr;
-			strm.avail_out = uncompressedLeft;
-			strm.next_out = bytes;
-
-			compressedLeft -= strm.avail_in;
-			/* inflate will change avail_in */
-
-			ret = inflate(&strm, Z_NO_FLUSH);
-
-			if (ret == Z_STREAM_ERROR)
-				return ret;
-
-			switch (ret) {
-			case Z_NEED_DICT:
-				ret = Z_DATA_ERROR;
-				/* and fall through */
-			case Z_DATA_ERROR: case Z_MEM_ERROR:
-				(void)inflateEnd(&strm);
-
-				return ret;
-			}
-
-			/* bytes uncompressed */
-			bytes += uncompressedLeft - strm.avail_out;
-			uncompressedLeft = strm.avail_out;
-		}
-
-		inflateEnd(&strm);
-		break;
-	default:
-		return Z_ERRNO;
-	}
-
-	return Z_OK;
-}
diff --git a/lib/lejp-conf.c b/lib/lejp-conf.c
index 6fc358feec18282eb0c865ce02ef3476c7803923..8fb05531dc17a163812d507361610fdeb240fd69 100644
--- a/lib/lejp-conf.c
+++ b/lib/lejp-conf.c
@@ -455,7 +455,8 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
 			"cgi://",
 			">http://",
 			">https://",
-			"callback://"
+			"callback://",
+			"gzip://",
 		};
 
 		if (!a->fresh_mount)
diff --git a/lib/libev.c b/lib/libev.c
index 3f882b205fd0f0f83044a678e39381bd63ae969f..71834a2d81728879dace6da5ba7e090457c50323 100644
--- a/lib/libev.c
+++ b/lib/libev.c
@@ -170,7 +170,7 @@ lws_libev_accept(struct lws *new_wsi, lws_sock_file_fd_type desc)
 	if (!LWS_LIBEV_ENABLED(context))
 		return;
 
-	if (wsi->mode == LWSCM_RAW_FILEDESC)
+	if (new_wsi->mode == LWSCM_RAW_FILEDESC)
 		fd = desc.filefd;
 	else
 		fd = desc.sockfd;
diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c
index 43da8fbe0642188786f29d104524908531b53297..3f9612b57a5e0167ca40a2f68ffe1aade773982e 100755
--- a/lib/libwebsockets.c
+++ b/lib/libwebsockets.c
@@ -220,9 +220,7 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason)
 			wsi->protocol->callback(wsi,
 						LWS_CALLBACK_RAW_CLOSE_FILE,
 						wsi->user_space, NULL, 0);
-			lws_free_wsi(wsi);
-
-			return;
+			goto async_close;
 	}
 
 #ifdef LWS_WITH_CGI
@@ -264,8 +262,7 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason)
 
 	if (wsi->mode == LWSCM_HTTP_SERVING_ACCEPTED &&
 	    wsi->u.http.fop_fd != NULL) {
-		lws_vfs_file_close(wsi->u.http.fop_fd);
-		wsi->u.http.fop_fd = NULL;
+		lws_vfs_file_close(&wsi->u.http.fop_fd);
 		wsi->vhost->protocols->callback(wsi,
 			LWS_CALLBACK_CLOSED_HTTP, wsi->user_space, NULL, 0);
 		wsi->told_user_closed = 1;
@@ -606,6 +603,7 @@ just_kill_connection:
 		       LWS_EXT_CB_DESTROY_ANY_WSI_CLOSING, NULL, 0) < 0)
 		lwsl_warn("ext destroy wsi failed\n");
 
+async_close:
 	wsi->socket_is_permanently_unusable = 1;
 
 #ifdef LWS_USE_LIBUV
@@ -971,32 +969,105 @@ lws_callback_vhost_protocols(struct lws *wsi, int reason, void *in, int len)
 LWS_VISIBLE LWS_EXTERN void
 lws_set_fops(struct lws_context *context, struct lws_plat_file_ops *fops)
 {
-	memcpy(&context->fops_default, fops, sizeof *fops);
+	memcpy(&context->fops, fops, sizeof *fops);
+}
+
+LWS_VISIBLE LWS_EXTERN lws_filepos_t
+lws_vfs_tell(lws_fop_fd_t fop_fd)
+{
+	return fop_fd->pos;
+}
+
+LWS_VISIBLE LWS_EXTERN lws_filepos_t
+lws_vfs_get_length(lws_fop_fd_t fop_fd)
+{
+	return fop_fd->len;
+}
+
+LWS_VISIBLE LWS_EXTERN uint32_t
+lws_vfs_get_mod_time(lws_fop_fd_t fop_fd)
+{
+	return fop_fd->mod_time;
+}
+
+LWS_VISIBLE lws_fileofs_t
+lws_vfs_file_seek_set(lws_fop_fd_t fop_fd, lws_fileofs_t offset)
+{
+	lws_fileofs_t ofs;
+	lwsl_debug("%s: seeking to %ld, len %ld\n", __func__, offset, fop_fd->len);
+	ofs = fop_fd->fops->LWS_FOP_SEEK_CUR(fop_fd, offset - fop_fd->pos);
+	lwsl_debug("%s: result %ld, fop_fd pos %ld\n", __func__, ofs, fop_fd->pos);
+	return ofs;
+}
+
+
+LWS_VISIBLE lws_fileofs_t
+lws_vfs_file_seek_end(lws_fop_fd_t fop_fd, lws_fileofs_t offset)
+{
+	return fop_fd->fops->LWS_FOP_SEEK_CUR(fop_fd, fop_fd->len + fop_fd->pos + offset);
 }
 
-LWS_VISIBLE LWS_EXTERN const struct lws_plat_file_ops *
-lws_select_fops_by_vfs_path(const struct lws_context *context, const char *vfs_path)
+
+const struct lws_plat_file_ops *
+lws_vfs_select_fops(const struct lws_plat_file_ops *fops, const char *vfs_path,
+		    const char **vpath)
 {
-	const struct lws_plat_file_ops *fops = context->fops;
-	int hit = -1, n = 0, matchlen = 0, len;
+	const struct lws_plat_file_ops *pf;
+	const char *p = vfs_path;
+	int n;
+
+	*vpath = NULL;
+
+	/* no non-platform fops, just use that */
+
+	if (!fops->next)
+		return fops;
 
-	while (fops->open) {
-		len = strlen(fops->path_prefix);
+	/*
+	 *  scan the vfs path looking for indications we are to be
+	 * handled by a specific fops
+	 */
+
+	while (*p) {
+		if (*p != '/') {
+			p++;
+			continue;
+		}
+		/* the first one is always platform fops, so skip */
+		pf = fops->next;
+		while (pf) {
+			n = 0;
+			while (n < ARRAY_SIZE(pf->fi) && pf->fi[n].sig) {
+				if (p >= vfs_path + pf->fi[n].len)
+					if (!strncmp(p - (pf->fi[n].len - 1),
+						    pf->fi[n].sig,
+						    pf->fi[n].len - 1)) {
+						*vpath = p + 1;
+						return pf;
+					}
 
-		if (!strncmp(vfs_path, fops->path_prefix, len))
-			if (len > matchlen) {
-				hit = n;
-				matchlen = len;
+				n++;
 			}
-		fops++;
+			pf = pf->next;
+		}
+		p++;
 	}
 
-	if (hit < 0)
-		return context->fops_default;
+	return fops;
+}
+
+LWS_VISIBLE LWS_EXTERN lws_fop_fd_t LWS_WARN_UNUSED_RESULT
+lws_vfs_file_open(const struct lws_plat_file_ops *fops, const char *vfs_path,
+		  lws_fop_flags_t *flags)
+{
+	const char *vpath;
+	const struct lws_plat_file_ops *selected = lws_vfs_select_fops(
+			fops, vfs_path, &vpath);
 
-	return &context->fops[hit];
+	return selected->LWS_FOP_OPEN(fops, vfs_path, vpath, flags);
 }
 
+
 /**
  * lws_now_secs() - seconds since 1970-1-1
  *
@@ -1396,7 +1467,7 @@ lws_union_transition(struct lws *wsi, enum connection_mode mode)
 LWS_VISIBLE struct lws_plat_file_ops *
 lws_get_fops(struct lws_context *context)
 {
-	return &context->fops_default[0];
+	return (struct lws_plat_file_ops *)context->fops;
 }
 
 LWS_VISIBLE LWS_EXTERN struct lws_context *
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
index bf7cc642e41da5753593222e2d14f1d02be89d09..14682a9b7e3820f783510965bf85220987abc913 100644
--- a/lib/libwebsockets.h
+++ b/lib/libwebsockets.h
@@ -33,6 +33,12 @@ extern "C" {
 #include <stdarg.h>
 #endif
 
+static inline int lws_is_be(void) {
+	const int probe = ~0xff;
+
+	return *(const char *)&probe;
+}
+
 #if defined(LWS_WITH_ESP8266)
 struct sockaddr_in;
 #define LWS_POSIX 0
@@ -1816,9 +1822,7 @@ struct lws_context_creation_info {
 	 * by a sentinel with NULL .open.
 	 *
 	 * If NULL, lws provides just the platform file operations struct for
-	 * backwards compatibility.  If set to point to an array of fops
-	 * structs, lws_select_fops_by_vfs_path() will select the best match
-	 * comparing the left of vfs_path to each fops .path_prefix.
+	 * backwards compatibility.
 	 */
 
 	/* Add new things just above here ---^
@@ -2559,6 +2563,7 @@ lws_get_mimetype(const char *file, const struct lws_http_mount *m);
 LWS_VISIBLE LWS_EXTERN int
 lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type,
 		    const char *other_headers, int other_headers_len);
+
 LWS_VISIBLE LWS_EXTERN int
 lws_serve_http_file_fragment(struct lws *wsi);
 //@}
@@ -4268,29 +4273,27 @@ lws_cgi_kill(struct lws *wsi);
 
 #if defined(LWS_WITH_ESP32)
 /* sdk preprocessor defs? compiler issue? gets confused with member names */
-#define LWS_FOP_OPEN _open
-#define LWS_FOP_CLOSE _close
-#define LWS_FOP_SEEK_CUR _seek_cur
-#define LWS_FOP_READ _read
-#define LWS_FOP_WRITE _write
+#define LWS_FOP_OPEN		_open
+#define LWS_FOP_CLOSE		_close
+#define LWS_FOP_SEEK_CUR	_seek_cur
+#define LWS_FOP_READ		_read
+#define LWS_FOP_WRITE		_write
 #else
-#define LWS_FOP_OPEN open
-#define LWS_FOP_CLOSE close
-#define LWS_FOP_SEEK_CUR seek_cur
-#define LWS_FOP_READ read
-#define LWS_FOP_WRITE write
+#define LWS_FOP_OPEN		open
+#define LWS_FOP_CLOSE		close
+#define LWS_FOP_SEEK_CUR	seek_cur
+#define LWS_FOP_READ		read
+#define LWS_FOP_WRITE		write
 #endif
 
 #define LWS_FOP_FLAGS_MASK		   ((1 << 23) - 1)
 #define LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP (1 << 24)
 #define LWS_FOP_FLAG_COMPR_IS_GZIP	   (1 << 25)
+#define LWS_FOP_FLAG_MOD_TIME_VALID	   (1 << 26)
+#define LWS_FOP_FLAG_VIRTUAL		   (1 << 27)
 
 struct lws_plat_file_ops;
-struct lws_fop_fd {
-	lws_filefd_type fd;
-	const struct lws_plat_file_ops *fops;
-	void *filesystem_priv;
-};
+
 #if defined(WIN32) || defined(_WIN32)
 /* ... */
 #if !defined(ssize_t)
@@ -4313,26 +4316,49 @@ typedef unsigned char uint8_t;
 #endif
 #endif
 
-typedef struct lws_fop_fd *lws_fop_fd_t;
 typedef size_t lws_filepos_t;
 typedef ssize_t lws_fileofs_t;
 typedef uint32_t lws_fop_flags_t;
 
+struct lws_fop_fd {
+	lws_filefd_type			fd;
+	/**< real file descriptor related to the file... */
+	const struct lws_plat_file_ops	*fops;
+	/**< fops that apply to this fop_fd */
+	void				*filesystem_priv;
+	/**< ignored by lws; owned by the fops handlers */
+	lws_filepos_t			pos;
+	/**< generic "position in file" */
+	lws_filepos_t			len;
+	/**< generic "length of file" */
+	lws_fop_flags_t			flags;
+	/**< copy of the returned flags */
+	uint32_t			mod_time;
+	/**< optional "modification time of file", only valid if .open()
+	 * set the LWS_FOP_FLAG_MOD_TIME_VALID flag */
+};
+typedef struct lws_fop_fd *lws_fop_fd_t;
+
+struct lws_fops_index {
+	const char *sig;	/* NULL or vfs signature, eg, ".zip/" */
+	uint8_t len;		/* length of above string */
+};
+
 struct lws_plat_file_ops {
 	lws_fop_fd_t (*LWS_FOP_OPEN)(const struct lws_plat_file_ops *fops,
-				     const char *filename,
-				     lws_filepos_t *filelen,
+				     const char *filename, const char *vpath,
 				     lws_fop_flags_t *flags);
 	/**< Open file (always binary access if plat supports it)
-	 * filelen is filled on exit to be the length of the file
+	 * vpath may be NULL, or if the fops understands it, the point at which
+	 * the filename's virtual part starts.
 	 * *flags & LWS_FOP_FLAGS_MASK should be set to O_RDONLY or O_RDWR.
 	 * If the file may be gzip-compressed,
 	 * LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP is set.  If it actually is
 	 * gzip-compressed, then the open handler should OR
 	 * LWS_FOP_FLAG_COMPR_IS_GZIP on to *flags before returning.
 	 */
-	int (*LWS_FOP_CLOSE)(lws_fop_fd_t fop_fd);
-	/**< close file */
+	int (*LWS_FOP_CLOSE)(lws_fop_fd_t *fop_fd);
+	/**< close file AND set the pointer to NULL */
 	lws_fileofs_t (*LWS_FOP_SEEK_CUR)(lws_fop_fd_t fop_fd,
 					  lws_fileofs_t offset_from_cur_pos);
 	/**< seek from current position */
@@ -4342,8 +4368,12 @@ struct lws_plat_file_ops {
 	int (*LWS_FOP_WRITE)(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
 			     uint8_t *buf, lws_filepos_t len);
 	/**< Write to file, on exit *amount is set to amount actually written */
-	const char *path_prefix;
-	/**< Optional, NULL or filesystem namespace prefix matching this fops */
+
+	struct lws_fops_index fi[3];
+	/**< vfs path signatures implying use of this fops */
+
+	const struct lws_plat_file_ops *next;
+	/**< NULL or next fops in list */
 
 	/* Add new things just above here ---^
 	 * This is part of the ABI, don't needlessly break compatibility */
@@ -4358,24 +4388,63 @@ LWS_VISIBLE LWS_EXTERN struct lws_plat_file_ops * LWS_WARN_UNUSED_RESULT
 lws_get_fops(struct lws_context *context);
 LWS_VISIBLE LWS_EXTERN void
 lws_set_fops(struct lws_context *context, struct lws_plat_file_ops *fops);
-LWS_VISIBLE LWS_EXTERN const struct lws_plat_file_ops * LWS_WARN_UNUSED_RESULT
-lws_select_fops_by_vfs_path(const struct lws_context *context, const char *vfs_path);
 /**
- * lws_plat_file_open() - file open operations
+ * lws_vfs_tell() - get current file position
+ *
+ * \param fop_fd: fop_fd we are asking about
+ */
+LWS_VISIBLE LWS_EXTERN lws_filepos_t LWS_WARN_UNUSED_RESULT
+lws_vfs_tell(lws_fop_fd_t fop_fd);
+/**
+ * lws_vfs_get_length() - get current file total length in bytes
+ *
+ * \param fop_fd: fop_fd we are asking about
+ */
+LWS_VISIBLE LWS_EXTERN lws_filepos_t LWS_WARN_UNUSED_RESULT
+lws_vfs_get_length(lws_fop_fd_t fop_fd);
+/**
+ * lws_vfs_get_mod_time() - get time file last modified
+ *
+ * \param fop_fd: fop_fd we are asking about
+ */
+LWS_VISIBLE LWS_EXTERN uint32_t LWS_WARN_UNUSED_RESULT
+lws_vfs_get_mod_time(lws_fop_fd_t fop_fd);
+/**
+ * lws_vfs_file_seek_set() - seek relative to start of file
+ *
+ * \param fop_fd: fop_fd we are seeking in
+ * \param offset: offset from start of file
+ */
+LWS_VISIBLE LWS_EXTERN lws_fileofs_t
+lws_vfs_file_seek_set(lws_fop_fd_t fop_fd, lws_fileofs_t offset);
+/**
+ * lws_vfs_file_seek_end() - seek relative to end of file
+ *
+ * \param fop_fd: fop_fd we are seeking in
+ * \param offset: offset from start of file
+ */
+LWS_VISIBLE LWS_EXTERN lws_fileofs_t
+lws_vfs_file_seek_end(lws_fop_fd_t fop_fd, lws_fileofs_t offset);
+
+LWS_VISIBLE LWS_EXTERN struct lws_plat_file_ops fops_zip;
+
+/**
+ * lws_plat_file_open() - open vfs filepath
  *
  * \param fops: file ops struct that applies to this descriptor
- * \param filename: filename to open
- * \param filelen: length of file (filled in by call)
+ * \param vfs_path: filename to open
  * \param flags: pointer to open flags
  *
+ * The vfs_path is scanned for known fops signatures, and the open directed
+ * to any matching fops open.
+ *
+ * User code should use this api to perform vfs opens.
+ *
  * returns semi-opaque handle
  */
-static LWS_INLINE lws_fop_fd_t LWS_WARN_UNUSED_RESULT
-lws_vfs_file_open(const struct lws_plat_file_ops *fops, const char *filename,
-		   lws_filepos_t *filelen, lws_fop_flags_t *flags)
-{
-	return fops->LWS_FOP_OPEN(fops, filename, filelen, flags);
-}
+LWS_VISIBLE LWS_EXTERN lws_fop_fd_t LWS_WARN_UNUSED_RESULT
+lws_vfs_file_open(const struct lws_plat_file_ops *fops, const char *vfs_path,
+		  lws_fop_flags_t *flags);
 
 /**
  * lws_plat_file_close() - close file
@@ -4383,9 +4452,9 @@ lws_vfs_file_open(const struct lws_plat_file_ops *fops, const char *filename,
  * \param fop_fd: file handle to close
  */
 static LWS_INLINE int
-lws_vfs_file_close(lws_fop_fd_t fop_fd)
+lws_vfs_file_close(lws_fop_fd_t *fop_fd)
 {
-	return fop_fd->fops->LWS_FOP_CLOSE(fop_fd);
+	return (*fop_fd)->fops->LWS_FOP_CLOSE(fop_fd);
 }
 
 /**
@@ -4429,15 +4498,15 @@ lws_vfs_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
 	return fop_fd->fops->LWS_FOP_WRITE(fop_fd, amount, buf, len);
 }
 
-/* these are the flatform file operations implmenetations... they can
+/* these are the platform file operations implementations... they can
  * be called directly and used in fops arrays
  */
 
 LWS_VISIBLE LWS_EXTERN lws_fop_fd_t
 _lws_plat_file_open(const struct lws_plat_file_ops *fops, const char *filename,
-		    lws_filepos_t *filelen, lws_fop_flags_t *flags);
+		    const char *vpath, lws_fop_flags_t *flags);
 LWS_VISIBLE LWS_EXTERN int
-_lws_plat_file_close(lws_fop_fd_t fop_fd);
+_lws_plat_file_close(lws_fop_fd_t *fop_fd);
 LWS_VISIBLE LWS_EXTERN lws_fileofs_t
 _lws_plat_file_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset);
 LWS_VISIBLE LWS_EXTERN int
diff --git a/lib/lws-plat-esp32.c b/lib/lws-plat-esp32.c
index 680dc5607a0b359f29f72ca208dd8cb28a8b78f8..e858b5a6e7df220dfc92b605a5aed0f541dd030f 100644
--- a/lib/lws-plat-esp32.c
+++ b/lib/lws-plat-esp32.c
@@ -397,7 +397,7 @@ lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt)
 
 LWS_VISIBLE lws_fop_fd_t
 _lws_plat_file_open(struct lws_plat_file_ops *fops, const char *filename,
-		   lws_filepos_t *filelen, lws_fop_flags_t *flags)
+		    const char *vpath, lws_fop_flags_t *flags)
 {
 	struct stat stat_buf;
 	lws_fop_fd_t fop_fd;
@@ -414,9 +414,10 @@ _lws_plat_file_open(struct lws_plat_file_ops *fops, const char *filename,
 
 	fop_fd->fops = fops;
 	fop_fd->fd = ret;
+	fop_fd->flags = *flags;
 	fop_fd->filesystem_priv = NULL; /* we don't use it */
-
-	*filelen = stat_buf.st_size;
+	fop_fd->pos = 0;
+	fop_fd->len = stat_buf.st_size;
 
 	return fop_fd;
 
@@ -427,11 +428,12 @@ bail:
 }
 
 LWS_VISIBLE int
-_lws_plat_file_close(lws_fop_fd_t fops_fd)
+_lws_plat_file_close(lws_fop_fd_t *fops_fd)
 {
-	int fd = fops_fd->fd;
+	int fd = (*fops_fd)->fd;
 
-	free(fd);
+	free(*fops_fd);
+	*fops_fd = NULL;
 
 	return close(fd);
 }
@@ -453,7 +455,7 @@ _lws_plat_file_read(lws_fop_fd_t fops_fd, lws_filepos_t *amount,
 		*amount = 0;
 		return -1;
 	}
-
+	fop_fd->pos += n;
 	*amount = n;
 
 	return 0;
@@ -470,7 +472,7 @@ _lws_plat_file_write(lws_fop_fd_t fops_fd, lws_filepos_t *amount,
 		*amount = 0;
 		return -1;
 	}
-
+	fop_fd->pos += n;
 	*amount = n;
 
 	return 0;
diff --git a/lib/lws-plat-optee.c b/lib/lws-plat-optee.c
index f6051d096ba596aedff18d307ddde655fb2ab3ae..20379bcd495f728985109ca16f8fe8b58ce1e49b 100644
--- a/lib/lws-plat-optee.c
+++ b/lib/lws-plat-optee.c
@@ -263,14 +263,13 @@ lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt)
 
 LWS_VISIBLE lws_fop_fd_t
 _lws_plat_file_open(lws_plat_file_open(struct lws_plat_file_ops *fops,
-		    const char *filename, lws_filepos_t *filelen,
-		    lws_fop_flags_t *flags)
+		    const char *filename, lws_fop_flags_t *flags)
 {
 	return NULL;
 }
 
 LWS_VISIBLE int
-_lws_plat_file_close(lws_fop_fd_t fop_fd)
+_lws_plat_file_close(lws_fop_fd_t *fop_fd)
 {
 	return 0;
 }
diff --git a/lib/lws-plat-unix.c b/lib/lws-plat-unix.c
index 4ab2eea4e5a997bce1470994743641c814252f86..49c3d21e8d81f00c19cc4a4b6dd6af784350bb54 100644
--- a/lib/lws-plat-unix.c
+++ b/lib/lws-plat-unix.c
@@ -611,7 +611,7 @@ lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt)
 
 LWS_VISIBLE lws_fop_fd_t
 _lws_plat_file_open(const struct lws_plat_file_ops *fops, const char *filename,
-		    lws_filepos_t *filelen, lws_fop_flags_t *flags)
+		    const char *vpath, lws_fop_flags_t *flags)
 {
 	struct stat stat_buf;
 	int ret = open(filename, (*flags) & LWS_FOP_FLAGS_MASK, 0664);
@@ -628,9 +628,11 @@ _lws_plat_file_open(const struct lws_plat_file_ops *fops, const char *filename,
 		goto bail;
 
 	fop_fd->fops = fops;
+	fop_fd->flags = *flags;
 	fop_fd->fd = ret;
 	fop_fd->filesystem_priv = NULL; /* we don't use it */
-	*filelen = stat_buf.st_size;
+	fop_fd->len = stat_buf.st_size;
+	fop_fd->pos = 0;
 
 	return fop_fd;
 
@@ -640,18 +642,36 @@ bail:
 }
 
 LWS_VISIBLE int
-_lws_plat_file_close(lws_fop_fd_t fop_fd)
+_lws_plat_file_close(lws_fop_fd_t *fop_fd)
 {
-	int fd = fop_fd->fd;
+	int fd = (*fop_fd)->fd;
+
+	free(*fop_fd);
+	*fop_fd = NULL;
 
-	free(fop_fd);
 	return close(fd);
 }
 
 LWS_VISIBLE lws_fileofs_t
 _lws_plat_file_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset)
 {
-	return lseek(fop_fd->fd, offset, SEEK_CUR);
+	lws_fileofs_t r;
+
+	if (offset > 0 && offset > fop_fd->len - fop_fd->pos)
+		offset = fop_fd->len - fop_fd->pos;
+
+	if ((lws_fileofs_t)fop_fd->pos + offset < 0)
+		offset = -fop_fd->pos;
+
+	r = lseek(fop_fd->fd, offset, SEEK_CUR);
+
+	if (r >= 0)
+		fop_fd->pos = r;
+	else
+		lwsl_err("error seeking from cur %ld, offset %ld\n",
+			 fop_fd->pos, offset);
+
+	return r;
 }
 
 LWS_VISIBLE int
@@ -665,7 +685,9 @@ _lws_plat_file_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
 		*amount = 0;
 		return -1;
 	}
-
+	fop_fd->pos += n;
+	lwsl_debug("%s: read %ld of req %ld, pos %ld, len %ld\n", __func__, n,
+		   (long)len, fop_fd->pos, fop_fd->len);
 	*amount = n;
 
 	return 0;
@@ -683,6 +705,7 @@ _lws_plat_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
 		return -1;
 	}
 
+	fop_fd->pos += n;
 	*amount = n;
 
 	return 0;
diff --git a/lib/lws-plat-win.c b/lib/lws-plat-win.c
index 117b69f26f4d825b574739b13f19a650e85bb2d9..188f1a94f75516a903a22447bdf1996fee5d4fc8 100644
--- a/lib/lws-plat-win.c
+++ b/lib/lws-plat-win.c
@@ -523,7 +523,7 @@ lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt)
 
 LWS_VISIBLE lws_fop_fd_t
 _lws_plat_file_open(struct lws_plat_file_ops *fops, const char *filename,
-		   lws_filepos_t *filelen, lws_fop_flags_t *flags)
+		    const char *vpath, lws_fop_flags_t *flags)
 {
 	HANDLE ret;
 	WCHAR buf[MAX_PATH];
@@ -548,22 +548,23 @@ _lws_plat_file_open(struct lws_plat_file_ops *fops, const char *filename,
 	fop_fd->fops = fops;
 	fop_fd->fd = ret;
 	fop_fd->filesystem_priv = NULL; /* we don't use it */
-
-	*filelen = GetFileSize(ret, NULL);
+	fop_fd->flags = *flags;
+	fop_fd->len = GetFileSize(ret, NULL);
+	fop_fd->pos = 0;
 
 	return fop_fd;
 
 bail:
-	*filelen = 0;
 	return NULL;
 }
 
 LWS_VISIBLE int
-_lws_plat_file_close(lws_fop_fd_t fop_fd)
+_lws_plat_file_close(lws_fop_fd_t *fop_fd)
 {
-	HANDLE fd = fop_fd->fd;
+	HANDLE fd = (*fop_fd)->fd;
 
-	free(fop_fd);
+	free(*fop_fd);
+	*fop_fd = NULL;
 
 	CloseHandle((HANDLE)fd);
 
@@ -588,6 +589,7 @@ _lws_plat_file_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
 		return 1;
 	}
 
+	fop_fd->pos += _amount;
 	*amount = (unsigned long)_amount;
 
 	return 0;
@@ -602,6 +604,8 @@ _lws_plat_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
 	(void)buf;
 	(void)len;
 
+	fop_fd->pos += len;
+
 	lwsl_err("%s: not implemented yet on this platform\n", __func__);
 
 	return -1;
diff --git a/lib/output.c b/lib/output.c
index 232246fbf06fb73b9fcc5a0f8cec88815f3b36e0..efc9573e35e332a1474c5fbb0bf86da442895819 100644
--- a/lib/output.c
+++ b/lib/output.c
@@ -574,7 +574,7 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi)
 					  wsi->trunc_offset,
 					  wsi->trunc_len) < 0) {
 				lwsl_info("%s: closing\n", __func__);
-				return -1;
+				goto file_had_it;
 			}
 			continue;
 		}
@@ -594,7 +594,7 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi)
 			if ((long)lws_vfs_file_seek_cur(wsi->u.http.fop_fd,
 						   wsi->u.http.range.start -
 						   wsi->u.http.filepos) < 0)
-				return -1;
+				goto file_had_it;
 
 			wsi->u.http.filepos = wsi->u.http.range.start;
 
@@ -634,7 +634,7 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi)
 		}
 
 		if (lws_vfs_file_read(wsi->u.http.fop_fd, &amount, p, poss) < 0)
-			return -1; /* caller will close */
+			goto file_had_it; /* caller will close */
 		
 		//lwsl_notice("amount %ld\n", amount);
 
@@ -656,7 +656,7 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi)
 				     wsi->vhost->protocols[(int)wsi->protocol_interpret_idx].callback, wsi,
 				     LWS_CALLBACK_PROCESS_HTML,
 				     wsi->user_space, &args, 0) < 0)
-					return -1;
+					goto file_had_it;
 				n = args.len;
 				p = (unsigned char *)args.p;
 			} else
@@ -678,7 +678,7 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi)
 					LWS_WRITE_HTTP
 				);
 			if (m < 0)
-				return -1;
+				goto file_had_it;
 
 			wsi->u.http.filepos += amount;
 
@@ -703,7 +703,7 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi)
 				if (lws_vfs_file_seek_cur(wsi->u.http.fop_fd,
 							   m - n) ==
 							     (unsigned long)-1)
-					return -1;
+					goto file_had_it;
 			}
 		}
 all_sent:
@@ -716,8 +716,7 @@ all_sent:
 		     {
 			wsi->state = LWSS_HTTP;
 			/* we might be in keepalive, so close it off here */
-			lws_vfs_file_close(wsi->u.http.fop_fd);
-			wsi->u.http.fop_fd = NULL;
+			lws_vfs_file_close(&wsi->u.http.fop_fd);
 			
 			lwsl_debug("file completed\n");
 
@@ -736,6 +735,11 @@ all_sent:
 	lws_callback_on_writable(wsi);
 
 	return 0; /* indicates further processing must be done */
+
+file_had_it:
+	lws_vfs_file_close(&wsi->u.http.fop_fd);
+
+	return -1;
 }
 
 #if LWS_POSIX
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index 3431c9717f2ed3f546c295abb276a079dd3b7390..80d0636826798c5e77b25ef13f8d3bc61016db84 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -439,57 +439,6 @@ extern "C" {
 #define SYSTEM_RANDOM_FILEPATH "/dev/urandom"
 #endif
 
-/**
- * Unzip library by Per Bothner.
- * Loosely based on Joonas Pihlajamaa's JUnzip.
- * Released into public domain. https://github.com/jokkebk/JUnzip
- * ------->
- */
-
-typedef struct jzfile {
-    unsigned char *start;
-    off_t length;
-    long position;
-    int numEntries;
-    uint32_t centralDirectoryOffset;
-} jzfile_t;
-
-#define zf_tell(ZF) ((ZF)->position)
-#define zf_available(ZF) ((ZF)->length - (ZF)->position)
-#define zf_current(ZF) ((ZF)->start + (ZF)->position)
-
-#define ZIP_LOCAL_FILE_HEADER_LENGTH 30
-
-typedef struct {
-    uint16_t compressionMethod;
-    uint32_t crc32;
-    uint32_t compressedSize;
-    uint32_t uncompressedSize;
-    long fileNameStart;
-    uint16_t fileNameLength;
-    uint16_t extraFieldLength; // unsupported
-    uint32_t offset;
-} jzfile_hdr_t;
-
-// Callback prototype for central and local file record reading functions
-typedef int (*jzcb_t)(jzfile_t *zip, int index, jzfile_hdr_t *header);
-
-// Read ZIP file end record. Will move within file.
-int jzReadEndRecord(jzfile_t *zip);
-
-// Read ZIP file global directory. Will move within file.
-// Callback is called for each record, until callback returns zero
-int jzReadCentralDirectory(jzfile_t *zip, jzcb_t callback);
-
-  // See to the start of the actual data of the given entry.
-int jzSeekData(jzfile_t *zip, jzfile_hdr_t *header);
-
-// Read data from file stream, described by header, to preallocated buffer
-// Return value is zlib coded, e.g. Z_OK, or error code
-int jzReadData(jzfile_t *zip, jzfile_hdr_t *header, void *buffer);
-
-/* <------ */
-
 enum lws_websocket_opcodes_07 {
 	LWSWSOPC_CONTINUATION = 0,
 	LWSWSOPC_TEXT_FRAME = 1,
@@ -860,7 +809,10 @@ struct lws_context {
 	time_t last_ws_ping_pong_check_s;
 	time_t time_up;
 	const struct lws_plat_file_ops *fops;
-	struct lws_plat_file_ops fops_default[2];
+	struct lws_plat_file_ops fops_platform;
+#if defined(LWS_WITH_ZIP_FOPS)
+	struct lws_plat_file_ops fops_zip;
+#endif
 	struct lws_context_per_thread pt[LWS_MAX_SMP];
 	struct lws_conn_stats conn_stats;
 #ifdef _WIN32
@@ -947,6 +899,7 @@ struct lws_context {
 	short server_string_len;
 	unsigned short ws_ping_pong_interval;
 	unsigned short deprecation_pending_listen_close_count;
+	uint8_t max_fi;
 };
 
 #define lws_get_context_protocol(ctx, x) ctx->vhost_list->protocols[x]
@@ -2005,6 +1958,10 @@ void lws_free(void *p);
 #define lws_free_set_NULL(P)	do { lws_realloc(P, 0); (P) = NULL; } while(0)
 #endif
 
+const struct lws_plat_file_ops *
+lws_vfs_select_fops(const struct lws_plat_file_ops *fops, const char *vfs_path,
+		    const char **vpath);
+
 /* lws_plat_ */
 LWS_EXTERN void
 lws_plat_delete_socket_from_fds(struct lws_context *context,
diff --git a/lib/server.c b/lib/server.c
index 73be40a221be24500bed8ddc7d06bcb03a6c6f5b..32d972e49a7ec7a6e8964604548e4a4be28e8f72 100644
--- a/lib/server.c
+++ b/lib/server.c
@@ -312,6 +312,22 @@ lws_get_mimetype(const char *file, const struct lws_http_mount *m)
 
 	return NULL;
 }
+static lws_fop_flags_t
+lws_vfs_prepare_flags(struct lws *wsi)
+{
+	lws_fop_flags_t f = 0;
+
+	if (!lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING))
+		return f;
+
+	if (strstr(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING),
+		   "gzip")) {
+		lwsl_info("client indicates GZIP is acceptable\n");
+		f |= LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP;
+	}
+
+	return f;
+}
 
 static int
 lws_http_serve(struct lws *wsi, char *uri, const char *origin,
@@ -319,8 +335,11 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin,
 {
 	const struct lws_protocol_vhost_options *pvo = m->interpret;
 	struct lws_process_html_args args;
-	const char *mimetype;
-#if !defined(_WIN32_WCE) && !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32)
+	const char *mimetype, *vpath;
+#if !defined(_WIN32_WCE) && !defined(LWS_WITH_ESP8266) && \
+    !defined(LWS_WITH_ESP32)
+	const struct lws_plat_file_ops *fops;
+	lws_fop_flags_t fflags = LWS_O_RDONLY;
 	struct stat st;
 	int spin = 0;
 #endif
@@ -334,15 +353,37 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin,
 
 	lws_snprintf(path, sizeof(path) - 1, "%s/%s", origin, uri);
 
-#if !defined(_WIN32_WCE) && !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32)
+	fflags |= lws_vfs_prepare_flags(wsi);
+
+#if !defined(_WIN32_WCE) && !defined(LWS_WITH_ESP8266) && \
+    !defined(LWS_WITH_ESP32)
 	do {
 		spin++;
+		fops = lws_vfs_select_fops(wsi->context->fops, path, &vpath);
+
+		if (wsi->u.http.fop_fd)
+			lws_vfs_file_close(&wsi->u.http.fop_fd);
+
+		wsi->u.http.fop_fd = fops->LWS_FOP_OPEN(wsi->context->fops,
+							path, vpath, &fflags);
+		if (!wsi->u.http.fop_fd) {
+			lwsl_err("Unable to open '%s'\n", path);
+
+			return -1;
+		}
 
-		if (stat(path, &st)) {
+		/* if it can't be statted, don't try */
+		if (fflags & LWS_FOP_FLAG_VIRTUAL)
+			break;
+
+		if (fstat(wsi->u.http.fop_fd->fd, &st)) {
 			lwsl_info("unable to stat %s\n", path);
 			goto bail;
 		}
 
+		wsi->u.http.fop_fd->mod_time = (uint32_t)st.st_mtime;
+		fflags |= LWS_FOP_FLAG_MOD_TIME_VALID;
+
 		lwsl_debug(" %s mode %d\n", path, S_IFMT & st.st_mode);
 #if !defined(WIN32) && LWS_POSIX
 		if ((S_IFMT & st.st_mode) == S_IFLNK) {
@@ -367,8 +408,9 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin,
 	if (spin == 5)
 		lwsl_err("symlink loop %s \n", path);
 
-	n = sprintf(sym, "%08lX%08lX", (unsigned long)st.st_size,
-				   (unsigned long)st.st_mtime);
+	n = sprintf(sym, "%08lX%08lX",
+		    (unsigned long)lws_vfs_get_length(wsi->u.http.fop_fd),
+		    (unsigned long)lws_vfs_get_mod_time(wsi->u.http.fop_fd));
 
 	/* disable ranges if IF_RANGE token invalid */
 
@@ -382,7 +424,8 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin,
 		 * he thinks he has some version of it already,
 		 * check if the tag matches
 		 */
-		if (!strcmp(sym, lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_IF_NONE_MATCH))) {
+		if (!strcmp(sym, lws_hdr_simple_ptr(wsi,
+					WSI_TOKEN_HTTP_IF_NONE_MATCH))) {
 
 			lwsl_debug("%s: ETAG match %s %s\n", __func__,
 				   uri, origin);
@@ -407,6 +450,8 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin,
 				return -1;
 			}
 
+			lws_vfs_file_close(&wsi->u.http.fop_fd);
+
 			return lws_http_transaction_completed(wsi);
 		}
 	}
@@ -2060,7 +2105,7 @@ lws_server_socket_service(struct lws_context *context, struct lws *wsi,
 
 		len = lws_ssl_capable_read(wsi, pt->serv_buf,
 					   context->pt_serv_buf_size);
-		lwsl_notice("%s: wsi %p read %d\r\n", __func__, wsi, len);
+//		lwsl_notice("%s: wsi %p read %d\r\n", __func__, wsi, len);
 		switch (len) {
 		case 0:
 			lwsl_info("%s: read 0 len\n", __func__);
@@ -2228,39 +2273,33 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type,
 	unsigned char *end = p + context->pt_serv_buf_size - LWS_PRE;
 	unsigned long computed_total_content_length;
 	int ret = 0, cclen = 8, n = HTTP_STATUS_OK;
-	lws_fop_flags_t fflags = O_RDONLY;
+	lws_fop_flags_t fflags = LWS_O_RDONLY;
 #if defined(LWS_WITH_RANGES)
 	int ranges;
 #endif
+	const struct lws_plat_file_ops *fops;
+	const char *vpath;
 
-	if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING))
-		if (strstr("gzip",  lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING)) &&
-		    strstr("deflate",  lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING))) {
-			lwsl_debug("client indicates GZIP is acceptable\n");
-			fflags |= LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP;
-		}
-
-
-	wsi->u.http.fop_fd = lws_vfs_file_open(lws_select_fops_by_vfs_path(
-						wsi->context, file), file,
-						&wsi->u.http.filelen,
-						&fflags);
+	/*
+	 * We either call the platform fops .open with first arg platform fops,
+	 * or we call fops_zip .open with first arg platform fops, and fops_zip
+	 * open will decide whether to switch to fops_zip or stay with fops_def.
+	 *
+	 * If wsi->u.http.fop_fd is already set, the caller already opened it
+	 */
 	if (!wsi->u.http.fop_fd) {
-		lwsl_err("Unable to open '%s'\n", file);
-
-		return -1;
-	}
-	computed_total_content_length = wsi->u.http.filelen;
+		fops = lws_vfs_select_fops(wsi->context->fops, file, &vpath);
+		fflags |= lws_vfs_prepare_flags(wsi);
+		wsi->u.http.fop_fd = fops->LWS_FOP_OPEN(wsi->context->fops,
+							file, vpath, &fflags);
+		if (!wsi->u.http.fop_fd) {
+			lwsl_err("Unable to open '%s'\n", file);
 
-	if ((fflags & (LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP |
-		       LWS_FOP_FLAG_COMPR_IS_GZIP)) ==
-	    (LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP | LWS_FOP_FLAG_COMPR_IS_GZIP)) {
-		if (lws_add_http_header_by_token(wsi,
-			WSI_TOKEN_HTTP_CONTENT_ENCODING,
-			(unsigned char *)"gzip, deflate", 13, &p, end))
 			return -1;
-		lwsl_debug("file is being provided in gzip\n");
+		}
 	}
+	wsi->u.http.filelen = lws_vfs_get_length(wsi->u.http.fop_fd);
+	computed_total_content_length = wsi->u.http.filelen;
 
 #if defined(LWS_WITH_RANGES)
 	ranges = lws_ranges_init(wsi, rp, wsi->u.http.filelen);
@@ -2279,6 +2318,8 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type,
 		if (lws_http_transaction_completed(wsi))
 			return -1; /* <0 means just hang up */
 
+		lws_vfs_file_close(&wsi->u.http.fop_fd);
+
 		return 0; /* == 0 means we dealt with the transaction complete */
 	}
 	if (ranges)
@@ -2288,6 +2329,16 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type,
 	if (lws_add_http_header_status(wsi, n, &p, end))
 		return -1;
 
+	if ((wsi->u.http.fop_fd->flags & (LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP |
+		       LWS_FOP_FLAG_COMPR_IS_GZIP)) ==
+	    (LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP | LWS_FOP_FLAG_COMPR_IS_GZIP)) {
+		if (lws_add_http_header_by_token(wsi,
+			WSI_TOKEN_HTTP_CONTENT_ENCODING,
+			(unsigned char *)"gzip", 4, &p, end))
+			return -1;
+		lwsl_info("file is being provided in gzip\n");
+	}
+
 #if defined(LWS_WITH_RANGES)
 	if (ranges < 2 && content_type && content_type[0])
 		if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
diff --git a/test-server/candide.zip b/test-server/candide.zip
new file mode 100644
index 0000000000000000000000000000000000000000..82a66199a701657e3b4819f36e9fe18f2379a886
Binary files /dev/null and b/test-server/candide.zip differ
diff --git a/test-server/test-server-http.c b/test-server/test-server-http.c
index 154674b99c27e66850ef501d179c7be1c4c64cb9..3b3029d81b73c2391060d62b35e30b6aa4b00285 100644
--- a/test-server/test-server-http.c
+++ b/test-server/test-server-http.c
@@ -292,13 +292,14 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
 			p = buffer + LWS_PRE;
 			end = p + sizeof(buffer) - LWS_PRE;
 
-			pss->fop_fd = lws_vfs_file_open(lws_get_fops(lws_get_context(wsi)),
-					leaf_path, &file_len, &flags);
-
+			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
@@ -346,7 +347,7 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
 				      p - (buffer + LWS_PRE),
 				      LWS_WRITE_HTTP_HEADERS);
 			if (n < 0) {
-				lws_vfs_file_close(pss->fop_fd);
+				lws_vfs_file_close(&pss->fop_fd);
 				return -1;
 			}
 			/*
@@ -588,12 +589,12 @@ later:
 		lws_callback_on_writable(wsi);
 		break;
 penultimate:
-		lws_vfs_file_close(pss->fop_fd);
+		lws_vfs_file_close(&pss->fop_fd);
 		pss->fop_fd = NULL;
 		goto try_to_reuse;
 
 bail:
-		lws_vfs_file_close(pss->fop_fd);
+		lws_vfs_file_close(&pss->fop_fd);
 
 		return -1;
 
diff --git a/test-server/test-server-libev.c b/test-server/test-server-libev.c
index b27b2ed715377d8fa21c6770b654e1fd626e1cb0..e1424ce21c1914678937996329f6482a54de7623 100644
--- a/test-server/test-server-libev.c
+++ b/test-server/test-server-libev.c
@@ -150,16 +150,16 @@ static const struct lws_extension exts[] = {
  * compressed files without decompressing the whole archive)
  */
 static lws_fop_fd_t
-test_server_fops_open(struct lws_plat_file_ops *fops, const char *filename,
-		   lws_filepos_t *filelen, lws_fop_flags_t *flags)
+test_server_fops_open(const struct lws_plat_file_ops *fops,
+		      const char *vfs_path, const char *vpath,
+		      lws_fop_flags_t *flags)
 {
 	lws_fop_fd_t n;
 
 	/* call through to original platform implementation */
-	n = fops_plat.open(fops, filename, filelen, flags);
+	n = fops_plat.open(fops, vfs_path, vpath, flags);
 
-	lwsl_notice("%s: opening %s, ret %p, len %lu\n", __func__, filename,
-		    n, *filelen);
+	lwsl_notice("%s: opening %s, ret %p\n", __func__, vfs_path, n);
 
 	return n;
 }
diff --git a/test-server/test-server-v2.0.c b/test-server/test-server-v2.0.c
index 7e5a559db6f180adbcf5019a57887a30c662ab4e..96e00a50e6458272fd86e059811def79adec1b29 100644
--- a/test-server/test-server-v2.0.c
+++ b/test-server/test-server-v2.0.c
@@ -67,11 +67,33 @@ static const struct lws_extension exts[] = {
 };
 
 /*
- * mount a handler for a section of the URL space
+ * mount handlers for sections of the URL space
  */
 
+static const struct lws_http_mount mount_ziptest = {
+	NULL,			/* linked-list pointer to next*/
+	"/ziptest",		/* mountpoint in URL namespace on this vhost */
+	LOCAL_RESOURCE_PATH"/candide.zip",	/* handler */
+	NULL,	/* default filename if none given */
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	LWSMPRO_FILE,	/* origin points to a callback */
+	8,			/* strlen("/ziptest"), ie length of the mountpoint */
+	NULL,
+
+	{ NULL, NULL } // sentinel
+};
+
 static const struct lws_http_mount mount_post = {
-	NULL,		/* linked-list pointer to next*/
+	(struct lws_http_mount *)&mount_ziptest, /* linked-list pointer to next*/
 	"/formtest",		/* mountpoint in URL namespace on this vhost */
 	"protocol-post-demo",	/* handler */
 	NULL,	/* default filename if none given */
diff --git a/test-server/test-server.c b/test-server/test-server.c
index 93fb116d74f13739e5dd4aba79442fb3c2d5b7c8..74c37da28776e2aeb76a2f80cb0d87375dba616d 100644
--- a/test-server/test-server.c
+++ b/test-server/test-server.c
@@ -122,17 +122,20 @@ static struct lws_protocols protocols[] = {
  */
 static lws_fop_fd_t
 test_server_fops_open(const struct lws_plat_file_ops *fops,
-		     const char *filename,
-		     lws_filepos_t *filelen,
+		     const char *vfs_path, const char *vpath,
 		     lws_fop_flags_t *flags)
 {
 	lws_fop_fd_t fop_fd;
 
 	/* call through to original platform implementation */
-	fop_fd = fops_plat.open(fops, filename, filelen, flags);
-
-	lwsl_info("%s: opening %s, ret %p, len %lu\n", __func__, filename,
-			fop_fd, (long)*filelen);
+	fop_fd = fops_plat.open(fops, vfs_path, vpath, flags);
+
+	if (fop_fd)
+		lwsl_info("%s: opening %s, ret %p, len %lu\n", __func__,
+				vfs_path, fop_fd,
+				(long)lws_vfs_get_length(fop_fd));
+	else
+		lwsl_info("%s: open %s failed\n", __func__, vfs_path);
 
 	return fop_fd;
 }