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