2 * libwebsockets - small server side websockets and web server implementation
4 * Original code used in this source file:
6 * https://github.com/PerBothner/DomTerm.git @912add15f3d0aec
11 * Copyright (C) 2017 Per Bothner <per@bothner.com>
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:
22 * The above copyright notice and this permission notice shall be included in
23 * all copies or substantial portions of the Software.
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
36 * Copyright (C) 2017 Andy Green <andy@warmcat.com>
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.
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.
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,
54 #include "core/private.h"
56 #if defined(LWS_WITH_MINIZ)
63 * This code works with zip format containers which may have files compressed
64 * with gzip deflate (type 8) or store uncompressed (type 0).
66 * Linux zip produces such zipfiles by default, eg
68 * $ zip ../myzip.zip file1 file2 file3
71 #define ZIP_COMPRESSION_METHOD_STORE 0
72 #define ZIP_COMPRESSION_METHOD_DEFLATE 8
75 lws_filepos_t filename_start;
81 uint16_t filename_len;
84 uint16_t file_com_len;
88 struct lws_fop_fd fop_fd; /* MUST BE FIRST logical fop_fd into
89 * file inside zip: fops_zip fops */
90 lws_fop_fd_t zip_fop_fd; /* logical fop fd on to zip file
91 * itself: using platform fops */
92 lws_fops_zip_hdr_t hdr;
94 lws_filepos_t content_start;
95 lws_filepos_t exp_uncomp_pos;
98 uint32_t trailer32[2];
100 uint8_t rbuf[128]; /* decompression chunk size */
103 unsigned int decompress:1; /* 0 = direct from file */
104 unsigned int add_gzip_container:1;
107 struct lws_plat_file_ops fops_zip;
108 #define fop_fd_to_priv(FD) ((lws_fops_zip_t)(FD))
110 static const uint8_t hd[] = { 31, 139, 8, 0, 0, 0, 0, 0, 0, 3 };
114 ZC_VERSION_MADE_BY = 4,
115 ZC_VERSION_NEEDED_TO_EXTRACT = 6,
116 ZC_GENERAL_PURPOSE_BIT_FLAG = 8,
117 ZC_COMPRESSION_METHOD = 10,
118 ZC_LAST_MOD_FILE_TIME = 12,
119 ZC_LAST_MOD_FILE_DATE = 14,
121 ZC_COMPRESSED_SIZE = 20,
122 ZC_UNCOMPRESSED_SIZE = 24,
123 ZC_FILE_NAME_LENGTH = 28,
124 ZC_EXTRA_FIELD_LENGTH = 30,
126 ZC_FILE_COMMENT_LENGTH = 32,
127 ZC_DISK_NUMBER_START = 34,
128 ZC_INTERNAL_FILE_ATTRIBUTES = 36,
129 ZC_EXTERNAL_FILE_ATTRIBUTES = 38,
130 ZC_REL_OFFSET_LOCAL_HEADER = 42,
131 ZC_DIRECTORY_LENGTH = 46,
133 ZE_SIGNATURE_OFFSET = 0,
135 ZE_CENTRAL_DIRECTORY_DISK_NUMBER = 6,
136 ZE_NUM_ENTRIES_THIS_DISK = 8,
138 ZE_CENTRAL_DIRECTORY_SIZE = 12,
139 ZE_CENTRAL_DIR_OFFSET = 16,
140 ZE_ZIP_COMMENT_LENGTH = 20,
141 ZE_DIRECTORY_LENGTH = 22,
143 ZL_REL_OFFSET_CONTENT = 28,
144 ZL_HEADER_LENGTH = 30,
146 LWS_FZ_ERR_SEEK_END_RECORD = 1,
147 LWS_FZ_ERR_READ_END_RECORD,
148 LWS_FZ_ERR_END_RECORD_MAGIC,
149 LWS_FZ_ERR_END_RECORD_SANITY,
150 LWS_FZ_ERR_CENTRAL_SEEK,
151 LWS_FZ_ERR_CENTRAL_READ,
152 LWS_FZ_ERR_CENTRAL_SANITY,
153 LWS_FZ_ERR_NAME_TOO_LONG,
154 LWS_FZ_ERR_NAME_SEEK,
155 LWS_FZ_ERR_NAME_READ,
156 LWS_FZ_ERR_CONTENT_SANITY,
157 LWS_FZ_ERR_CONTENT_SEEK,
158 LWS_FZ_ERR_SCAN_SEEK,
159 LWS_FZ_ERR_NOT_FOUND,
160 LWS_FZ_ERR_ZLIB_INIT,
161 LWS_FZ_ERR_READ_CONTENT,
162 LWS_FZ_ERR_SEEK_COMPRESSED,
168 const uint8_t *c = (const uint8_t *)p;
170 return (uint16_t)((c[0] | (c[1] << 8)));
176 const uint8_t *c = (const uint8_t *)p;
178 return (uint32_t)((c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24)));
182 lws_fops_zip_scan(lws_fops_zip_t priv, const char *name, int len)
184 lws_filepos_t amount;
188 if (lws_vfs_file_seek_end(priv->zip_fop_fd, -ZE_DIRECTORY_LENGTH) < 0)
189 return LWS_FZ_ERR_SEEK_END_RECORD;
191 if (lws_vfs_file_read(priv->zip_fop_fd, &amount, buf,
192 ZE_DIRECTORY_LENGTH))
193 return LWS_FZ_ERR_READ_END_RECORD;
195 if (amount != ZE_DIRECTORY_LENGTH)
196 return LWS_FZ_ERR_READ_END_RECORD;
199 * We require the zip to have the last record right at the end
200 * Linux zip always does this if no zip comment.
202 if (buf[0] != 'P' || buf[1] != 'K' || buf[2] != 5 || buf[3] != 6)
203 return LWS_FZ_ERR_END_RECORD_MAGIC;
205 i = get_u16(buf + ZE_NUM_ENTRIES);
207 if (get_u16(buf + ZE_DESK_NUMBER) ||
208 get_u16(buf + ZE_CENTRAL_DIRECTORY_DISK_NUMBER) ||
209 i != get_u16(buf + ZE_NUM_ENTRIES_THIS_DISK))
210 return LWS_FZ_ERR_END_RECORD_SANITY;
212 /* end record is OK... look for our file in the central dir */
214 if (lws_vfs_file_seek_set(priv->zip_fop_fd,
215 get_u32(buf + ZE_CENTRAL_DIR_OFFSET)) < 0)
216 return LWS_FZ_ERR_CENTRAL_SEEK;
219 priv->content_start = lws_vfs_tell(priv->zip_fop_fd);
221 if (lws_vfs_file_read(priv->zip_fop_fd, &amount, buf,
222 ZC_DIRECTORY_LENGTH))
223 return LWS_FZ_ERR_CENTRAL_READ;
225 if (amount != ZC_DIRECTORY_LENGTH)
226 return LWS_FZ_ERR_CENTRAL_READ;
228 if (get_u32(buf + ZC_SIGNATURE) != 0x02014B50)
229 return LWS_FZ_ERR_CENTRAL_SANITY;
231 lwsl_debug("cstart 0x%lx\n", (unsigned long)priv->content_start);
233 priv->hdr.filename_len = get_u16(buf + ZC_FILE_NAME_LENGTH);
234 priv->hdr.extra = get_u16(buf + ZC_EXTRA_FIELD_LENGTH);
235 priv->hdr.filename_start = lws_vfs_tell(priv->zip_fop_fd);
237 priv->hdr.method = get_u16(buf + ZC_COMPRESSION_METHOD);
238 priv->hdr.crc32 = get_u32(buf + ZC_CRC32);
239 priv->hdr.comp_size = get_u32(buf + ZC_COMPRESSED_SIZE);
240 priv->hdr.uncomp_size = get_u32(buf + ZC_UNCOMPRESSED_SIZE);
241 priv->hdr.offset = get_u32(buf + ZC_REL_OFFSET_LOCAL_HEADER);
242 priv->hdr.mod_time = get_u32(buf + ZC_LAST_MOD_FILE_TIME);
243 priv->hdr.file_com_len = get_u16(buf + ZC_FILE_COMMENT_LENGTH);
245 if (priv->hdr.filename_len != len)
248 if (len >= (int)sizeof(buf) - 1)
249 return LWS_FZ_ERR_NAME_TOO_LONG;
251 if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd,
253 return LWS_FZ_ERR_NAME_READ;
254 if ((int)amount != len)
255 return LWS_FZ_ERR_NAME_READ;
258 lwsl_debug("check %s vs %s\n", buf, name);
260 if (strcmp((const char *)buf, name))
263 /* we found a match */
264 if (lws_vfs_file_seek_set(priv->zip_fop_fd, priv->hdr.offset) < 0)
265 return LWS_FZ_ERR_NAME_SEEK;
266 if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd,
269 return LWS_FZ_ERR_NAME_READ;
270 if (amount != ZL_HEADER_LENGTH)
271 return LWS_FZ_ERR_NAME_READ;
273 priv->content_start = priv->hdr.offset +
275 priv->hdr.filename_len +
276 get_u16(buf + ZL_REL_OFFSET_CONTENT);
278 lwsl_debug("content supposed to start at 0x%lx\n",
279 (unsigned long)priv->content_start);
281 if (priv->content_start > priv->zip_fop_fd->len)
282 return LWS_FZ_ERR_CONTENT_SANITY;
284 if (lws_vfs_file_seek_set(priv->zip_fop_fd,
285 priv->content_start) < 0)
286 return LWS_FZ_ERR_CONTENT_SEEK;
288 /* we are aligned at the start of the content */
290 priv->exp_uncomp_pos = 0;
295 if (i && lws_vfs_file_seek_set(priv->zip_fop_fd,
296 priv->content_start +
297 ZC_DIRECTORY_LENGTH +
298 priv->hdr.filename_len +
300 priv->hdr.file_com_len) < 0)
301 return LWS_FZ_ERR_SCAN_SEEK;
304 return LWS_FZ_ERR_NOT_FOUND;
308 lws_fops_zip_reset_inflate(lws_fops_zip_t priv)
310 if (priv->decompress)
311 inflateEnd(&priv->inflate);
313 priv->inflate.zalloc = Z_NULL;
314 priv->inflate.zfree = Z_NULL;
315 priv->inflate.opaque = Z_NULL;
316 priv->inflate.avail_in = 0;
317 priv->inflate.next_in = Z_NULL;
319 if (inflateInit2(&priv->inflate, -MAX_WBITS) != Z_OK) {
320 lwsl_err("inflate init failed\n");
321 return LWS_FZ_ERR_ZLIB_INIT;
324 if (lws_vfs_file_seek_set(priv->zip_fop_fd, priv->content_start) < 0)
325 return LWS_FZ_ERR_CONTENT_SEEK;
327 priv->exp_uncomp_pos = 0;
333 lws_fops_zip_open(const struct lws_plat_file_ops *fops, const char *vfs_path,
334 const char *vpath, lws_fop_flags_t *flags)
336 lws_fop_flags_t local_flags = 0;
342 * vpath points at the / after the fops signature in vfs_path, eg
343 * with a vfs_path "/var/www/docs/manual.zip/index.html", vpath
344 * will come pointing at "/index.html"
347 priv = lws_zalloc(sizeof(*priv), "fops_zip priv");
351 priv->fop_fd.fops = &fops_zip;
354 if ((vpath - vfs_path - 1) < m)
355 m = lws_ptr_diff(vpath, vfs_path) - 1;
356 lws_strncpy(rp, vfs_path, m + 1);
358 /* open the zip file itself using the incoming fops, not fops_zip */
360 priv->zip_fop_fd = fops->LWS_FOP_OPEN(fops, rp, NULL, &local_flags);
361 if (!priv->zip_fop_fd) {
362 lwsl_err("unable to open zip %s\n", rp);
369 m = lws_fops_zip_scan(priv, vpath, (int)strlen(vpath));
371 lwsl_err("unable to find record matching '%s' %d\n", vpath, m);
375 /* the directory metadata tells us modification time, so pass it on */
376 priv->fop_fd.mod_time = priv->hdr.mod_time;
377 *flags |= LWS_FOP_FLAG_MOD_TIME_VALID | LWS_FOP_FLAG_VIRTUAL;
378 priv->fop_fd.flags = *flags;
380 /* The zip fop_fd is left pointing at the start of the content.
382 * 1) Content could be uncompressed (STORE), and we can always serve
385 * 2) Content could be compressed (GZIP), and the client can handle
386 * receiving GZIP... we can wrap it in a GZIP header and trailer
387 * and serve the content part directly. The flag indicating we
388 * are providing GZIP directly is set so lws will send the right
391 * 3) Content could be compressed (GZIP) but the client can't handle
392 * receiving GZIP... we can decompress it and serve as it is
393 * inflated piecemeal.
395 * 4) Content may be compressed some unknown way... fail
398 if (priv->hdr.method == ZIP_COMPRESSION_METHOD_STORE) {
400 * it is stored uncompressed, leave it indicated as
401 * uncompressed, and just serve it from inside the
402 * zip with no gzip container;
405 lwsl_info("direct zip serving (stored)\n");
407 priv->fop_fd.len = priv->hdr.uncomp_size;
409 return &priv->fop_fd;
412 if ((*flags & LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP) &&
413 priv->hdr.method == ZIP_COMPRESSION_METHOD_DEFLATE) {
416 * We can serve the gzipped file contents directly as gzip
417 * from inside the zip container; client says it is OK.
419 * To convert to standalone gzip, we have to add a 10-byte
420 * constant header and a variable 8-byte trailer around the
423 * The 8-byte trailer is prepared now and held in the priv.
426 lwsl_info("direct zip serving (gzipped)\n");
428 priv->fop_fd.len = sizeof(hd) + priv->hdr.comp_size +
432 uint8_t *p = priv->u.trailer8;
434 *p++ = (uint8_t)priv->hdr.crc32;
435 *p++ = (uint8_t)(priv->hdr.crc32 >> 8);
436 *p++ = (uint8_t)(priv->hdr.crc32 >> 16);
437 *p++ = (uint8_t)(priv->hdr.crc32 >> 24);
438 *p++ = (uint8_t)priv->hdr.uncomp_size;
439 *p++ = (uint8_t)(priv->hdr.uncomp_size >> 8);
440 *p++ = (uint8_t)(priv->hdr.uncomp_size >> 16);
441 *p = (uint8_t)(priv->hdr.uncomp_size >> 24);
443 priv->u.trailer32[0] = priv->hdr.crc32;
444 priv->u.trailer32[1] = priv->hdr.uncomp_size;
447 *flags |= LWS_FOP_FLAG_COMPR_IS_GZIP;
448 priv->fop_fd.flags = *flags;
449 priv->add_gzip_container = 1;
451 return &priv->fop_fd;
454 if (priv->hdr.method == ZIP_COMPRESSION_METHOD_DEFLATE) {
456 /* we must decompress it to serve it */
458 lwsl_info("decompressed zip serving\n");
460 priv->fop_fd.len = priv->hdr.uncomp_size;
462 if (lws_fops_zip_reset_inflate(priv)) {
463 lwsl_err("inflate init failed\n");
467 priv->decompress = 1;
469 return &priv->fop_fd;
472 /* we can't handle it ... */
474 lwsl_err("zipped file %s compressed in unknown way (%d)\n", vfs_path,
478 lws_vfs_file_close(&priv->zip_fop_fd);
485 /* ie, we are closing the fop_fd for the file inside the gzip */
488 lws_fops_zip_close(lws_fop_fd_t *fd)
490 lws_fops_zip_t priv = fop_fd_to_priv(*fd);
492 if (priv->decompress)
493 inflateEnd(&priv->inflate);
495 lws_vfs_file_close(&priv->zip_fop_fd); /* close the gzip fop_fd */
504 lws_fops_zip_seek_cur(lws_fop_fd_t fd, lws_fileofs_t offset_from_cur_pos)
506 fd->pos += offset_from_cur_pos;
512 lws_fops_zip_read(lws_fop_fd_t fd, lws_filepos_t *amount, uint8_t *buf,
515 lws_fops_zip_t priv = fop_fd_to_priv(fd);
516 lws_filepos_t ramount, rlen, cur = lws_vfs_tell(fd);
519 if (priv->decompress) {
521 if (priv->exp_uncomp_pos != fd->pos) {
523 * there has been a seek in the uncompressed fop_fd
524 * we have to restart the decompression and loop eating
525 * the decompressed data up to the seek point
527 lwsl_info("seek in decompressed\n");
529 lws_fops_zip_reset_inflate(priv);
531 while (priv->exp_uncomp_pos != fd->pos) {
533 if (rlen > fd->pos - priv->exp_uncomp_pos)
534 rlen = fd->pos - priv->exp_uncomp_pos;
535 if (lws_fops_zip_read(fd, amount, buf, rlen))
536 return LWS_FZ_ERR_SEEK_COMPRESSED;
541 priv->inflate.avail_out = (unsigned int)len;
542 priv->inflate.next_out = buf;
545 if (!priv->inflate.avail_in) {
546 rlen = sizeof(priv->rbuf);
547 if (rlen > priv->hdr.comp_size -
548 (cur - priv->content_start))
549 rlen = priv->hdr.comp_size -
550 (priv->hdr.comp_size -
551 priv->content_start);
553 if (priv->zip_fop_fd->fops->LWS_FOP_READ(
554 priv->zip_fop_fd, &ramount, priv->rbuf,
556 return LWS_FZ_ERR_READ_CONTENT;
560 priv->inflate.avail_in = (unsigned int)ramount;
561 priv->inflate.next_in = priv->rbuf;
564 ret = inflate(&priv->inflate, Z_NO_FLUSH);
565 if (ret == Z_STREAM_ERROR)
578 if (!priv->inflate.avail_in && priv->inflate.avail_out &&
579 cur != priv->content_start + priv->hdr.comp_size)
582 *amount = len - priv->inflate.avail_out;
584 priv->exp_uncomp_pos += *amount;
590 if (priv->add_gzip_container) {
592 lwsl_info("%s: gzip + container\n", __func__);
595 /* place the canned header at the start */
597 if (len && fd->pos < sizeof(hd)) {
598 rlen = sizeof(hd) - fd->pos;
601 /* provide stuff from canned header */
602 memcpy(buf, hd + fd->pos, (size_t)rlen);
609 /* serve gzipped data direct from zipfile */
611 if (len && fd->pos >= sizeof(hd) &&
612 fd->pos < priv->hdr.comp_size + sizeof(hd)) {
614 rlen = priv->hdr.comp_size - (priv->zip_fop_fd->pos -
615 priv->content_start);
620 priv->zip_fop_fd->pos < (priv->hdr.comp_size +
621 priv->content_start)) {
622 if (lws_vfs_file_read(priv->zip_fop_fd,
623 &ramount, buf, rlen))
624 return LWS_FZ_ERR_READ_CONTENT;
626 fd->pos += ramount; // virtual pos
632 /* place the prepared trailer at the end */
634 if (len && fd->pos >= priv->hdr.comp_size + sizeof(hd) &&
635 fd->pos < priv->hdr.comp_size + sizeof(hd) +
637 cur = fd->pos - priv->hdr.comp_size - sizeof(hd);
638 rlen = sizeof(priv->u) - cur;
642 memcpy(buf, priv->u.trailer8 + cur, (size_t)rlen);
651 lwsl_info("%s: store\n", __func__);
653 if (len > priv->hdr.uncomp_size - (cur - priv->content_start))
654 len = priv->hdr.comp_size - (priv->hdr.comp_size -
655 priv->content_start);
657 if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd,
659 return LWS_FZ_ERR_READ_CONTENT;
664 struct lws_plat_file_ops fops_zip = {
667 lws_fops_zip_seek_cur,
670 { { ".zip/", 5 }, { ".jar/", 5 }, { ".war/", 5 } },