diff --git a/CMakeLists.txt b/CMakeLists.txt index 95f473ef..6429374b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,6 +114,7 @@ option(LWS_WITH_NO_LOGS "Disable all logging from being compiled in" OFF) option(LWS_STATIC_PIC "Build the static version of the library with position-independent code" OFF) option(LWS_WITH_RANGES "Support http ranges (RFC7233)" ON) option(LWS_FALLBACK_GETHOSTBYNAME "Also try to do dns resolution using gethostbyname if getaddrinfo fails" OFF) +option(LWS_WITH_ZIP_FOPS "Support serving pre-zipped files" ON) if (LWS_WITH_LWSWS) message(STATUS "LWS_WITH_LWSWS --> Enabling LWS_WITH_PLUGINS and LWS_WITH_LIBUV") @@ -652,6 +653,11 @@ if (LWS_WITH_RANGES) lib/ranges.c) endif() +if (LWS_WITH_ZIP_FOPS) + list(APPEND SOURCES + lib/junzip.c) +endif() + # Add helper files for Windows. if (WIN32) set(WIN32_HELPERS_PATH win32port/win32helpers) @@ -1670,6 +1676,7 @@ message(" LWS_STATIC_PIC = ${LWS_STATIC_PIC}") message(" LWS_WITH_RANGES = ${LWS_WITH_RANGES}") message(" LWS_PLAT_OPTEE = ${LWS_PLAT_OPTEE}") message(" LWS_WITH_ESP32 = ${LWS_WITH_ESP32}") +message(" LWS_WITH_ZIP_FOPS = ${LWS_WITH_ZIP_FOPS}") message("---------------------------------------------------------------------") diff --git a/lib/junzip.c b/lib/junzip.c index d50489fd..b20d534e 100644 --- a/lib/junzip.c +++ b/lib/junzip.c @@ -6,218 +6,262 @@ #include -#include "junzip.h" +#include "private-libwebsockets.h" -#define ZIP_CENTRAL_SIGNATURE 0 -#define ZIP_CENTRAL_VERSION_MADE_BY 4 -#define ZIP_CENTRAL_VERSION_NEEDED_TO_EXTRACT 6 -#define ZIP_CENTRAL_GENERAL_PURPOSE_BIT_FLAG 8 -#define ZIP_CENTRAL_COMPRESSION_METHOD 10 -#define ZIP_CENTRAL_LAST_MOD_FILE_TIME 12 -#define ZIP_CENTRAL_LAST_MOD_FILE_DATE 14 -#define ZIP_CENTRAL_CRC32 16 -#define ZIP_CENTRAL_COMPRESSED_SIZE 20 -#define ZIP_CENTRAL_UNCOMPRESSED_SIZE 24 -#define ZIP_CENTRAL_FILE_NAME_LENGTH 28 -#define ZIP_CENTRAL_EXTRA_FIELD_LENGTH 30 -#define ZIP_CENTRAL_FILE_COMMENT_LENGTH 32 -#define ZIP_CENTRAL_DISK_NUMBER_START 34 -#define ZIP_CENTRAL_INTERNAL_FILE_ATTRIBUTES 36 -#define ZIP_CENTRAL_EXTERNAL_FILE_ATTRIBUTES 38 -#define ZIP_CENTRAL_RELATIVE_OFFSET_OF_LOCAL_HEADER 42 -#define ZIP_CENTRAL_DIRECTORY_LENGTH 46 +enum { + ZC_SIGNATURE = 0, + ZC_VERSION_MADE_BY = 4, + ZC_VERSION_NEEDED_TO_EXTRACT = 6, + ZC_GENERAL_PURPOSE_BIT_FLAG = 8, + ZC_COMPRESSION_METHOD = 10, + ZC_LAST_MOD_FILE_TIME = 12, + ZC_LAST_MOD_FILE_DATE = 14, + ZC_CRC32 = 16, + ZC_COMPRESSED_SIZE = 20, + ZC_UNCOMPRESSED_SIZE = 24, + ZC_FILE_NAME_LENGTH = 28, + ZC_EXTRA_FIELD_LENGTH = 30, + ZC_FILE_COMMENT_LENGTH = 32, + ZC_DISK_NUMBER_START = 34, + ZC_INTERNAL_FILE_ATTRIBUTES = 36, + ZC_EXTERNAL_FILE_ATTRIBUTES = 38, + ZC_RELATIVE_OFFSET_OF_LOCAL_HEADER = 42, + ZC_DIRECTORY_LENGTH = 46, -#define ZIP_END_SIGNATURE_OFFSET 0 -#define ZIP_END_DESK_NUMBER 4 -#define ZIP_END_CENTRAL_DIRECTORY_DISK_NUMBER 6 -#define ZIP_END_NUM_ENTRIES_THIS_DISK 8 -#define ZIP_END_NUM_ENTRIES 10 -#define ZIP_END_CENTRAL_DIRECTORY_SIZE 12 -#define ZIP_END_CENTRAL_DIRECTORY_OFFSET 16 -#define ZIP_END_ZIP_COMMENT_LENGTH 20 -#define ZIP_END_DIRECTORY_LENGTH 22 + ZE_SIGNATURE_OFFSET = 0, + ZE_DESK_NUMBER = 4, + ZE_CENTRAL_DIRECTORY_DISK_NUMBER = 6, + ZE_NUM_ENTRIES_THIS_DISK = 8, + ZE_NUM_ENTRIES = 10, + ZE_CENTRAL_DIRECTORY_SIZE = 12, + ZE_CENTRAL_DIRECTORY_OFFSET = 16, + ZE_ZIP_COMMENT_LENGTH = 20, + ZE_DIRECTORY_LENGTH = 22, +}; -#define get_u16(PTR) ((PTR)[0] | ((PTR)[1] << 8)) -#define get_u32(PTR) ((PTR)[0] | ((PTR)[1]<<8) | ((PTR)[2]<<16) | ((PTR)[3]<<24)) - -static int -zf_seek_set(JZFile *zfile, size_t offset) +static uint16_t +get_u16(void *p) { - int new_position = offset; - if (new_position < 0 || new_position > zfile->length) - return -1; - zfile->position = new_position; - return 0; + const uint8_t *c = (const uint8_t *)p; + + return (uint16_t)((c[0] | (c[1] << 8))); +} + +static uint32_t +get_u32(void *p) +{ + const uint8_t *c = (const uint8_t *)p; + + return (uint32_t)((c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24))); } static int -zf_seek_cur(JZFile *zfile, size_t offset) +zf_seek_set(jzfile_t *zfile, size_t offset) { - int new_position = zfile->position + offset; - if (new_position < 0 || new_position > zfile->length) - return -1; - zfile->position = new_position; - return 0; + int new_position = offset; + + if (new_position < 0 || new_position > zfile->length) + return -1; + zfile->position = new_position; + + return 0; } static int -zf_seek_end(JZFile *zfile, size_t offset) +zf_seek_cur(jzfile_t *zfile, size_t offset) { - int new_position = zfile->length + offset; - if (new_position < 0 || new_position > zfile->length) - return -1; - zfile->position = new_position; - return 0; + int new_position = zfile->position + offset; + + if (new_position < 0 || new_position > zfile->length) + return -1; + zfile->position = new_position; + + return 0; } -size_t zf_read(JZFile *zfile, void *buf, size_t size) +static int +zf_seek_end(jzfile_t *zfile, size_t offset) { - size_t avail = zfile->length - zfile->position; - if (size > avail) - size = avail; - memcpy(buf, zfile->start + zfile->position, size); - zfile->position += size; - return size; + int new_position = zfile->length + offset; + + if (new_position < 0 || new_position > zfile->length) + return -1; + zfile->position = new_position; + + return 0; } -// Read ZIP file end record. Will move within file. -int jzReadEndRecord(JZFile *zip) { - long fileSize, readBytes, i; +size_t +zf_read(jzfile_t *zfile, void *buf, size_t size) +{ + size_t avail = zfile->length - zfile->position; - if(zf_seek_end(zip, -ZIP_END_DIRECTORY_LENGTH)) { - return Z_ERRNO; - } + if (size > avail) + size = avail; + memcpy(buf, zfile->start + zfile->position, size); + zfile->position += size; - unsigned char *ptr = zf_current(zip); - while (ptr[0] != 0x50 || ptr[1] != 0x4B || ptr[2] != 0x05 || ptr[3] != 0x06) { - if (ptr == zip->start) { - return Z_ERRNO; - } - ptr--; - } - zip->numEntries = get_u16(ptr + ZIP_END_NUM_ENTRIES); - zip->centralDirectoryOffset= get_u32(ptr + ZIP_END_CENTRAL_DIRECTORY_OFFSET); - if (get_u16(ptr + ZIP_END_DESK_NUMBER) - || get_u16(ptr + ZIP_END_CENTRAL_DIRECTORY_DISK_NUMBER) - || zip->numEntries != get_u16(ptr + ZIP_END_NUM_ENTRIES_THIS_DISK)) { - return Z_ERRNO; - } - - return Z_OK; + return size; } -// Read ZIP file global directory. Will move within file. -int jzReadCentralDirectory(JZFile *zip, JZRecordCallback callback) { - JZFileHeader header; - int i; +/* Read ZIP file end record. Will move within file. */ +int +jzReadEndRecord(jzfile_t *zip) +{ + unsigned char *ptr = zf_current(zip); - if(zf_seek_set(zip, zip->centralDirectoryOffset)) { - return Z_ERRNO; - } + if (zf_seek_end(zip, -ZE_DIRECTORY_LENGTH)) + return Z_ERRNO; - for(i=0; i < zip->numEntries; i++) { - unsigned char *ptr = zf_current(zip); - if (zf_available(zip) < ZIP_CENTRAL_DIRECTORY_LENGTH) { - return Z_ERRNO; - } - zf_seek_cur(zip, ZIP_CENTRAL_DIRECTORY_LENGTH); - if (get_u32(ptr + ZIP_CENTRAL_SIGNATURE) != 0x02014B50) { - return Z_ERRNO; - } - // Construct JZFileHeader from global file header - header.compressionMethod = get_u16(ptr + ZIP_CENTRAL_COMPRESSION_METHOD); - header.crc32 = get_u32(ptr + ZIP_CENTRAL_CRC32); - header.compressedSize = get_u32(ptr + ZIP_CENTRAL_COMPRESSED_SIZE); - header.uncompressedSize = get_u32(ptr + ZIP_CENTRAL_UNCOMPRESSED_SIZE); - header.fileNameLength = get_u16(ptr + ZIP_CENTRAL_FILE_NAME_LENGTH); - header.extraFieldLength = get_u16(ptr + ZIP_CENTRAL_EXTRA_FIELD_LENGTH); - header.offset = get_u32(ptr + ZIP_CENTRAL_RELATIVE_OFFSET_OF_LOCAL_HEADER); + while (ptr[0] != 0x50 || ptr[1] != 0x4B || ptr[2] != 5 || ptr[3] != 6) + if (ptr-- == zip->start) + return Z_ERRNO; - header.fileNameStart = zf_tell(zip); - if (zf_seek_cur(zip, header.fileNameLength + header.extraFieldLength + get_u16(ptr + ZIP_CENTRAL_FILE_COMMENT_LENGTH))) { - return Z_ERRNO; - } + zip->numEntries = get_u16(ptr + ZE_NUM_ENTRIES); + zip->centralDirectoryOffset= get_u32(ptr + ZE_CENTRAL_DIRECTORY_OFFSET); - if(!callback(zip, i, &header)) - break; // end if callback returns zero - } + if (get_u16(ptr + ZE_DESK_NUMBER) || + get_u16(ptr + ZE_CENTRAL_DIRECTORY_DISK_NUMBER) || + zip->numEntries != get_u16(ptr + ZE_NUM_ENTRIES_THIS_DISK)) + return Z_ERRNO; - return Z_OK; + return Z_OK; } -int jzSeekData(JZFile *zip, JZFileHeader *entry) { - size_t offset = entry->offset; - offset += ZIP_LOCAL_FILE_HEADER_LENGTH; - offset += entry->fileNameLength + entry->extraFieldLength; - if (offset < 0 || offset > zip->length) - return Z_STREAM_END; - zip->position = offset; - return Z_OK; +/* Read ZIP file global directory. Will move within file. */ +int +jzReadCentralDirectory(jzfile_t *zip, jzcb_t callback) +{ + jzfile_hdr_t h; + int i; + + if (zf_seek_set(zip, zip->centralDirectoryOffset)) + return Z_ERRNO; + + for (i = 0; i < zip->numEntries; i++) { + unsigned char *ptr = zf_current(zip); + + if (zf_available(zip) < ZC_DIRECTORY_LENGTH) + return Z_ERRNO; + + zf_seek_cur(zip, ZC_DIRECTORY_LENGTH); + if (get_u32(ptr + ZC_SIGNATURE) != 0x02014B50) + return Z_ERRNO; + + // Construct jzfile_hdr_t from global file h + h.compressionMethod = get_u16(ptr + ZC_COMPRESSION_METHOD); + h.crc32 = get_u32(ptr + ZC_CRC32); + h.compressedSize = get_u32(ptr + ZC_COMPRESSED_SIZE); + h.uncompressedSize = get_u32(ptr + ZC_UNCOMPRESSED_SIZE); + h.fileNameLength = get_u16(ptr + ZC_FILE_NAME_LENGTH); + h.extraFieldLength = get_u16(ptr + ZC_EXTRA_FIELD_LENGTH); + h.offset = get_u32(ptr + ZC_RELATIVE_OFFSET_OF_LOCAL_HEADER); + + h.fileNameStart = zf_tell(zip); + if (zf_seek_cur(zip, h.fileNameLength + h.extraFieldLength + + get_u16(ptr + ZC_FILE_COMMENT_LENGTH))) + return Z_ERRNO; + + if (!callback(zip, i, &h)) + break; // end if callback returns zero + } + + return Z_OK; } -// Read data from file stream, described by header, to preallocated buffer -int jzReadData(JZFile *zip, JZFileHeader *header, void *buffer) { - unsigned char *bytes = (unsigned char *)buffer; // cast - long compressedLeft, uncompressedLeft; - z_stream strm; - int ret; +int jzSeekData(jzfile_t *zip, jzfile_hdr_t *entry) +{ + size_t offset = entry->offset; - if(header->compressionMethod == 0) { // Store - just read it - if (zf_read(zip, buffer, header->uncompressedSize) < - header->uncompressedSize) - return Z_ERRNO; - } else if (header->compressionMethod == 8) { // Deflate - using zlib - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; + offset += ZIP_LOCAL_FILE_HEADER_LENGTH; + offset += entry->fileNameLength + entry->extraFieldLength; - strm.avail_in = 0; - strm.next_in = Z_NULL; + if (offset < 0 || offset > zip->length) + return Z_STREAM_END; - // Use inflateInit2 with negative window bits to indicate raw data - if ((ret = inflateInit2(&strm, -MAX_WBITS)) != Z_OK) - return ret; // Zlib errors are negative + zip->position = offset; - // Inflate compressed data - for (compressedLeft = header->compressedSize, - uncompressedLeft = header->uncompressedSize; - compressedLeft && uncompressedLeft && ret != Z_STREAM_END; - compressedLeft -= strm.avail_in) { - // Read next chunk - unsigned char *ptr = zf_current(zip); - strm.avail_in = compressedLeft; - zf_seek_cur(zip, compressedLeft); - if(strm.avail_in == 0) { - inflateEnd(&strm); - return Z_ERRNO; - } - - strm.next_in = ptr; - strm.avail_out = uncompressedLeft; - strm.next_out = bytes; - - compressedLeft -= strm.avail_in; // inflate will change avail_in - - ret = inflate(&strm, Z_NO_FLUSH); - - if(ret == Z_STREAM_ERROR) return ret; // shouldn't happen - - switch (ret) { - case Z_NEED_DICT: - ret = Z_DATA_ERROR; /* and fall through */ - case Z_DATA_ERROR: case Z_MEM_ERROR: - (void)inflateEnd(&strm); - return ret; - } - - bytes += uncompressedLeft - strm.avail_out; // bytes uncompressed - uncompressedLeft = strm.avail_out; - } - - inflateEnd(&strm); - } else { - return Z_ERRNO; - } - - return Z_OK; + return Z_OK; +} + +/* Read data from file stream, described by h, to preallocated buffer */ +int +jzReadData(jzfile_t *zip, jzfile_hdr_t *h, void *buffer) +{ + unsigned char *bytes = (unsigned char *)buffer; + long compressedLeft, uncompressedLeft; + z_stream strm; + int ret; + + switch (h->compressionMethod) { + case 0: /* Store - just read it */ + if (zf_read(zip, buffer, h->uncompressedSize) < + h->uncompressedSize) + return Z_ERRNO; + break; + case 8: /* Deflate - using zlib */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + + strm.avail_in = 0; + strm.next_in = Z_NULL; + + /* + * Use inflateInit2 with negative window bits to + * indicate raw data + */ + if ((ret = inflateInit2(&strm, -MAX_WBITS)) != Z_OK) + return ret; /* Zlib errors are negative */ + + /* Inflate compressed data */ + for (compressedLeft = h->compressedSize, + uncompressedLeft = h->uncompressedSize; + compressedLeft && uncompressedLeft && ret != Z_STREAM_END; + compressedLeft -= strm.avail_in) { + /* Read next chunk */ + unsigned char *ptr = zf_current(zip); + + strm.avail_in = compressedLeft; + zf_seek_cur(zip, compressedLeft); + if (strm.avail_in == 0) { + inflateEnd(&strm); + + return Z_ERRNO; + } + + strm.next_in = ptr; + strm.avail_out = uncompressedLeft; + strm.next_out = bytes; + + compressedLeft -= strm.avail_in; + /* inflate will change avail_in */ + + ret = inflate(&strm, Z_NO_FLUSH); + + if (ret == Z_STREAM_ERROR) + return ret; + + switch (ret) { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; + /* and fall through */ + case Z_DATA_ERROR: case Z_MEM_ERROR: + (void)inflateEnd(&strm); + + return ret; + } + + /* bytes uncompressed */ + bytes += uncompressedLeft - strm.avail_out; + uncompressedLeft = strm.avail_out; + } + + inflateEnd(&strm); + break; + default: + return Z_ERRNO; + } + + return Z_OK; } diff --git a/lib/junzip.h b/lib/junzip.h deleted file mode 100644 index 145a70de..00000000 --- a/lib/junzip.h +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Unzip library by Per Bothner. - * Loosely based on Joonas Pihlajamaa's JUnzip. - * Released into public domain. https://github.com/jokkebk/JUnzip - */ - -#ifndef __JUNZIP_H -#define __JUNZIP_H - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -#include - -// If you don't have stdint.h, the following two lines should work for most 32/64 bit systems -// typedef unsigned int uint32_t; -// typedef unsigned short uint16_t; - -typedef struct JZFile JZFile; - -struct JZFile { - unsigned char *start; - off_t length; - long position; - int numEntries; - uint32_t centralDirectoryOffset; -}; - -#define zf_tell(ZF) ((ZF)->position) -#define zf_available(ZF) ((ZF)->length - (ZF)->position) -#define zf_current(ZF) ((ZF)->start + (ZF)->position) - -#define ZIP_LOCAL_FILE_HEADER_LENGTH 30 - -typedef struct { - uint16_t compressionMethod; - uint32_t crc32; - uint32_t compressedSize; - uint32_t uncompressedSize; - long fileNameStart; - uint16_t fileNameLength; - uint16_t extraFieldLength; // unsupported - uint32_t offset; -} JZFileHeader; - -// Callback prototype for central and local file record reading functions -typedef int (*JZRecordCallback)(JZFile *zip, int index, JZFileHeader *header); - -// Read ZIP file end record. Will move within file. -int jzReadEndRecord(JZFile *zip); - -// Read ZIP file global directory. Will move within file. -// Callback is called for each record, until callback returns zero -int jzReadCentralDirectory(JZFile *zip, JZRecordCallback callback); - - // See to the start of the actual data of the given entry. -int jzSeekData(JZFile *zip, JZFileHeader *header); - -// Read data from file stream, described by header, to preallocated buffer -// Return value is zlib coded, e.g. Z_OK, or error code -int jzReadData(JZFile *zip, JZFileHeader *header, void *buffer); - -#ifdef __cplusplus -}; -#endif /* __cplusplus */ - -#endif diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index c5244ad8..bf7cc642 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -4296,10 +4296,23 @@ struct lws_fop_fd { #if !defined(ssize_t) typedef SSIZE_T ssize_t; #endif +#endif + +#if defined(LWS_HAVE_STDINT_H) +#include +#else +#if defined(WIN32) || defined(_WIN32) /* !!! >:-[ */ typedef unsigned __int32 uint32_t; +typedef unsigned __int16 uint16_t; typedef unsigned __int8 uint8_t; +#else +typedef unsigned int uint32_t; +typedef unsigned short uint16_t; +typedef unsigned char uint8_t; #endif +#endif + typedef struct lws_fop_fd *lws_fop_fd_t; typedef size_t lws_filepos_t; typedef ssize_t lws_fileofs_t; diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index e8fbd2f6..3431c971 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -439,6 +439,57 @@ extern "C" { #define SYSTEM_RANDOM_FILEPATH "/dev/urandom" #endif +/** + * Unzip library by Per Bothner. + * Loosely based on Joonas Pihlajamaa's JUnzip. + * Released into public domain. https://github.com/jokkebk/JUnzip + * -------> + */ + +typedef struct jzfile { + unsigned char *start; + off_t length; + long position; + int numEntries; + uint32_t centralDirectoryOffset; +} jzfile_t; + +#define zf_tell(ZF) ((ZF)->position) +#define zf_available(ZF) ((ZF)->length - (ZF)->position) +#define zf_current(ZF) ((ZF)->start + (ZF)->position) + +#define ZIP_LOCAL_FILE_HEADER_LENGTH 30 + +typedef struct { + uint16_t compressionMethod; + uint32_t crc32; + uint32_t compressedSize; + uint32_t uncompressedSize; + long fileNameStart; + uint16_t fileNameLength; + uint16_t extraFieldLength; // unsupported + uint32_t offset; +} jzfile_hdr_t; + +// Callback prototype for central and local file record reading functions +typedef int (*jzcb_t)(jzfile_t *zip, int index, jzfile_hdr_t *header); + +// Read ZIP file end record. Will move within file. +int jzReadEndRecord(jzfile_t *zip); + +// Read ZIP file global directory. Will move within file. +// Callback is called for each record, until callback returns zero +int jzReadCentralDirectory(jzfile_t *zip, jzcb_t callback); + + // See to the start of the actual data of the given entry. +int jzSeekData(jzfile_t *zip, jzfile_hdr_t *header); + +// Read data from file stream, described by header, to preallocated buffer +// Return value is zlib coded, e.g. Z_OK, or error code +int jzReadData(jzfile_t *zip, jzfile_hdr_t *header, void *buffer); + +/* <------ */ + enum lws_websocket_opcodes_07 { LWSWSOPC_CONTINUATION = 0, LWSWSOPC_TEXT_FRAME = 1, diff --git a/lws_config.h.in b/lws_config.h.in index 2bb1a506..b51c21c6 100644 --- a/lws_config.h.in +++ b/lws_config.h.in @@ -122,5 +122,9 @@ /* OPTEE */ #cmakedefine LWS_PLAT_OPTEE +/* ZIP FOPS */ +#cmakedefine LWS_WITH_ZIP_FOPS +#cmakedefine LWS_HAVE_STDINT_H + #cmakedefine LWS_FALLBACK_GETHOSTBYNAME ${LWS_SIZEOFPTR_CODE}