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
56typedef 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
69typedef 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
89struct lws_plat_file_ops fops_zip;
90#define fop_fd_to_priv(FD) ((lws_fops_zip_t)(FD))
91
92static const uint8_t hd[] = { 31, 139, 8, 0, 0, 0, 0, 0, 0, 3 };
93
94enum {
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
150static uint16_t
151get_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
158static uint32_t
159get_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
166int
167lws_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
279next:
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
292static int
293lws_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
317static lws_fop_fd_t
318lws_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
462bail2:
463	lws_vfs_file_close(&priv->zip_fop_fd);
464bail1:
465	free(priv);
466
467	return NULL;
468}
469
470/* ie, we are closing the fop_fd for the file inside the gzip */
471
472static int
473lws_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
488static lws_fileofs_t
489lws_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
496static int
497lws_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
529spin:
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
647struct 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