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 "private-libwebsockets.h"
59 * This code works with zip format containers which may have files compressed
60 * with gzip deflate (type 8) or store uncompressed (type 0).
62 * Linux zip produces such zipfiles by default, eg
64 * $ zip ../myzip.zip file1 file2 file3
67 #define ZIP_COMPRESSION_METHOD_STORE 0
68 #define ZIP_COMPRESSION_METHOD_DEFLATE 8
71 lws_filepos_t filename_start;
77 uint16_t filename_len;
80 uint16_t file_com_len;
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;
90 lws_filepos_t content_start;
91 lws_filepos_t exp_uncomp_pos;
94 uint32_t trailer32[2];
96 uint8_t rbuf[128]; /* decompression chunk size */
99 unsigned int decompress:1; /* 0 = direct from file */
100 unsigned int add_gzip_container:1;
103 struct lws_plat_file_ops fops_zip;
104 #define fop_fd_to_priv(FD) ((lws_fops_zip_t)(FD))
106 static const uint8_t hd[] = { 31, 139, 8, 0, 0, 0, 0, 0, 0, 3 };
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,
117 ZC_COMPRESSED_SIZE = 20,
118 ZC_UNCOMPRESSED_SIZE = 24,
119 ZC_FILE_NAME_LENGTH = 28,
120 ZC_EXTRA_FIELD_LENGTH = 30,
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,
129 ZE_SIGNATURE_OFFSET = 0,
131 ZE_CENTRAL_DIRECTORY_DISK_NUMBER = 6,
132 ZE_NUM_ENTRIES_THIS_DISK = 8,
134 ZE_CENTRAL_DIRECTORY_SIZE = 12,
135 ZE_CENTRAL_DIR_OFFSET = 16,
136 ZE_ZIP_COMMENT_LENGTH = 20,
137 ZE_DIRECTORY_LENGTH = 22,
139 ZL_REL_OFFSET_CONTENT = 28,
140 ZL_HEADER_LENGTH = 30,
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,
164 const uint8_t *c = (const uint8_t *)p;
166 return (uint16_t)((c[0] | (c[1] << 8)));
172 const uint8_t *c = (const uint8_t *)p;
174 return (uint32_t)((c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24)));
178 lws_fops_zip_scan(lws_fops_zip_t priv, const char *name, int len)
180 lws_filepos_t amount;
184 if (lws_vfs_file_seek_end(priv->zip_fop_fd, -ZE_DIRECTORY_LENGTH) < 0)
185 return LWS_FZ_ERR_SEEK_END_RECORD;
187 if (lws_vfs_file_read(priv->zip_fop_fd, &amount, buf,
188 ZE_DIRECTORY_LENGTH))
189 return LWS_FZ_ERR_READ_END_RECORD;
191 if (amount != ZE_DIRECTORY_LENGTH)
192 return LWS_FZ_ERR_READ_END_RECORD;
195 * We require the zip to have the last record right at the end
196 * Linux zip always does this if no zip comment.
198 if (buf[0] != 'P' || buf[1] != 'K' || buf[2] != 5 || buf[3] != 6)
199 return LWS_FZ_ERR_END_RECORD_MAGIC;
201 i = get_u16(buf + ZE_NUM_ENTRIES);
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;
208 /* end record is OK... look for our file in the central dir */
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;
215 priv->content_start = lws_vfs_tell(priv->zip_fop_fd);
217 if (lws_vfs_file_read(priv->zip_fop_fd, &amount, buf,
218 ZC_DIRECTORY_LENGTH))
219 return LWS_FZ_ERR_CENTRAL_READ;
221 if (amount != ZC_DIRECTORY_LENGTH)
222 return LWS_FZ_ERR_CENTRAL_READ;
224 if (get_u32(buf + ZC_SIGNATURE) != 0x02014B50)
225 return LWS_FZ_ERR_CENTRAL_SANITY;
227 lwsl_debug("cstart 0x%lx\n", (unsigned long)priv->content_start);
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);
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);
241 if (priv->hdr.filename_len != len)
244 if (len >= sizeof(buf) - 1)
245 return LWS_FZ_ERR_NAME_TOO_LONG;
247 if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd,
249 return LWS_FZ_ERR_NAME_READ;
251 return LWS_FZ_ERR_NAME_READ;
254 lwsl_debug("check %s vs %s\n", buf, name);
256 if (strcmp((const char *)buf, name))
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,
265 return LWS_FZ_ERR_NAME_READ;
266 if (amount != ZL_HEADER_LENGTH)
267 return LWS_FZ_ERR_NAME_READ;
269 priv->content_start = priv->hdr.offset +
271 priv->hdr.filename_len +
272 get_u16(buf + ZL_REL_OFFSET_CONTENT);
274 lwsl_debug("content supposed to start at 0x%lx\n",
275 (unsigned long)priv->content_start);
277 if (priv->content_start > priv->zip_fop_fd->len)
278 return LWS_FZ_ERR_CONTENT_SANITY;
280 if (lws_vfs_file_seek_set(priv->zip_fop_fd,
281 priv->content_start) < 0)
282 return LWS_FZ_ERR_CONTENT_SEEK;
284 /* we are aligned at the start of the content */
286 priv->exp_uncomp_pos = 0;
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 +
296 priv->hdr.file_com_len) < 0)
297 return LWS_FZ_ERR_SCAN_SEEK;
300 return LWS_FZ_ERR_NOT_FOUND;
304 lws_fops_zip_reset_inflate(lws_fops_zip_t priv)
306 if (priv->decompress)
307 inflateEnd(&priv->inflate);
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;
315 if (inflateInit2(&priv->inflate, -MAX_WBITS) != Z_OK) {
316 lwsl_err("inflate init failed\n");
317 return LWS_FZ_ERR_ZLIB_INIT;
320 if (lws_vfs_file_seek_set(priv->zip_fop_fd, priv->content_start) < 0)
321 return LWS_FZ_ERR_CONTENT_SEEK;
323 priv->exp_uncomp_pos = 0;
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)
332 lws_fop_flags_t local_flags = 0;
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"
343 priv = lws_zalloc(sizeof(*priv));
347 priv->fop_fd.fops = &fops_zip;
350 if ((vpath - vfs_path - 1) < m)
351 m = vpath - vfs_path - 1;
352 strncpy(rp, vfs_path, m);
355 /* open the zip file itself using the incoming fops, not fops_zip */
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);
366 m = lws_fops_zip_scan(priv, vpath, strlen(vpath));
368 lwsl_err("unable to find record matching '%s' %d\n", vpath, m);
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;
377 /* The zip fop_fd is left pointing at the start of the content.
379 * 1) Content could be uncompressed (STORE), and we can always serve
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
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.
392 * 4) Content may be compressed some unknown way... fail
395 if (priv->hdr.method == ZIP_COMPRESSION_METHOD_STORE) {
397 * it is stored uncompressed, leave it indicated as
398 * uncompressed, and just serve it from inside the
399 * zip with no gzip container;
402 lwsl_info("direct zip serving (stored)\n");
404 priv->fop_fd.len = priv->hdr.uncomp_size;
406 return &priv->fop_fd;
409 if ((*flags & LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP) &&
410 priv->hdr.method == ZIP_COMPRESSION_METHOD_DEFLATE) {
413 * We can serve the gzipped file contents directly as gzip
414 * from inside the zip container; client says it is OK.
416 * To convert to standalone gzip, we have to add a 10-byte
417 * constant header and a variable 8-byte trailer around the
420 * The 8-byte trailer is prepared now and held in the priv.
423 lwsl_info("direct zip serving (gzipped)\n");
425 priv->fop_fd.len = sizeof(hd) + priv->hdr.comp_size +
429 uint8_t *p = priv->u.trailer8;
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);
440 priv->u.trailer32[0] = priv->hdr.crc32;
441 priv->u.trailer32[1] = priv->hdr.uncomp_size;
444 *flags |= LWS_FOP_FLAG_COMPR_IS_GZIP;
445 priv->fop_fd.flags = *flags;
446 priv->add_gzip_container = 1;
448 return &priv->fop_fd;
451 if (priv->hdr.method == ZIP_COMPRESSION_METHOD_DEFLATE) {
453 /* we must decompress it to serve it */
455 lwsl_info("decompressed zip serving\n");
457 priv->fop_fd.len = priv->hdr.uncomp_size;
459 if (lws_fops_zip_reset_inflate(priv)) {
460 lwsl_err("inflate init failed\n");
464 priv->decompress = 1;
466 return &priv->fop_fd;
469 /* we can't handle it ... */
471 lwsl_err("zipped file %s compressed in unknown way (%d)\n", vfs_path,
475 lws_vfs_file_close(&priv->zip_fop_fd);
482 /* ie, we are closing the fop_fd for the file inside the gzip */
485 lws_fops_zip_close(lws_fop_fd_t *fd)
487 lws_fops_zip_t priv = fop_fd_to_priv(*fd);
489 if (priv->decompress)
490 inflateEnd(&priv->inflate);
492 lws_vfs_file_close(&priv->zip_fop_fd); /* close the gzip fop_fd */
501 lws_fops_zip_seek_cur(lws_fop_fd_t fd, lws_fileofs_t offset_from_cur_pos)
503 fd->pos += offset_from_cur_pos;
509 lws_fops_zip_read(lws_fop_fd_t fd, lws_filepos_t *amount, uint8_t *buf,
512 lws_fops_zip_t priv = fop_fd_to_priv(fd);
513 lws_filepos_t ramount, rlen, cur = lws_vfs_tell(fd);
516 if (priv->decompress) {
518 if (priv->exp_uncomp_pos != fd->pos) {
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
524 lwsl_info("seek in decompressed\n");
526 lws_fops_zip_reset_inflate(priv);
528 while (priv->exp_uncomp_pos != fd->pos) {
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;
538 priv->inflate.avail_out = len;
539 priv->inflate.next_out = buf;
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);
550 if (priv->zip_fop_fd->fops->LWS_FOP_READ(
551 priv->zip_fop_fd, &ramount, priv->rbuf,
553 return LWS_FZ_ERR_READ_CONTENT;
557 priv->inflate.avail_in = ramount;
558 priv->inflate.next_in = priv->rbuf;
561 ret = inflate(&priv->inflate, Z_NO_FLUSH);
562 if (ret == Z_STREAM_ERROR)
568 /* and fall through */
575 if (!priv->inflate.avail_in && priv->inflate.avail_out &&
576 cur != priv->content_start + priv->hdr.comp_size)
579 *amount = len - priv->inflate.avail_out;
581 priv->exp_uncomp_pos += *amount;
587 if (priv->add_gzip_container) {
589 lwsl_info("%s: gzip + container\n", __func__);
592 /* place the canned header at the start */
594 if (len && fd->pos < sizeof(hd)) {
595 rlen = sizeof(hd) - fd->pos;
598 /* provide stuff from canned header */
599 memcpy(buf, hd + fd->pos, rlen);
606 /* serve gzipped data direct from zipfile */
608 if (len && fd->pos >= sizeof(hd) &&
609 fd->pos < priv->hdr.comp_size + sizeof(hd)) {
611 rlen = priv->hdr.comp_size - (priv->zip_fop_fd->pos -
612 priv->content_start);
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;
623 fd->pos += ramount; // virtual pos
629 /* place the prepared trailer at the end */
631 if (len && fd->pos >= priv->hdr.comp_size + sizeof(hd) &&
632 fd->pos < priv->hdr.comp_size + sizeof(hd) +
634 cur = fd->pos - priv->hdr.comp_size - sizeof(hd);
635 rlen = sizeof(priv->u) - cur;
639 memcpy(buf, priv->u.trailer8 + cur, rlen);
648 lwsl_info("%s: store\n", __func__);
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);
654 if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd,
656 return LWS_FZ_ERR_READ_CONTENT;
661 struct lws_plat_file_ops fops_zip = {
664 lws_fops_zip_seek_cur,
667 { { ".zip/", 5 }, { ".jar/", 5 }, { ".war/", 5 } },