• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * libwebsockets - small server side websockets and web server implementation
3  *
4  * Original code used in this source file:
5  *
6  * https://github.com/PerBothner/DomTerm.git @912add15f3d0aec
7  *
8  * ./lws-term/io.c
9  * ./lws-term/junzip.c
10  *
11  * Copyright (C) 2017  Per Bothner <per@bothner.com>
12  *
13  * MIT License
14  *
15  * Permission is hereby granted, free of charge, to any person obtaining a copy
16  * of this software and associated documentation files (the "Software"), to deal
17  * in the Software without restriction, including without limitation the rights
18  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19  * ( copies of the Software, and to permit persons to whom the Software is
20  * furnished to do so, subject to the following conditions:
21  *
22  * The above copyright notice and this permission notice shall be included in
23  * all copies or substantial portions of the Software.
24  *
25  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31  * SOFTWARE.
32  *
33  * Somewhat rewritten by AG
34  */
35 
36 #include "private-lib-core.h"
37 
38 #if defined(LWS_WITH_MINIZ)
39 #include <miniz.h>
40 #else
41 #include <zlib.h>
42 #endif
43 
44 /*
45  * This code works with zip format containers which may have files compressed
46  * with gzip deflate (type 8) or store uncompressed (type 0).
47  *
48  * Linux zip produces such zipfiles by default, eg
49  *
50  *  $ zip ../myzip.zip file1 file2 file3
51  */
52 
53 #define ZIP_COMPRESSION_METHOD_STORE 0
54 #define ZIP_COMPRESSION_METHOD_DEFLATE 8
55 
56 typedef struct {
57 	lws_filepos_t		filename_start;
58 	uint32_t		crc32;
59 	uint32_t		comp_size;
60 	uint32_t		uncomp_size;
61 	uint32_t		offset;
62 	uint32_t		mod_time;
63 	uint16_t		filename_len;
64 	uint16_t		extra;
65 	uint16_t		method;
66 	uint16_t		file_com_len;
67 } lws_fops_zip_hdr_t;
68 
69 typedef struct {
70 	struct lws_fop_fd	fop_fd; /* MUST BE FIRST logical fop_fd into
71 	 	 	 	 	 * file inside zip: fops_zip fops */
72 	lws_fop_fd_t		zip_fop_fd; /* logical fop fd on to zip file
73 	 	 	 	 	     * itself: using platform fops */
74 	lws_fops_zip_hdr_t	hdr;
75 	z_stream		inflate;
76 	lws_filepos_t		content_start;
77 	lws_filepos_t		exp_uncomp_pos;
78 	union {
79 		uint8_t		trailer8[8];
80 		uint32_t	trailer32[2];
81 	} u;
82 	uint8_t			rbuf[128]; /* decompression chunk size */
83 	int			entry_count;
84 
85 	unsigned int		decompress:1; /* 0 = direct from file */
86 	unsigned int		add_gzip_container:1;
87 } *lws_fops_zip_t;
88 
89 struct lws_plat_file_ops fops_zip;
90 #define fop_fd_to_priv(FD) ((lws_fops_zip_t)(FD))
91 
92 static const uint8_t hd[] = { 31, 139, 8, 0, 0, 0, 0, 0, 0, 3 };
93 
94 enum {
95 	ZC_SIGNATURE				= 0,
96 	ZC_VERSION_MADE_BY 			= 4,
97 	ZC_VERSION_NEEDED_TO_EXTRACT 		= 6,
98 	ZC_GENERAL_PURPOSE_BIT_FLAG 		= 8,
99 	ZC_COMPRESSION_METHOD 			= 10,
100 	ZC_LAST_MOD_FILE_TIME 			= 12,
101 	ZC_LAST_MOD_FILE_DATE 			= 14,
102 	ZC_CRC32 				= 16,
103 	ZC_COMPRESSED_SIZE 			= 20,
104 	ZC_UNCOMPRESSED_SIZE 			= 24,
105 	ZC_FILE_NAME_LENGTH 			= 28,
106 	ZC_EXTRA_FIELD_LENGTH 			= 30,
107 
108 	ZC_FILE_COMMENT_LENGTH 			= 32,
109 	ZC_DISK_NUMBER_START 			= 34,
110 	ZC_INTERNAL_FILE_ATTRIBUTES 		= 36,
111 	ZC_EXTERNAL_FILE_ATTRIBUTES 		= 38,
112 	ZC_REL_OFFSET_LOCAL_HEADER 		= 42,
113 	ZC_DIRECTORY_LENGTH 			= 46,
114 
115 	ZE_SIGNATURE_OFFSET 			= 0,
116 	ZE_DESK_NUMBER 				= 4,
117 	ZE_CENTRAL_DIRECTORY_DISK_NUMBER 	= 6,
118 	ZE_NUM_ENTRIES_THIS_DISK 		= 8,
119 	ZE_NUM_ENTRIES 				= 10,
120 	ZE_CENTRAL_DIRECTORY_SIZE 		= 12,
121 	ZE_CENTRAL_DIR_OFFSET 			= 16,
122 	ZE_ZIP_COMMENT_LENGTH 			= 20,
123 	ZE_DIRECTORY_LENGTH 			= 22,
124 
125 	ZL_REL_OFFSET_CONTENT			= 28,
126 	ZL_HEADER_LENGTH			= 30,
127 
128 	LWS_FZ_ERR_SEEK_END_RECORD		= 1,
129 	LWS_FZ_ERR_READ_END_RECORD,
130 	LWS_FZ_ERR_END_RECORD_MAGIC,
131 	LWS_FZ_ERR_END_RECORD_SANITY,
132 	LWS_FZ_ERR_CENTRAL_SEEK,
133 	LWS_FZ_ERR_CENTRAL_READ,
134 	LWS_FZ_ERR_CENTRAL_SANITY,
135 	LWS_FZ_ERR_NAME_TOO_LONG,
136 	LWS_FZ_ERR_NAME_SEEK,
137 	LWS_FZ_ERR_NAME_READ,
138 	LWS_FZ_ERR_CONTENT_SANITY,
139 	LWS_FZ_ERR_CONTENT_SEEK,
140 	LWS_FZ_ERR_SCAN_SEEK,
141 	LWS_FZ_ERR_NOT_FOUND,
142 	LWS_FZ_ERR_ZLIB_INIT,
143 	LWS_FZ_ERR_READ_CONTENT,
144 	LWS_FZ_ERR_SEEK_COMPRESSED,
145 };
146 
147 static uint16_t
get_u16(void * p)148 get_u16(void *p)
149 {
150 	const uint8_t *c = (const uint8_t *)p;
151 
152 	return (uint16_t)((c[0] | (c[1] << 8)));
153 }
154 
155 static uint32_t
get_u32(void * p)156 get_u32(void *p)
157 {
158 	const uint8_t *c = (const uint8_t *)p;
159 
160 	return (uint32_t)((c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24)));
161 }
162 
163 int
lws_fops_zip_scan(lws_fops_zip_t priv,const char * name,int len)164 lws_fops_zip_scan(lws_fops_zip_t priv, const char *name, int len)
165 {
166 	lws_filepos_t amount;
167 	uint8_t buf[96];
168 	int i;
169 
170 	if (lws_vfs_file_seek_end(priv->zip_fop_fd, -ZE_DIRECTORY_LENGTH) < 0)
171 		return LWS_FZ_ERR_SEEK_END_RECORD;
172 
173 	if (lws_vfs_file_read(priv->zip_fop_fd, &amount, buf,
174 			      ZE_DIRECTORY_LENGTH))
175 		return LWS_FZ_ERR_READ_END_RECORD;
176 
177 	if (amount != ZE_DIRECTORY_LENGTH)
178 		return LWS_FZ_ERR_READ_END_RECORD;
179 
180 	/*
181 	 * We require the zip to have the last record right at the end
182 	 * Linux zip always does this if no zip comment.
183 	 */
184 	if (buf[0] != 'P' || buf[1] != 'K' || buf[2] != 5 || buf[3] != 6)
185 		return LWS_FZ_ERR_END_RECORD_MAGIC;
186 
187 	i = get_u16(buf + ZE_NUM_ENTRIES);
188 
189 	if (get_u16(buf + ZE_DESK_NUMBER) ||
190 	    get_u16(buf + ZE_CENTRAL_DIRECTORY_DISK_NUMBER) ||
191 	    i != get_u16(buf + ZE_NUM_ENTRIES_THIS_DISK))
192 		return LWS_FZ_ERR_END_RECORD_SANITY;
193 
194 	/* end record is OK... look for our file in the central dir */
195 
196 	if (lws_vfs_file_seek_set(priv->zip_fop_fd,
197 				  get_u32(buf + ZE_CENTRAL_DIR_OFFSET)) < 0)
198 		return LWS_FZ_ERR_CENTRAL_SEEK;
199 
200 	while (i--) {
201 		priv->content_start = lws_vfs_tell(priv->zip_fop_fd);
202 
203 		if (lws_vfs_file_read(priv->zip_fop_fd, &amount, buf,
204 				      ZC_DIRECTORY_LENGTH))
205 			return LWS_FZ_ERR_CENTRAL_READ;
206 
207 		if (amount != ZC_DIRECTORY_LENGTH)
208 			return LWS_FZ_ERR_CENTRAL_READ;
209 
210 		if (get_u32(buf + ZC_SIGNATURE) != 0x02014B50)
211 			return LWS_FZ_ERR_CENTRAL_SANITY;
212 
213                lwsl_debug("cstart 0x%lx\n", (unsigned long)priv->content_start);
214 
215 		priv->hdr.filename_len = get_u16(buf + ZC_FILE_NAME_LENGTH);
216 		priv->hdr.extra = get_u16(buf + ZC_EXTRA_FIELD_LENGTH);
217 		priv->hdr.filename_start = lws_vfs_tell(priv->zip_fop_fd);
218 
219 		priv->hdr.method = get_u16(buf + ZC_COMPRESSION_METHOD);
220 		priv->hdr.crc32 = get_u32(buf + ZC_CRC32);
221 		priv->hdr.comp_size = get_u32(buf + ZC_COMPRESSED_SIZE);
222 		priv->hdr.uncomp_size = get_u32(buf + ZC_UNCOMPRESSED_SIZE);
223 		priv->hdr.offset = get_u32(buf + ZC_REL_OFFSET_LOCAL_HEADER);
224 		priv->hdr.mod_time = get_u32(buf + ZC_LAST_MOD_FILE_TIME);
225 		priv->hdr.file_com_len = get_u16(buf + ZC_FILE_COMMENT_LENGTH);
226 
227 		if (priv->hdr.filename_len != len)
228 			goto next;
229 
230 		if (len >= (int)sizeof(buf) - 1)
231 			return LWS_FZ_ERR_NAME_TOO_LONG;
232 
233 		if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd,
234 							&amount, buf, len))
235 			return LWS_FZ_ERR_NAME_READ;
236 		if ((int)amount != len)
237 			return LWS_FZ_ERR_NAME_READ;
238 
239 		buf[len] = '\0';
240 		lwsl_debug("check %s vs %s\n", buf, name);
241 
242 		if (strcmp((const char *)buf, name))
243 			goto next;
244 
245 		/* we found a match */
246 		if (lws_vfs_file_seek_set(priv->zip_fop_fd, priv->hdr.offset) < 0)
247 			return LWS_FZ_ERR_NAME_SEEK;
248 		if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd,
249 							&amount, buf,
250 							ZL_HEADER_LENGTH))
251 			return LWS_FZ_ERR_NAME_READ;
252 		if (amount != ZL_HEADER_LENGTH)
253 			return LWS_FZ_ERR_NAME_READ;
254 
255 		priv->content_start = priv->hdr.offset +
256 				      ZL_HEADER_LENGTH +
257 				      priv->hdr.filename_len +
258 				      get_u16(buf + ZL_REL_OFFSET_CONTENT);
259 
260 		lwsl_debug("content supposed to start at 0x%lx\n",
261                           (unsigned long)priv->content_start);
262 
263 		if (priv->content_start > priv->zip_fop_fd->len)
264 			return LWS_FZ_ERR_CONTENT_SANITY;
265 
266 		if (lws_vfs_file_seek_set(priv->zip_fop_fd,
267 					  priv->content_start) < 0)
268 			return LWS_FZ_ERR_CONTENT_SEEK;
269 
270 		/* we are aligned at the start of the content */
271 
272 		priv->exp_uncomp_pos = 0;
273 
274 		return 0;
275 
276 next:
277 		if (i && lws_vfs_file_seek_set(priv->zip_fop_fd,
278 					       priv->content_start +
279 					       ZC_DIRECTORY_LENGTH +
280 					       priv->hdr.filename_len +
281 					       priv->hdr.extra +
282 					       priv->hdr.file_com_len) < 0)
283 			return LWS_FZ_ERR_SCAN_SEEK;
284 	}
285 
286 	return LWS_FZ_ERR_NOT_FOUND;
287 }
288 
289 static int
lws_fops_zip_reset_inflate(lws_fops_zip_t priv)290 lws_fops_zip_reset_inflate(lws_fops_zip_t priv)
291 {
292 	if (priv->decompress)
293 		inflateEnd(&priv->inflate);
294 
295 	priv->inflate.zalloc = Z_NULL;
296 	priv->inflate.zfree = Z_NULL;
297 	priv->inflate.opaque = Z_NULL;
298 	priv->inflate.avail_in = 0;
299 	priv->inflate.next_in = Z_NULL;
300 
301 	if (inflateInit2(&priv->inflate, -MAX_WBITS) != Z_OK) {
302 		lwsl_err("inflate init failed\n");
303 		return LWS_FZ_ERR_ZLIB_INIT;
304 	}
305 
306 	if (lws_vfs_file_seek_set(priv->zip_fop_fd, priv->content_start) < 0)
307 		return LWS_FZ_ERR_CONTENT_SEEK;
308 
309 	priv->exp_uncomp_pos = 0;
310 
311 	return 0;
312 }
313 
314 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)315 lws_fops_zip_open(const struct lws_plat_file_ops *fops, const char *vfs_path,
316 		  const char *vpath, lws_fop_flags_t *flags)
317 {
318 	lws_fop_flags_t local_flags = 0;
319 	lws_fops_zip_t priv;
320 	char rp[192];
321 	int m;
322 
323 	/*
324 	 * vpath points at the / after the fops signature in vfs_path, eg
325 	 * with a vfs_path "/var/www/docs/manual.zip/index.html", vpath
326 	 * will come pointing at "/index.html"
327 	 */
328 
329 	priv = lws_zalloc(sizeof(*priv), "fops_zip priv");
330 	if (!priv)
331 		return NULL;
332 
333 	priv->fop_fd.fops = &fops_zip;
334 
335 	m = sizeof(rp) - 1;
336 	if ((vpath - vfs_path - 1) < m)
337 		m = lws_ptr_diff(vpath, vfs_path) - 1;
338 	lws_strncpy(rp, vfs_path, m + 1);
339 
340 	/* open the zip file itself using the incoming fops, not fops_zip */
341 
342 	priv->zip_fop_fd = fops->LWS_FOP_OPEN(fops, rp, NULL, &local_flags);
343 	if (!priv->zip_fop_fd) {
344 		lwsl_err("unable to open zip %s\n", rp);
345 		goto bail1;
346 	}
347 
348 	if (*vpath == '/')
349 		vpath++;
350 
351 	m = lws_fops_zip_scan(priv, vpath, (int)strlen(vpath));
352 	if (m) {
353 		lwsl_err("unable to find record matching '%s' %d\n", vpath, m);
354 		goto bail2;
355 	}
356 
357 	/* the directory metadata tells us modification time, so pass it on */
358 	priv->fop_fd.mod_time = priv->hdr.mod_time;
359 	*flags |= LWS_FOP_FLAG_MOD_TIME_VALID | LWS_FOP_FLAG_VIRTUAL;
360 	priv->fop_fd.flags = *flags;
361 
362 	/* The zip fop_fd is left pointing at the start of the content.
363 	 *
364 	 * 1) Content could be uncompressed (STORE), and we can always serve
365 	 *    that directly
366 	 *
367 	 * 2) Content could be compressed (GZIP), and the client can handle
368 	 *    receiving GZIP... we can wrap it in a GZIP header and trailer
369 	 *    and serve the content part directly.  The flag indicating we
370 	 *    are providing GZIP directly is set so lws will send the right
371 	 *    headers.
372 	 *
373 	 * 3) Content could be compressed (GZIP) but the client can't handle
374 	 *    receiving GZIP... we can decompress it and serve as it is
375 	 *    inflated piecemeal.
376 	 *
377 	 * 4) Content may be compressed some unknown way... fail
378 	 *
379 	 */
380 	if (priv->hdr.method == ZIP_COMPRESSION_METHOD_STORE) {
381 		/*
382 		 * it is stored uncompressed, leave it indicated as
383 		 * uncompressed, and just serve it from inside the
384 		 * zip with no gzip container;
385 		 */
386 
387 		lwsl_info("direct zip serving (stored)\n");
388 
389 		priv->fop_fd.len = priv->hdr.uncomp_size;
390 
391 		return &priv->fop_fd;
392 	}
393 
394 	if ((*flags & LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP) &&
395 	    priv->hdr.method == ZIP_COMPRESSION_METHOD_DEFLATE) {
396 
397 		/*
398 		 * We can serve the gzipped file contents directly as gzip
399 		 * from inside the zip container; client says it is OK.
400 		 *
401 		 * To convert to standalone gzip, we have to add a 10-byte
402 		 * constant header and a variable 8-byte trailer around the
403 		 * content.
404 		 *
405 		 * The 8-byte trailer is prepared now and held in the priv.
406 		 */
407 
408 		lwsl_info("direct zip serving (gzipped)\n");
409 
410 		priv->fop_fd.len = sizeof(hd) + priv->hdr.comp_size +
411 				   sizeof(priv->u);
412 
413 		if (lws_is_be()) {
414 			uint8_t *p = priv->u.trailer8;
415 
416 			*p++ = (uint8_t)priv->hdr.crc32;
417 			*p++ = (uint8_t)(priv->hdr.crc32 >> 8);
418 			*p++ = (uint8_t)(priv->hdr.crc32 >> 16);
419 			*p++ = (uint8_t)(priv->hdr.crc32 >> 24);
420 			*p++ = (uint8_t)priv->hdr.uncomp_size;
421 			*p++ = (uint8_t)(priv->hdr.uncomp_size >> 8);
422 			*p++ = (uint8_t)(priv->hdr.uncomp_size >> 16);
423 			*p   = (uint8_t)(priv->hdr.uncomp_size >> 24);
424 		} else {
425 			priv->u.trailer32[0] = priv->hdr.crc32;
426 			priv->u.trailer32[1] = priv->hdr.uncomp_size;
427 		}
428 
429 		*flags |= LWS_FOP_FLAG_COMPR_IS_GZIP;
430 		priv->fop_fd.flags = *flags;
431 		priv->add_gzip_container = 1;
432 
433 		return &priv->fop_fd;
434 	}
435 
436 	if (priv->hdr.method == ZIP_COMPRESSION_METHOD_DEFLATE) {
437 
438 		/* we must decompress it to serve it */
439 
440 		lwsl_info("decompressed zip serving\n");
441 
442 		priv->fop_fd.len = priv->hdr.uncomp_size;
443 
444 		if (lws_fops_zip_reset_inflate(priv)) {
445 			lwsl_err("inflate init failed\n");
446 			goto bail2;
447 		}
448 
449 		priv->decompress = 1;
450 
451 		return &priv->fop_fd;
452 	}
453 
454 	/* we can't handle it ... */
455 
456 	lwsl_err("zipped file %s compressed in unknown way (%d)\n", vfs_path,
457 		 priv->hdr.method);
458 
459 bail2:
460 	lws_vfs_file_close(&priv->zip_fop_fd);
461 bail1:
462 	free(priv);
463 
464 	return NULL;
465 }
466 
467 /* ie, we are closing the fop_fd for the file inside the gzip */
468 
469 static int
lws_fops_zip_close(lws_fop_fd_t * fd)470 lws_fops_zip_close(lws_fop_fd_t *fd)
471 {
472 	lws_fops_zip_t priv = fop_fd_to_priv(*fd);
473 
474 	if (priv->decompress)
475 		inflateEnd(&priv->inflate);
476 
477 	lws_vfs_file_close(&priv->zip_fop_fd); /* close the gzip fop_fd */
478 
479 	free(priv);
480 	*fd = NULL;
481 
482 	return 0;
483 }
484 
485 static lws_fileofs_t
lws_fops_zip_seek_cur(lws_fop_fd_t fd,lws_fileofs_t offset_from_cur_pos)486 lws_fops_zip_seek_cur(lws_fop_fd_t fd, lws_fileofs_t offset_from_cur_pos)
487 {
488 	fd->pos += offset_from_cur_pos;
489 
490 	return fd->pos;
491 }
492 
493 static int
lws_fops_zip_read(lws_fop_fd_t fd,lws_filepos_t * amount,uint8_t * buf,lws_filepos_t len)494 lws_fops_zip_read(lws_fop_fd_t fd, lws_filepos_t *amount, uint8_t *buf,
495 		  lws_filepos_t len)
496 {
497 	lws_fops_zip_t priv = fop_fd_to_priv(fd);
498 	lws_filepos_t ramount, rlen, cur = lws_vfs_tell(fd);
499 	int ret;
500 
501 	if (priv->decompress) {
502 
503 		if (priv->exp_uncomp_pos != fd->pos) {
504 			/*
505 			 *  there has been a seek in the uncompressed fop_fd
506 			 * we have to restart the decompression and loop eating
507 			 * the decompressed data up to the seek point
508 			 */
509 			lwsl_info("seek in decompressed\n");
510 
511 			lws_fops_zip_reset_inflate(priv);
512 
513 			while (priv->exp_uncomp_pos != fd->pos) {
514 				rlen = len;
515 				if (rlen > fd->pos - priv->exp_uncomp_pos)
516 					rlen = fd->pos - priv->exp_uncomp_pos;
517 				if (lws_fops_zip_read(fd, amount, buf, rlen))
518 					return LWS_FZ_ERR_SEEK_COMPRESSED;
519 			}
520 			*amount = 0;
521 		}
522 
523 		priv->inflate.avail_out = (unsigned int)len;
524 		priv->inflate.next_out = buf;
525 
526 spin:
527 		if (!priv->inflate.avail_in) {
528 			rlen = sizeof(priv->rbuf);
529 			if (rlen > priv->hdr.comp_size -
530 				   (cur - priv->content_start))
531 				rlen = priv->hdr.comp_size -
532 				       (priv->hdr.comp_size -
533 					priv->content_start);
534 
535 			if (priv->zip_fop_fd->fops->LWS_FOP_READ(
536 					priv->zip_fop_fd, &ramount, priv->rbuf,
537 					rlen))
538 				return LWS_FZ_ERR_READ_CONTENT;
539 
540 			cur += ramount;
541 
542 			priv->inflate.avail_in = (unsigned int)ramount;
543 			priv->inflate.next_in = priv->rbuf;
544 		}
545 
546 		ret = inflate(&priv->inflate, Z_NO_FLUSH);
547 		if (ret == Z_STREAM_ERROR)
548 			return ret;
549 
550 		switch (ret) {
551 		case Z_NEED_DICT:
552 			ret = Z_DATA_ERROR;
553 			/* fallthru */
554 		case Z_DATA_ERROR:
555 		case Z_MEM_ERROR:
556 
557 			return ret;
558 		}
559 
560 		if (!priv->inflate.avail_in && priv->inflate.avail_out &&
561 		     cur != priv->content_start + priv->hdr.comp_size)
562 			goto spin;
563 
564 		*amount = len - priv->inflate.avail_out;
565 
566 		priv->exp_uncomp_pos += *amount;
567 		fd->pos += *amount;
568 
569 		return 0;
570 	}
571 
572 	if (priv->add_gzip_container) {
573 
574 		lwsl_info("%s: gzip + container\n", __func__);
575 		*amount = 0;
576 
577 		/* place the canned header at the start */
578 
579 		if (len && fd->pos < sizeof(hd)) {
580 			rlen = sizeof(hd) - fd->pos;
581 			if (rlen > len)
582 				rlen = len;
583 			/* provide stuff from canned header */
584 			memcpy(buf, hd + fd->pos, (size_t)rlen);
585 			fd->pos += rlen;
586 			buf += rlen;
587 			len -= rlen;
588 			*amount += rlen;
589 		}
590 
591 		/* serve gzipped data direct from zipfile */
592 
593 		if (len && fd->pos >= sizeof(hd) &&
594 		    fd->pos < priv->hdr.comp_size + sizeof(hd)) {
595 
596 			rlen = priv->hdr.comp_size - (priv->zip_fop_fd->pos -
597 						      priv->content_start);
598 			if (rlen > len)
599 				rlen = len;
600 
601 			if (rlen &&
602 			    priv->zip_fop_fd->pos < (priv->hdr.comp_size +
603 					    	     priv->content_start)) {
604 				if (lws_vfs_file_read(priv->zip_fop_fd,
605 						      &ramount, buf, rlen))
606 					return LWS_FZ_ERR_READ_CONTENT;
607 				*amount += ramount;
608 				fd->pos += ramount; // virtual pos
609 				buf += ramount;
610 				len -= ramount;
611 			}
612 		}
613 
614 		/* place the prepared trailer at the end */
615 
616 		if (len && fd->pos >= priv->hdr.comp_size + sizeof(hd) &&
617 		    fd->pos < priv->hdr.comp_size + sizeof(hd) +
618 		    	      sizeof(priv->u)) {
619 			cur = fd->pos - priv->hdr.comp_size - sizeof(hd);
620 			rlen = sizeof(priv->u) - cur;
621 			if (rlen > len)
622 				rlen = len;
623 
624 			memcpy(buf, priv->u.trailer8 + cur, (size_t)rlen);
625 
626 			*amount += rlen;
627 			fd->pos += rlen;
628 		}
629 
630 		return 0;
631 	}
632 
633 	lwsl_info("%s: store\n", __func__);
634 
635 	if (len > priv->hdr.uncomp_size - (cur - priv->content_start))
636 		len = priv->hdr.comp_size - (priv->hdr.comp_size -
637 					     priv->content_start);
638 
639 	if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd,
640 						 amount, buf, len))
641 		return LWS_FZ_ERR_READ_CONTENT;
642 
643 	return 0;
644 }
645 
646 struct lws_plat_file_ops fops_zip = {
647 	lws_fops_zip_open,
648 	lws_fops_zip_close,
649 	lws_fops_zip_seek_cur,
650 	lws_fops_zip_read,
651 	NULL,
652 	{ { ".zip/", 5 }, { ".jar/", 5 }, { ".war/", 5 } },
653 	NULL,
654 };
655