• 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 #define eff_size(_priv) (_priv->hdr.method == ZIP_COMPRESSION_METHOD_STORE ? \
148 				  _priv->hdr.uncomp_size : _priv->hdr.comp_size)
149 
150 static uint16_t
get_u16(void * p)151 get_u16(void *p)
152 {
153 	const uint8_t *c = (const uint8_t *)p;
154 
155 	return (uint16_t)((c[0] | (c[1] << 8)));
156 }
157 
158 static uint32_t
get_u32(void * p)159 get_u32(void *p)
160 {
161 	const uint8_t *c = (const uint8_t *)p;
162 
163 	return (uint32_t)((c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24)));
164 }
165 
166 int
lws_fops_zip_scan(lws_fops_zip_t priv,const char * name,int len)167 lws_fops_zip_scan(lws_fops_zip_t priv, const char *name, int len)
168 {
169 	lws_filepos_t amount;
170 	uint8_t buf[96];
171 	int i;
172 
173 	if (lws_vfs_file_seek_end(priv->zip_fop_fd, -ZE_DIRECTORY_LENGTH) < 0)
174 		return LWS_FZ_ERR_SEEK_END_RECORD;
175 
176 	if (lws_vfs_file_read(priv->zip_fop_fd, &amount, buf,
177 			      ZE_DIRECTORY_LENGTH))
178 		return LWS_FZ_ERR_READ_END_RECORD;
179 
180 	if (amount != ZE_DIRECTORY_LENGTH)
181 		return LWS_FZ_ERR_READ_END_RECORD;
182 
183 	/*
184 	 * We require the zip to have the last record right at the end
185 	 * Linux zip always does this if no zip comment.
186 	 */
187 	if (buf[0] != 'P' || buf[1] != 'K' || buf[2] != 5 || buf[3] != 6)
188 		return LWS_FZ_ERR_END_RECORD_MAGIC;
189 
190 	i = get_u16(buf + ZE_NUM_ENTRIES);
191 
192 	if (get_u16(buf + ZE_DESK_NUMBER) ||
193 	    get_u16(buf + ZE_CENTRAL_DIRECTORY_DISK_NUMBER) ||
194 	    i != get_u16(buf + ZE_NUM_ENTRIES_THIS_DISK))
195 		return LWS_FZ_ERR_END_RECORD_SANITY;
196 
197 	/* end record is OK... look for our file in the central dir */
198 
199 	if (lws_vfs_file_seek_set(priv->zip_fop_fd,
200 				  get_u32(buf + ZE_CENTRAL_DIR_OFFSET)) < 0)
201 		return LWS_FZ_ERR_CENTRAL_SEEK;
202 
203 	while (i--) {
204 		priv->content_start = lws_vfs_tell(priv->zip_fop_fd);
205 
206 		if (lws_vfs_file_read(priv->zip_fop_fd, &amount, buf,
207 				      ZC_DIRECTORY_LENGTH))
208 			return LWS_FZ_ERR_CENTRAL_READ;
209 
210 		if (amount != ZC_DIRECTORY_LENGTH)
211 			return LWS_FZ_ERR_CENTRAL_READ;
212 
213 		if (get_u32(buf + ZC_SIGNATURE) != 0x02014B50)
214 			return LWS_FZ_ERR_CENTRAL_SANITY;
215 
216                lwsl_debug("cstart 0x%lx\n", (unsigned long)priv->content_start);
217 
218 		priv->hdr.filename_len = get_u16(buf + ZC_FILE_NAME_LENGTH);
219 		priv->hdr.extra = get_u16(buf + ZC_EXTRA_FIELD_LENGTH);
220 		priv->hdr.filename_start = lws_vfs_tell(priv->zip_fop_fd);
221 
222 		priv->hdr.method = get_u16(buf + ZC_COMPRESSION_METHOD);
223 		priv->hdr.crc32 = get_u32(buf + ZC_CRC32);
224 		priv->hdr.comp_size = get_u32(buf + ZC_COMPRESSED_SIZE);
225 		priv->hdr.uncomp_size = get_u32(buf + ZC_UNCOMPRESSED_SIZE);
226 		priv->hdr.offset = get_u32(buf + ZC_REL_OFFSET_LOCAL_HEADER);
227 		priv->hdr.mod_time = get_u32(buf + ZC_LAST_MOD_FILE_TIME);
228 		priv->hdr.file_com_len = get_u16(buf + ZC_FILE_COMMENT_LENGTH);
229 
230 		if (priv->hdr.filename_len != len)
231 			goto next;
232 
233 		if (len >= (int)sizeof(buf) - 1)
234 			return LWS_FZ_ERR_NAME_TOO_LONG;
235 
236 		if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd,
237 							&amount, buf, (unsigned int)len))
238 			return LWS_FZ_ERR_NAME_READ;
239 		if ((int)amount != len)
240 			return LWS_FZ_ERR_NAME_READ;
241 
242 		buf[len] = '\0';
243 		lwsl_debug("check %s vs %s\n", buf, name);
244 
245 		if (strcmp((const char *)buf, name))
246 			goto next;
247 
248 		/* we found a match */
249 		if (lws_vfs_file_seek_set(priv->zip_fop_fd, priv->hdr.offset) < 0)
250 			return LWS_FZ_ERR_NAME_SEEK;
251 		if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd,
252 							&amount, buf,
253 							ZL_HEADER_LENGTH))
254 			return LWS_FZ_ERR_NAME_READ;
255 		if (amount != ZL_HEADER_LENGTH)
256 			return LWS_FZ_ERR_NAME_READ;
257 
258 		priv->content_start = priv->hdr.offset +
259 				      ZL_HEADER_LENGTH +
260 				      priv->hdr.filename_len +
261 				      get_u16(buf + ZL_REL_OFFSET_CONTENT);
262 
263 		lwsl_debug("content supposed to start at 0x%lx\n",
264                           (unsigned long)priv->content_start);
265 
266 		if (priv->content_start > priv->zip_fop_fd->len)
267 			return LWS_FZ_ERR_CONTENT_SANITY;
268 
269 		if (lws_vfs_file_seek_set(priv->zip_fop_fd,
270 					  (lws_fileofs_t)priv->content_start) < 0)
271 			return LWS_FZ_ERR_CONTENT_SEEK;
272 
273 		/* we are aligned at the start of the content */
274 
275 		priv->exp_uncomp_pos = 0;
276 
277 		return 0;
278 
279 next:
280 		if (i && lws_vfs_file_seek_set(priv->zip_fop_fd,
281 					       (lws_fileofs_t)priv->content_start +
282 					       (ZC_DIRECTORY_LENGTH +
283 					       priv->hdr.filename_len +
284 					       priv->hdr.extra +
285 					       priv->hdr.file_com_len)) < 0)
286 			return LWS_FZ_ERR_SCAN_SEEK;
287 	}
288 
289 	return LWS_FZ_ERR_NOT_FOUND;
290 }
291 
292 static int
lws_fops_zip_reset_inflate(lws_fops_zip_t priv)293 lws_fops_zip_reset_inflate(lws_fops_zip_t priv)
294 {
295 	if (priv->decompress)
296 		inflateEnd(&priv->inflate);
297 
298 	priv->inflate.zalloc = Z_NULL;
299 	priv->inflate.zfree = Z_NULL;
300 	priv->inflate.opaque = Z_NULL;
301 	priv->inflate.avail_in = 0;
302 	priv->inflate.next_in = Z_NULL;
303 
304 	if (inflateInit2(&priv->inflate, -MAX_WBITS) != Z_OK) {
305 		lwsl_err("inflate init failed\n");
306 		return LWS_FZ_ERR_ZLIB_INIT;
307 	}
308 
309 	if (lws_vfs_file_seek_set(priv->zip_fop_fd, (lws_fileofs_t)priv->content_start) < 0)
310 		return LWS_FZ_ERR_CONTENT_SEEK;
311 
312 	priv->exp_uncomp_pos = 0;
313 
314 	return 0;
315 }
316 
317 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)318 lws_fops_zip_open(const struct lws_plat_file_ops *fops, const char *vfs_path,
319 		  const char *vpath, lws_fop_flags_t *flags)
320 {
321 	lws_fop_flags_t local_flags = 0;
322 	lws_fops_zip_t priv;
323 	char rp[192];
324 	int m;
325 
326 	/*
327 	 * vpath points at the / after the fops signature in vfs_path, eg
328 	 * with a vfs_path "/var/www/docs/manual.zip/index.html", vpath
329 	 * will come pointing at "/index.html"
330 	 */
331 
332 	priv = lws_zalloc(sizeof(*priv), "fops_zip priv");
333 	if (!priv)
334 		return NULL;
335 
336 	priv->fop_fd.fops = &fops_zip;
337 
338 	m = sizeof(rp) - 1;
339 	if ((vpath - vfs_path - 1) < m)
340 		m = lws_ptr_diff(vpath, vfs_path) - 1;
341 	lws_strncpy(rp, vfs_path, (unsigned int)m + 1);
342 
343 	/* open the zip file itself using the incoming fops, not fops_zip */
344 
345 	priv->zip_fop_fd = fops->LWS_FOP_OPEN(fops, rp, NULL, &local_flags);
346 	if (!priv->zip_fop_fd) {
347 		lwsl_err("%s: unable to open zip %s\n", __func__, rp);
348 		goto bail1;
349 	}
350 
351 	if (*vpath == '/')
352 		vpath++;
353 
354 	m = lws_fops_zip_scan(priv, vpath, (int)strlen(vpath));
355 	if (m) {
356 		lwsl_err("unable to find record matching '%s' %d\n", vpath, m);
357 		goto bail2;
358 	}
359 
360 	/* the directory metadata tells us modification time, so pass it on */
361 	priv->fop_fd.mod_time = priv->hdr.mod_time;
362 	*flags |= LWS_FOP_FLAG_MOD_TIME_VALID | LWS_FOP_FLAG_VIRTUAL;
363 	priv->fop_fd.flags = *flags;
364 
365 	/* The zip fop_fd is left pointing at the start of the content.
366 	 *
367 	 * 1) Content could be uncompressed (STORE), and we can always serve
368 	 *    that directly
369 	 *
370 	 * 2) Content could be compressed (GZIP), and the client can handle
371 	 *    receiving GZIP... we can wrap it in a GZIP header and trailer
372 	 *    and serve the content part directly.  The flag indicating we
373 	 *    are providing GZIP directly is set so lws will send the right
374 	 *    headers.
375 	 *
376 	 * 3) Content could be compressed (GZIP) but the client can't handle
377 	 *    receiving GZIP... we can decompress it and serve as it is
378 	 *    inflated piecemeal.
379 	 *
380 	 * 4) Content may be compressed some unknown way... fail
381 	 *
382 	 */
383 	if (priv->hdr.method == ZIP_COMPRESSION_METHOD_STORE) {
384 		/*
385 		 * it is stored uncompressed, leave it indicated as
386 		 * uncompressed, and just serve it from inside the
387 		 * zip with no gzip container;
388 		 */
389 
390 		lwsl_info("direct zip serving (stored)\n");
391 
392 		priv->fop_fd.len = priv->hdr.uncomp_size;
393 
394 		return &priv->fop_fd;
395 	}
396 
397 	if ((*flags & LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP) &&
398 	    priv->hdr.method == ZIP_COMPRESSION_METHOD_DEFLATE) {
399 
400 		/*
401 		 * We can serve the gzipped file contents directly as gzip
402 		 * from inside the zip container; client says it is OK.
403 		 *
404 		 * To convert to standalone gzip, we have to add a 10-byte
405 		 * constant header and a variable 8-byte trailer around the
406 		 * content.
407 		 *
408 		 * The 8-byte trailer is prepared now and held in the priv.
409 		 */
410 
411 		lwsl_info("direct zip serving (gzipped)\n");
412 
413 		priv->fop_fd.len = sizeof(hd) + priv->hdr.comp_size +
414 				   sizeof(priv->u);
415 
416 		if (lws_is_be()) {
417 			uint8_t *p = priv->u.trailer8;
418 
419 			*p++ = (uint8_t)priv->hdr.crc32;
420 			*p++ = (uint8_t)(priv->hdr.crc32 >> 8);
421 			*p++ = (uint8_t)(priv->hdr.crc32 >> 16);
422 			*p++ = (uint8_t)(priv->hdr.crc32 >> 24);
423 			*p++ = (uint8_t)priv->hdr.uncomp_size;
424 			*p++ = (uint8_t)(priv->hdr.uncomp_size >> 8);
425 			*p++ = (uint8_t)(priv->hdr.uncomp_size >> 16);
426 			*p   = (uint8_t)(priv->hdr.uncomp_size >> 24);
427 		} else {
428 			priv->u.trailer32[0] = priv->hdr.crc32;
429 			priv->u.trailer32[1] = priv->hdr.uncomp_size;
430 		}
431 
432 		*flags |= LWS_FOP_FLAG_COMPR_IS_GZIP;
433 		priv->fop_fd.flags = *flags;
434 		priv->add_gzip_container = 1;
435 
436 		return &priv->fop_fd;
437 	}
438 
439 	if (priv->hdr.method == ZIP_COMPRESSION_METHOD_DEFLATE) {
440 
441 		/* we must decompress it to serve it */
442 
443 		lwsl_info("decompressed zip serving\n");
444 
445 		priv->fop_fd.len = priv->hdr.uncomp_size;
446 
447 		if (lws_fops_zip_reset_inflate(priv)) {
448 			lwsl_err("inflate init failed\n");
449 			goto bail2;
450 		}
451 
452 		priv->decompress = 1;
453 
454 		return &priv->fop_fd;
455 	}
456 
457 	/* we can't handle it ... */
458 
459 	lwsl_err("zipped file %s compressed in unknown way (%d)\n", vfs_path,
460 		 priv->hdr.method);
461 
462 bail2:
463 	lws_vfs_file_close(&priv->zip_fop_fd);
464 bail1:
465 	free(priv);
466 
467 	return NULL;
468 }
469 
470 /* ie, we are closing the fop_fd for the file inside the gzip */
471 
472 static int
lws_fops_zip_close(lws_fop_fd_t * fd)473 lws_fops_zip_close(lws_fop_fd_t *fd)
474 {
475 	lws_fops_zip_t priv = fop_fd_to_priv(*fd);
476 
477 	if (priv->decompress)
478 		inflateEnd(&priv->inflate);
479 
480 	lws_vfs_file_close(&priv->zip_fop_fd); /* close the gzip fop_fd */
481 
482 	free(priv);
483 	*fd = NULL;
484 
485 	return 0;
486 }
487 
488 static lws_fileofs_t
lws_fops_zip_seek_cur(lws_fop_fd_t fd,lws_fileofs_t offset_from_cur_pos)489 lws_fops_zip_seek_cur(lws_fop_fd_t fd, lws_fileofs_t offset_from_cur_pos)
490 {
491 	fd->pos = (lws_filepos_t)((lws_fileofs_t)fd->pos + offset_from_cur_pos);
492 
493 	return (lws_fileofs_t)fd->pos;
494 }
495 
496 static int
lws_fops_zip_read(lws_fop_fd_t fd,lws_filepos_t * amount,uint8_t * buf,lws_filepos_t len)497 lws_fops_zip_read(lws_fop_fd_t fd, lws_filepos_t *amount, uint8_t *buf,
498 		  lws_filepos_t len)
499 {
500 	lws_fops_zip_t priv = fop_fd_to_priv(fd);
501 	lws_filepos_t ramount, rlen, cur = lws_vfs_tell(fd);
502 	int ret;
503 
504 	if (priv->decompress) {
505 
506 		if (priv->exp_uncomp_pos != fd->pos) {
507 			/*
508 			 *  there has been a seek in the uncompressed fop_fd
509 			 * we have to restart the decompression and loop eating
510 			 * the decompressed data up to the seek point
511 			 */
512 			lwsl_info("seek in decompressed\n");
513 
514 			lws_fops_zip_reset_inflate(priv);
515 
516 			while (priv->exp_uncomp_pos != fd->pos) {
517 				rlen = len;
518 				if (rlen > fd->pos - priv->exp_uncomp_pos)
519 					rlen = fd->pos - priv->exp_uncomp_pos;
520 				if (lws_fops_zip_read(fd, amount, buf, rlen))
521 					return LWS_FZ_ERR_SEEK_COMPRESSED;
522 			}
523 			*amount = 0;
524 		}
525 
526 		priv->inflate.avail_out = (unsigned int)len;
527 		priv->inflate.next_out = buf;
528 
529 spin:
530 		if (!priv->inflate.avail_in) {
531 			rlen = sizeof(priv->rbuf);
532 			if (rlen > eff_size(priv) - (cur - priv->content_start))
533 				rlen = eff_size(priv) - (cur - 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 > eff_size(priv) - cur)
636 		len = eff_size(priv) - cur;
637 
638 	if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd,
639 						 amount, buf, len))
640 		return LWS_FZ_ERR_READ_CONTENT;
641 
642 	fd->pos += *amount;
643 
644 	return 0;
645 }
646 
647 struct lws_plat_file_ops fops_zip = {
648 	lws_fops_zip_open,
649 	lws_fops_zip_close,
650 	lws_fops_zip_seek_cur,
651 	lws_fops_zip_read,
652 	NULL,
653 	{ { ".zip/", 5 }, { ".jar/", 5 }, { ".war/", 5 } },
654 	NULL,
655 };
656