diff --git a/meta/miniz.lua b/meta/miniz.lua new file mode 100644 index 00000000..b61127b3 --- /dev/null +++ b/meta/miniz.lua @@ -0,0 +1,187 @@ +--- @meta + +--- @class miniz.inflate.options +--- @field zlib? boolean If true, the input is expected to be in zlib format. Defaults to false. + +--- @class miniz.deflate.options +--- @field zlib? boolean If true, the output will be in zlib format. Defaults to false. +--- @field probes? integer The number of dictionary probes per search. Defaults to 128. The log2 of this value is the compression level. +--- @field filter_matches? boolean If true, the deflator will filter out matches that are too small to be worth encoding. Defaults to true. +--- @field force_static? boolean If true, the deflator will always use static Huffman codes. Defaults to false. +--- @field force_raw? boolean If true, the deflator will always use uncompressed blocks. Defaults to false. + +--- @class miniz.locate_file.options +--- @field case_sensitive? boolean If true, the file name will be matched case-sensitively. Defaults to false. +--- @field ignore_path? boolean If true, the file name will be matched without considering the path. Defaults to false. + +--- @class miniz.stat +--- @field index integer The index of the file in the ZIP archive. +--- @field version_made_by integer The version of the ZIP specification used to create the file. +--- @field version_needed integer The minimum version of the ZIP specification needed to extract the file. +--- @field bit_flag integer The general purpose bit flag of the file. +--- @field method integer The compression method used for the file. +--- @field time integer The modification time of the file. +--- @field crc32 integer The CRC-32 checksum of the file. +--- @field comp_size integer The size of the compressed data of the file. +--- @field uncomp_size integer The size of the uncompressed data of the file. +--- @field internal_attr integer The internal attribute of the file. +--- @field external_attr integer The external attribute of the file. +--- @field filename string The name of the file, may be truncated. Use `get_filename` to get the full name. +--- @field comment string The comment associated with the file, may be truncated. +--- @field is_directory boolean Whether the file is a directory. + +--- @alias miniz.flush 'no'|'partial'|'sync'|'full'|'finish'|'block' + +--- @class miniz +local miniz = {} + +--- Creates a new ZIP reader for the file at the given path. +--- @param path string +--- @param flags? integer The flags to use when opening the file. Defaults to 0. +--- @return miniz.reader|nil reader +--- @return string|nil err +function miniz.new_reader(path, flags) end + +--- Creates a new ZIP writer in memory. +--- @param initial_reserve_size? integer The space reserved before the archive. Defaults to 0. +--- @param initial_allocation_size? integer The initial size of the output buffer. Defaults to 128KB. +--- @return miniz.writer writer +function miniz.new_writer(initial_reserve_size, initial_allocation_size) end + +--- Inflate the given compressed data. +--- @param data string +--- @param options? integer|miniz.inflate.options +--- @return string|nil output +function miniz.inflate(data, options) end + +--- Deflate the given uncompressed data. +--- @param data string +--- @param options? integer|miniz.deflate.options +--- @return string|nil output +function miniz.deflate(data, options) end + +--- Calculate the Adler-32 checksum of the given data, starting with the given initial value. +--- @param adler integer|nil The initial value of the checksum. Defaults to 1. +--- @param data string|nil The data to calculate the checksum of. +--- @return integer adler32 The resulting checksum. +function miniz.adler32(adler, data) end + +--- Calculate the CCITT CRC-32 checksum of the given data, starting with the given initial value. +--- @param crc32 integer|nil The initial value of the checksum. Defaults to 0. +--- @param data string|nil The data to calculate the checksum of. +--- @return integer crc32 The resulting checksum. +function miniz.crc32(crc32, data) end + +--- ZLIB compress the given data with the given compression level. +--- @param data string +--- @param level? integer The compression level, from 0 (no compression) to 9 (best compression). Defaults to 6. +--- @return string|nil output +--- @return string|nil err +function miniz.compress(data, level) end + +--- ZLIB uncompress the given data, starting with the given initial length. +--- @param data string +--- @param initial_length? integer The initial length of the uncompressed data. Defaults to the length of the compressed data multiplied by 2. +--- @return string|nil output +--- @return string|integer err_or_processed The number of bytes processed from the input, or an error message if the decompression failed. +function miniz.uncompress(data, initial_length) end + +--- Returns the version of miniz being used. +--- @return string version +function miniz.version() end + +--- Creates a new ZLIB deflate stream. +--- @param level? integer The compression level, from 0 (no compression) to 9 (best compression). Defaults to 6. +--- @return miniz.deflator deflator +function miniz.new_deflator(level) end + +--- Creates a new ZLIB inflate stream. +--- @return miniz.inflator inflator +function miniz.new_inflator() end + +--- @class miniz.reader +local reader = {} + +--- Returns the total number of files in the ZIP archive. +--- @return integer num_files +function reader:get_num_files() end + +--- Returns the index of the file in the ZIP archive that matches the given path and options, or nil. +--- @param path string The path of the file to locate. +--- @param options? integer|miniz.locate_file.options +--- @return integer|nil file_index +--- @return string|nil err +function reader:locate_file(path, options) end + +--- Returns the stat of the file at the given index. +--- @param file_index integer The index of the file to get the name of. +--- @return miniz.stat|nil stat +--- @return string|nil err +function reader:stat(file_index) end + +--- Returns the full name of the file at the given index. +--- @param file_index integer The index of the file to get the name of. +--- @return string|nil filename +--- @return string|nil err +function reader:get_filename(file_index) end + +--- Returns true if the file at the given index is a directory, false otherwise. +--- @param file_index integer The index of the file to check. +--- @return boolean is_directory +function reader:is_directory(file_index) end + +--- Extracts the data of the file at the given index. +--- @param file_index integer The index of the file to extract. +--- @param flags? integer The flags to use when extracting the file. Defaults to 0. +--- @return string|nil data +--- @return string|nil err +function reader:extract(file_index, flags) end + +--- Returns the offset of the archive from the start of the file. +--- @return integer offset +function reader:get_offset() end + +--- @class miniz.writer +local writer = {} + +--- Copy a file from an existing ZIP archive into this one. +--- @param source miniz.reader The ZIP reader to copy from. +--- @param file_index integer The index of the file to copy from the source ZIP archive. +function writer:add_from_zip(source, file_index) end + +--- Add a file to the ZIP archive. +--- @param path string The path of the file to add. +--- @param data string The data of the file to add. +--- @param level_and_flags? integer The compression level and flags. +--- @param time? integer The modification time of the file. +function writer:add(path, data, level_and_flags, time) end + +--- Finalize the ZIP archive and return the resulting data. After calling this method, the writer should not be used anymore. +--- @return string output +function writer:finalize() end + +--- A stream-oriented zlib decompressor. This can be more efficient when dealing with large data in chunks. +--- @class miniz.inflator +local inflator = {} + +--- Inflate the given compressed data, optionally flushing the stream with the given flush mode. +--- @param data string +--- @param flush? miniz.flush The flush mode to use for this inflate operation. Defaults to 'no'. +--- @return string|nil output +--- @return string|nil err +--- @return integer processed The number of bytes processed from the input. +function inflator:inflate(data, flush) end + +--- A stream-oriented zlib compressor. This can be more efficient when dealing with large data in chunks. +--- @class miniz.deflator +local deflator = {} + +--- Deflate the given uncompressed data, optionally flushing the stream with the given flush mode. +--- @param data string +--- @param flush? miniz.flush The flush mode to use for this deflate operation. Defaults to 'no'. +--- @return string|nil output +--- @return string|nil err +--- @return integer processed The number of bytes processed from the input. +function deflator:deflate(data, flush) end + +return miniz diff --git a/samples/test.app/main.lua b/samples/test.app/main.lua index 0a546ae2..3633b01e 100644 --- a/samples/test.app/main.lua +++ b/samples/test.app/main.lua @@ -175,12 +175,12 @@ do print("miniz zlib compression - full data") local original = string.rep(bundle.readfile("sonnet-133.txt"), 1000) local deflator = miniz.new_deflator(9) - local deflated, err, part = deflator:deflate(original, "finish") - p("Compressed", #(deflated or part or "")) + local deflated, err, read = deflator:deflate(original, "finish") + p("Compressed", #(deflated or ""), read, #original) deflated = assert(deflated, err) local inflator = miniz.new_inflator() - local inflated, err, part = inflator:inflate(deflated) - p("Decompressed", #(inflated or part or "")) + local inflated, err, read = inflator:inflate(deflated) + p("Decompressed", #(inflated or ""), read, #deflated) inflated = assert(inflated, err) assert(inflated == original, "inflated data doesn't match original") end @@ -196,14 +196,14 @@ do local inflator = miniz.new_inflator() for i, part in ipairs(original_parts) do p("part", part) - local deflated, err, partial = deflator:deflate(part, + local deflated, err, read = deflator:deflate(part, i == #original_parts and "finish" or "sync") - p("compressed", deflated, partial) - deflated = assert(not err, err) and (deflated or partial) - local inflated, err, partial = inflator:inflate(deflated, + p("compressed", deflated, read, #part) + deflated = assert(not err, err) and (deflated) + local inflated, err, read = inflator:inflate(deflated, i == #original_parts and "finish" or "sync") - p("decompressed", inflated, partial) - inflated = assert(not err, err) and (inflated or partial) + p("decompressed", inflated, read, #deflated) + inflated = assert(not err, err) and (inflated) assert(inflated == part, "inflated data doesn't match original") end diff --git a/src/lminiz.c b/src/lminiz.c index fb471dbe..75dc94a8 100644 --- a/src/lminiz.c +++ b/src/lminiz.c @@ -15,34 +15,15 @@ * */ +#include #include "./luvi.h" #define MINIZ_NO_ZLIB_COMPATIBLE_NAMES #include "../deps/miniz/miniz.h" -typedef struct { - mz_zip_archive archive; - uv_loop_t *loop; - uv_fs_t req; - uv_file fd; -} lmz_file_t; - -typedef struct { - int mode; // 0 = deflate, 1 = inflate - mz_stream stream; -} lmz_stream_t; - -static size_t lmz_file_read(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) { - lmz_file_t* zip = pOpaque; - const uv_buf_t buf = uv_buf_init(pBuf, n); - file_ofs += mz_zip_get_archive_file_start_offset(&zip->archive); - uv_fs_read(zip->loop, &(zip->req), zip->fd, &buf, 1, file_ofs, NULL); - return zip->req.result; -} - static int lmz_check_compression_level(lua_State* L, int index) { - int level = luaL_optinteger(L, index, MZ_DEFAULT_COMPRESSION); - if (level < MZ_DEFAULT_COMPRESSION || level > MZ_BEST_COMPRESSION) { - luaL_error(L, "Compression level must be between %d and %d", MZ_DEFAULT_COMPRESSION, MZ_BEST_COMPRESSION); + int level = luaL_optinteger(L, index, MZ_DEFAULT_LEVEL); + if (level < MZ_NO_COMPRESSION || level > MZ_BEST_COMPRESSION) { + return luaL_argerror(L, index, "compression level must be between 0 and 9"); } return level; } @@ -50,72 +31,78 @@ static int lmz_check_compression_level(lua_State* L, int index) { static int lmz_reader_init(lua_State* L) { const char* path = luaL_checkstring(L, 1); mz_uint32 flags = luaL_optinteger(L, 2, 0); - mz_uint64 size; - lmz_file_t* zip = lua_newuserdata(L, sizeof(*zip)); - mz_zip_archive* archive = &(zip->archive); + + mz_zip_archive* archive = lua_newuserdata(L, sizeof(*archive)); + memset(archive, 0, sizeof(*archive)); + luaL_getmetatable(L, "miniz_reader"); lua_setmetatable(L, -2); - memset(archive, 0, sizeof(*archive)); - zip->loop = luv_loop(L); - zip->fd = uv_fs_open(zip->loop, &(zip->req), path, O_RDONLY, 0644, NULL); - uv_fs_fstat(zip->loop, &(zip->req), zip->fd, NULL); - size = zip->req.statbuf.st_size; - archive->m_pRead = lmz_file_read; - archive->m_pIO_opaque = zip; - if (!mz_zip_reader_init(archive, size, flags)) { + + if (!mz_zip_reader_init_file(archive, path, flags)) { + const char* msg = mz_zip_get_error_string(mz_zip_get_last_error(archive)); lua_pushnil(L); - lua_pushfstring(L, "read %s fail because of %s", path, - mz_zip_get_error_string(mz_zip_get_last_error(archive))); + lua_pushfstring(L, "read %s failed: %s", path, msg); return 2; } + return 1; } static int lmz_reader_gc(lua_State *L) { - lmz_file_t* zip = luaL_checkudata(L, 1, "miniz_reader"); - uv_fs_close(zip->loop, &(zip->req), zip->fd, NULL); - uv_fs_req_cleanup(&(zip->req)); - mz_zip_reader_end(&(zip->archive)); - return 0; -} - -static int lmz_writer_gc(lua_State *L) { - lmz_file_t* zip = luaL_checkudata(L, 1, "miniz_writer"); - mz_zip_writer_end(&(zip->archive)); + mz_zip_archive* archive = luaL_checkudata(L, 1, "miniz_reader"); + mz_zip_reader_end(archive); return 0; } static int lmz_reader_get_num_files(lua_State *L) { - lmz_file_t* zip = luaL_checkudata(L, 1, "miniz_reader"); - lua_pushinteger(L, mz_zip_reader_get_num_files(&(zip->archive))); + mz_zip_archive* archive = luaL_checkudata(L, 1, "miniz_reader"); + lua_pushinteger(L, mz_zip_reader_get_num_files(archive)); return 1; } static int lmz_reader_locate_file(lua_State *L) { - lmz_file_t* zip = luaL_checkudata(L, 1, "miniz_reader"); + mz_zip_archive* archive = luaL_checkudata(L, 1, "miniz_reader"); const char *path = luaL_checkstring(L, 2); - mz_uint32 flags = luaL_optinteger(L, 3, 0); - int index = mz_zip_reader_locate_file(&(zip->archive), path, NULL, flags); + mz_uint32 flags = 0; + if (lua_isinteger(L, 3)) { + flags = lua_tointeger(L, 3); + } else if (lua_istable(L, 3)) { + lua_getfield(L, 3, "case_sensitive"); + if (lua_toboolean(L, -1)) + flags |= MZ_ZIP_FLAG_CASE_SENSITIVE; + lua_pop(L, 1); + + lua_getfield(L, 3, "ignore_path"); + if (lua_toboolean(L, -1)) + flags |= MZ_ZIP_FLAG_IGNORE_PATH; + lua_pop(L, 1); + } else if (!lua_isnoneornil(L, 3)) { + return luaL_argerror(L, 3, "expected integer or table"); + } + + int index = mz_zip_reader_locate_file(archive, path, NULL, flags); if (index < 0) { lua_pushnil(L); - lua_pushfstring(L, "Can't find file %s.", path); + lua_pushstring(L, mz_zip_get_error_string(mz_zip_get_last_error(archive))); return 2; } + lua_pushinteger(L, index + 1); return 1; } static int lmz_reader_stat(lua_State* L) { - lmz_file_t* zip = luaL_checkudata(L, 1, "miniz_reader"); + mz_zip_archive* archive = luaL_checkudata(L, 1, "miniz_reader"); mz_uint file_index = (mz_uint)luaL_checkinteger(L, 2) - 1; mz_zip_archive_file_stat stat; - if (!mz_zip_reader_file_stat(&(zip->archive), file_index, &stat)) { + if (!mz_zip_reader_file_stat(archive, file_index, &stat)) { lua_pushnil(L); - lua_pushfstring(L, "%d is an invalid index", file_index); + lua_pushstring(L, mz_zip_get_error_string(mz_zip_get_last_error(archive))); return 2; } - lua_newtable(L); - lua_pushinteger(L, file_index); + + lua_createtable(L, 0, 16); + lua_pushinteger(L, file_index + 1); lua_setfield(L, -2, "index"); lua_pushinteger(L, stat.m_version_made_by); lua_setfield(L, -2, "version_made_by"); @@ -141,144 +128,176 @@ static int lmz_reader_stat(lua_State* L) { lua_setfield(L, -2, "filename"); lua_pushstring(L, stat.m_comment); lua_setfield(L, -2, "comment"); + lua_pushboolean(L, stat.m_is_directory); + lua_setfield(L, -2, "is_directory"); return 1; } static int lmz_reader_get_filename(lua_State* L) { - lmz_file_t* zip = luaL_checkudata(L, 1, "miniz_reader"); + mz_zip_archive* archive = luaL_checkudata(L, 1, "miniz_reader"); mz_uint file_index = (mz_uint)luaL_checkinteger(L, 2) - 1; - char pFilename[PATH_MAX]; - mz_uint filename_buf_size = PATH_MAX; - if (!mz_zip_reader_get_filename(&(zip->archive), file_index, pFilename, filename_buf_size)) { + char pFilename[0x1000]; + + if (!mz_zip_reader_get_filename(archive, file_index, pFilename, sizeof(pFilename))) { lua_pushnil(L); - lua_pushfstring(L, "%d is an invalid index", file_index); + lua_pushstring(L, mz_zip_get_error_string(mz_zip_get_last_error(archive))); return 2; } + lua_pushstring(L, pFilename); return 1; } static int lmz_reader_is_file_a_directory(lua_State *L) { - lmz_file_t* zip = luaL_checkudata(L, 1, "miniz_reader"); + mz_zip_archive* archive = luaL_checkudata(L, 1, "miniz_reader"); mz_uint file_index = (mz_uint)luaL_checkinteger(L, 2) - 1; - lua_pushboolean(L, mz_zip_reader_is_file_a_directory(&(zip->archive), file_index)); + + lua_pushboolean(L, mz_zip_reader_is_file_a_directory(archive, file_index)); return 1; } static int lmz_reader_extract(lua_State *L) { - lmz_file_t* zip = luaL_checkudata(L, 1, "miniz_reader"); + mz_zip_archive* archive = luaL_checkudata(L, 1, "miniz_reader"); mz_uint file_index = (mz_uint)luaL_checkinteger(L, 2) - 1; mz_uint flags = luaL_optinteger(L, 3, 0); - size_t out_len; - char* out_buf = mz_zip_reader_extract_to_heap(&(zip->archive), file_index, &out_len, flags); + + size_t out_len = 0; + char* out_buf = mz_zip_reader_extract_to_heap(archive, file_index, &out_len, flags); + if (!out_buf) { + lua_pushnil(L); + lua_pushstring(L, mz_zip_get_error_string(mz_zip_get_last_error(archive))); + return 2; + } + lua_pushlstring(L, out_buf, out_len); free(out_buf); return 1; } static int lmz_reader_get_offset(lua_State *L) { - lmz_file_t* zip = luaL_checkudata(L, 1, "miniz_reader"); - mz_zip_archive* archive = &(zip->archive); + mz_zip_archive* archive = luaL_checkudata(L, 1, "miniz_reader"); lua_pushinteger(L, mz_zip_get_archive_file_start_offset(archive)); return 1; } static int lmz_writer_init(lua_State *L) { - size_t size_to_reserve_at_beginning = luaL_optinteger(L, 1, 0); + size_t initial_reserve_size = luaL_optinteger(L, 1, 0); size_t initial_allocation_size = luaL_optinteger(L, 2, 128 * 1024); - lmz_file_t* zip = lua_newuserdata(L, sizeof(*zip)); - mz_zip_archive* archive = &(zip->archive); + + mz_zip_archive* archive = lua_newuserdata(L, sizeof(*archive)); + memset(archive, 0, sizeof(*archive)); + luaL_getmetatable(L, "miniz_writer"); lua_setmetatable(L, -2); - memset(archive, 0, sizeof(*archive)); - zip->loop = luv_loop(L); - if (!mz_zip_writer_init_heap(archive, size_to_reserve_at_beginning, initial_allocation_size)) { - return luaL_error(L, "Problem initializing heap writer"); + + if (!mz_zip_writer_init_heap(archive, initial_reserve_size, initial_allocation_size)) { + return luaL_error(L, "failed to initialize writer: %s", mz_zip_get_error_string(mz_zip_get_last_error(archive))); } + return 1; } +static int lmz_writer_gc(lua_State *L) { + mz_zip_archive* archive = luaL_checkudata(L, 1, "miniz_writer"); + mz_zip_writer_end(archive); + return 0; +} + static int lmz_writer_add_from_zip_reader(lua_State *L) { - lmz_file_t* zip = luaL_checkudata(L, 1, "miniz_writer"); - lmz_file_t* source = luaL_checkudata(L, 2, "miniz_reader"); + mz_zip_archive* zip = luaL_checkudata(L, 1, "miniz_writer"); + mz_zip_archive* source = luaL_checkudata(L, 2, "miniz_reader"); mz_uint file_index = (mz_uint)luaL_checkinteger(L, 3) - 1; - if (!mz_zip_writer_add_from_zip_reader(&(zip->archive), &(source->archive), file_index)) { - return luaL_error(L, "Failure to copy file between zips"); + + if (!mz_zip_writer_add_from_zip_reader(zip, source, file_index)) { + return luaL_error(L, "failed to add: %s", mz_zip_get_error_string(mz_zip_get_last_error(zip))); } + return 0; } -static int lmz_writer_add_mem(lua_State *L) { - lmz_file_t* zip = luaL_checkudata(L, 1, "miniz_writer"); +static int lmz_writer_add(lua_State *L) { + mz_zip_archive* zip = luaL_checkudata(L, 1, "miniz_writer"); const char* path = luaL_checkstring(L, 2); - size_t size; + size_t size = 0; const char* data = luaL_checklstring(L, 3, &size); - mz_uint flags = luaL_optinteger(L, 4, 0); - if (!mz_zip_writer_add_mem(&(zip->archive), path, data, size, flags)) { - return luaL_error(L, "Failure to add entry to zip"); + mz_uint level_and_flags = luaL_optinteger(L, 4, 0); + MZ_TIME_T mtime = luaL_optinteger(L, 5, 0); + if (mtime == 0) + mtime = time(NULL); + + if (!mz_zip_writer_add_mem_ex_v2(zip, path, data, size, NULL, 0, level_and_flags, 0, 0, &mtime, NULL, 0, NULL, 0)) { + return luaL_error(L, "failed to add: %s", mz_zip_get_error_string(mz_zip_get_last_error(zip))); } + return 0; } + static int lmz_writer_finalize(lua_State *L) { - lmz_file_t* zip = luaL_checkudata(L, 1, "miniz_writer"); + mz_zip_archive* zip = luaL_checkudata(L, 1, "miniz_writer"); void* data; size_t size; - if (!mz_zip_writer_finalize_heap_archive(&(zip->archive), &data, &size)) { - luaL_error(L, "Problem finalizing archive"); + + if (!mz_zip_writer_finalize_heap_archive(zip, &data, &size)) { + return luaL_error(L, "failed to finalize: %s", mz_zip_get_error_string(mz_zip_get_last_error(zip))); } - lua_pushlstring(L, data, size); + + lua_pushlstring(L, (const char*)data, size); + zip->m_pFree(zip->m_pAlloc_opaque, data); return 1; } static int lmz_deflator_init(lua_State* L) { int level = lmz_check_compression_level(L, 1); - lmz_stream_t* stream = lua_newuserdata(L, sizeof(*stream)); - mz_streamp miniz_stream = &(stream->stream); + + mz_streamp stream = lua_newuserdata(L, sizeof(*stream)); + memset(stream, 0, sizeof(*stream)); + luaL_getmetatable(L, "miniz_deflator"); lua_setmetatable(L, -2); - memset(miniz_stream, 0, sizeof(*miniz_stream)); - int status = mz_deflateInit(miniz_stream, level); + + int status = mz_deflateInit(stream, level); if (status != MZ_OK) { const char* msg = mz_error(status); - if (msg) { - luaL_error(L, "Problem initializing stream: %s", msg); - } else { - luaL_error(L, "Problem initializing stream"); + if (!msg) { + msg = "unknown error"; } + + return luaL_error(L, "failed to initialize stream: %s", msg); } - stream->mode = 0; + return 1; } static int lmz_inflator_init(lua_State* L) { - lmz_stream_t* stream = lua_newuserdata(L, sizeof(*stream)); - mz_streamp miniz_stream = &(stream->stream); + mz_streamp stream = lua_newuserdata(L, sizeof(*stream)); + memset(stream, 0, sizeof(*stream)); + luaL_getmetatable(L, "miniz_inflator"); lua_setmetatable(L, -2); - memset(miniz_stream, 0, sizeof(*miniz_stream)); - int status = mz_inflateInit(miniz_stream); + + int status = mz_inflateInit(stream); if (status != MZ_OK) { const char* msg = mz_error(status); - if (msg) { - luaL_error(L, "Problem initializing stream: %s", msg); - } else { - luaL_error(L, "Problem initializing stream"); + if (!msg) { + msg = "unknown error"; } + + return luaL_error(L, "failed to initialize stream: %s", msg); } - stream->mode = 1; + return 1; } static int lmz_deflator_gc(lua_State* L) { - lmz_stream_t* stream = luaL_checkudata(L, 1, "miniz_deflator"); - mz_deflateEnd(&(stream->stream)); + mz_streamp stream = luaL_checkudata(L, 1, "miniz_deflator"); + mz_deflateEnd(stream); return 0; } static int lmz_inflator_gc(lua_State* L) { - lmz_stream_t* stream = luaL_checkudata(L, 1, "miniz_inflator"); - mz_inflateEnd(&(stream->stream)); + mz_streamp stream = luaL_checkudata(L, 1, "miniz_inflator"); + mz_inflateEnd(stream); return 0; } @@ -287,26 +306,25 @@ static const char* flush_types[] = { NULL }; -static int lmz_inflator_deflator_impl(lua_State* L, lmz_stream_t* stream) { - mz_streamp miniz_stream = &(stream->stream); +static int lmz_inflator_deflator_impl(lua_State* L, mz_streamp stream, int inflate) { size_t data_size; const char* data = luaL_checklstring(L, 2, &data_size); int flush = luaL_checkoption(L, 3, "no", flush_types); - miniz_stream->avail_in = data_size; - miniz_stream->next_in = (const unsigned char*)data; + + stream->avail_in = data_size; + stream->next_in = (const unsigned char*)data; + mz_ulong total_in_before = stream->total_in; + luaL_Buffer buf; luaL_buffinit(L, &buf); do { - miniz_stream->avail_out = LUAL_BUFFERSIZE; - miniz_stream->next_out = (unsigned char*)luaL_prepbuffer(&buf); - int status; - size_t before = miniz_stream->total_out; - if (stream->mode) { - status = mz_inflate(miniz_stream, flush); - } else { - status = mz_deflate(miniz_stream, flush); - } - size_t added = miniz_stream->total_out - before; + stream->avail_out = LUAL_BUFFERSIZE; + stream->next_out = (unsigned char*)luaL_prepbuffer(&buf); + + size_t before = stream->total_out; + int status = inflate ? mz_inflate(stream, flush) : mz_deflate(stream, flush); + size_t added = stream->total_out - before; + switch (status) { case MZ_OK: case MZ_STREAM_END: @@ -317,39 +335,93 @@ static int lmz_inflator_deflator_impl(lua_State* L, lmz_stream_t* stream) { default: lua_pushnil(L); lua_pushstring(L, mz_error(status)); - luaL_pushresult(&buf); + lua_pushinteger(L, stream->total_in - total_in_before); return 3; } - } while (miniz_stream->avail_out == 0); + } while (stream->avail_out == 0); luaL_pushresult(&buf); - return 1; + lua_pushnil(L); + lua_pushinteger(L, stream->total_in - total_in_before); + return 3; } static int lmz_deflator_deflate(lua_State* L) { - lmz_stream_t* stream = luaL_checkudata(L, 1, "miniz_deflator"); - return lmz_inflator_deflator_impl(L, stream); + mz_streamp stream = luaL_checkudata(L, 1, "miniz_deflator"); + return lmz_inflator_deflator_impl(L, stream, 0); } + static int lmz_inflator_inflate(lua_State* L) { - lmz_stream_t* stream = luaL_checkudata(L, 1, "miniz_inflator"); - return lmz_inflator_deflator_impl(L, stream); + mz_streamp stream = luaL_checkudata(L, 1, "miniz_inflator"); + return lmz_inflator_deflator_impl(L, stream, 1); } -static int ltinfl(lua_State* L) { - size_t in_len; +static int lmz_inflate(lua_State* L) { + size_t in_len = 0; const char* in_buf = luaL_checklstring(L, 1, &in_len); - size_t out_len; - int flags = luaL_optinteger(L, 2, 0); + int flags = 0; + if (lua_isinteger(L, 2)) { + flags = lua_tointeger(L, 2); + } else if (lua_istable(L, 2)) { + lua_getfield(L, 2, "zlib"); + if (lua_toboolean(L, -1)) + flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; + lua_pop(L, 1); + } else if (!lua_isnoneornil(L, 2)) { + return luaL_argerror(L, 2, "expected table"); + } + + size_t out_len = 0; char* out_buf = tinfl_decompress_mem_to_heap(in_buf, in_len, &out_len, flags); lua_pushlstring(L, out_buf, out_len); free(out_buf); return 1; } -static int ltdefl(lua_State* L) { - size_t in_len; +static int lmz_deflate(lua_State* L) { + size_t in_len = 0; const char* in_buf = luaL_checklstring(L, 1, &in_len); - size_t out_len; - int flags = luaL_optinteger(L, 2, 0); + + int flags = TDEFL_DEFAULT_MAX_PROBES; + if (lua_isinteger(L, 2)) { + flags = lua_tointeger(L, 2); + } else if (lua_istable(L, 2)) { + lua_getfield(L, 2, "probes"); + if (lua_isinteger(L, -1)) { + int hash_probes = lua_tointeger(L, -1); + if (hash_probes < 1 || hash_probes > 0xFFF) { + return luaL_argerror(L, 2, "probes option must be between 1 and 4095"); + } + flags &= ~TDEFL_MAX_PROBES_MASK; + flags |= hash_probes; + } else if (!lua_isnoneornil(L, -1)) { + return luaL_argerror(L, 2, "probes option must be an integer"); + } + lua_pop(L, 1); + + lua_getfield(L, 2, "zlib"); + if (lua_toboolean(L, -1)) + flags |= TDEFL_WRITE_ZLIB_HEADER; + lua_pop(L, 1); + + lua_getfield(L, 2, "filter_matches"); + if (lua_toboolean(L, -1)) + flags |= TDEFL_FILTER_MATCHES; + lua_pop(L, 1); + + lua_getfield(L, 2, "force_static"); + if (lua_toboolean(L, -1)) + flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; + lua_pop(L, 1); + + lua_getfield(L, 2, "force_raw"); + if (lua_toboolean(L, -1)) + flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; + lua_pop(L, 1); + } else if (!lua_isnoneornil(L, 2)) { + return luaL_argerror(L, 2, "expected integer or table"); + } + + size_t out_len = 0; char* out_buf = tdefl_compress_mem_to_heap(in_buf, in_len, &out_len, flags); lua_pushlstring(L, out_buf, out_len); free(out_buf); @@ -357,7 +429,7 @@ static int ltdefl(lua_State* L) { } static int lmz_adler32(lua_State* L) { - mz_ulong adler = luaL_optinteger(L, 1, 1); + mz_ulong adler = luaL_optinteger(L, 1, MZ_ADLER32_INIT); size_t buf_len = 0; const unsigned char* ptr = (const unsigned char*)luaL_optlstring(L, 2, NULL, &buf_len); adler = mz_adler32(adler, ptr, buf_len); @@ -366,7 +438,7 @@ static int lmz_adler32(lua_State* L) { } static int lmz_crc32(lua_State* L) { - mz_ulong crc32 = luaL_optinteger(L, 1, 0); + mz_ulong crc32 = luaL_optinteger(L, 1, MZ_CRC32_INIT); size_t buf_len = 0; const unsigned char* ptr = (const unsigned char*)luaL_optlstring(L, 2, NULL, &buf_len); crc32 = mz_crc32(crc32, ptr, buf_len); @@ -381,16 +453,15 @@ static int lmz_version(lua_State* L) { static int lmz_compress(lua_State* L) { - int level, ret; - size_t in_len, out_len; - const unsigned char *inb; - unsigned char *outb; - in_len = 0; - inb = (const unsigned char *)luaL_checklstring(L, 1, &in_len); - level = lmz_check_compression_level(L, 2); - out_len = mz_compressBound(in_len); - outb = malloc(out_len); - ret = mz_compress2(outb, &out_len, inb, in_len, level); + size_t in_len = 0; + const unsigned char *inb = (const unsigned char *)luaL_checklstring(L, 1, &in_len); + + int level = lmz_check_compression_level(L, 2); + + size_t out_len = mz_compressBound(in_len); + unsigned char *outb = (unsigned char *)malloc(out_len); + + int ret = mz_compress2(outb, &out_len, inb, in_len, level); switch (ret) { case MZ_OK: lua_pushlstring(L, (const char*)outb, out_len); @@ -408,19 +479,19 @@ static int lmz_compress(lua_State* L) static int lmz_uncompress(lua_State* L) { + size_t in_len = 0; + const unsigned char* inb = (const unsigned char*)luaL_checklstring(L, 1, &in_len); + mz_ulong out_len = luaL_optinteger(L, 2, in_len * 2); + if (out_len < in_len) + out_len = in_len; + int ret; - size_t in_len, out_len; - const unsigned char* inb; unsigned char* outb; - in_len = 0; - inb = (const unsigned char*)luaL_checklstring(L, 1, &in_len); - out_len = luaL_optinteger(L, 2, in_len * 2); - if (out_len < 1 || out_len > INT_MAX) { - luaL_error(L, "Initial buffer size must be between 1 and %d", INT_MAX); - } + mz_ulong processed; do { + processed = in_len; outb = malloc(out_len); - ret = mz_uncompress(outb, &out_len, inb, in_len); + ret = mz_uncompress2(outb, &out_len, inb, &processed); if (ret == MZ_BUF_ERROR) { out_len *= 2; free(outb); @@ -428,10 +499,12 @@ static int lmz_uncompress(lua_State* L) break; } } while (out_len > 1 && out_len < INT_MAX); + switch (ret) { case MZ_OK: lua_pushlstring(L, (const char*)outb, out_len); - ret = 1; + lua_pushinteger(L, processed); + ret = 2; break; default: lua_pushnil(L); @@ -443,7 +516,7 @@ static int lmz_uncompress(lua_State* L) return ret; } -static const luaL_Reg lminiz_read_m[] = { +static const luaL_Reg lminiz_reader_m[] = { {"get_num_files", lmz_reader_get_num_files}, {"stat", lmz_reader_stat}, {"get_filename", lmz_reader_get_filename}, @@ -454,19 +527,19 @@ static const luaL_Reg lminiz_read_m[] = { {NULL, NULL} }; -static const luaL_Reg lminiz_write_m[] = { +static const luaL_Reg lminiz_writer_m[] = { {"add_from_zip", lmz_writer_add_from_zip_reader}, - {"add", lmz_writer_add_mem}, + {"add", lmz_writer_add}, {"finalize", lmz_writer_finalize}, {NULL, NULL} }; -static const luaL_Reg lminiz_deflate_m[] = { +static const luaL_Reg lminiz_deflator_m[] = { {"deflate", lmz_deflator_deflate}, {NULL,NULL} }; -static const luaL_Reg lminiz_inflate_m[] = { +static const luaL_Reg lminiz_inflator_m[] = { {"inflate", lmz_inflator_inflate}, {NULL,NULL} }; @@ -474,8 +547,8 @@ static const luaL_Reg lminiz_inflate_m[] = { static const luaL_Reg lminiz_f[] = { {"new_reader", lmz_reader_init}, {"new_writer", lmz_writer_init}, - {"inflate", ltinfl}, - {"deflate", ltdefl}, + {"inflate", lmz_inflate}, + {"deflate", lmz_deflate}, {"adler32", lmz_adler32}, {"crc32", lmz_crc32}, {"compress", lmz_compress}, @@ -488,29 +561,33 @@ static const luaL_Reg lminiz_f[] = { LUALIB_API int luaopen_miniz(lua_State *L) { luaL_newmetatable(L, "miniz_reader"); - luaL_newlib(L, lminiz_read_m); + luaL_newlib(L, lminiz_reader_m); lua_setfield(L, -2, "__index"); lua_pushcfunction(L, lmz_reader_gc); lua_setfield(L, -2, "__gc"); lua_pop(L, 1); + luaL_newmetatable(L, "miniz_writer"); - luaL_newlib(L, lminiz_write_m); + luaL_newlib(L, lminiz_writer_m); lua_setfield(L, -2, "__index"); lua_pushcfunction(L, lmz_writer_gc); lua_setfield(L, -2, "__gc"); lua_pop(L, 1); + luaL_newmetatable(L, "miniz_deflator"); - luaL_newlib(L, lminiz_deflate_m); + luaL_newlib(L, lminiz_deflator_m); lua_setfield(L, -2, "__index"); lua_pushcfunction(L, lmz_deflator_gc); lua_setfield(L, -2, "__gc"); lua_pop(L, 1); + luaL_newmetatable(L, "miniz_inflator"); - luaL_newlib(L, lminiz_inflate_m); + luaL_newlib(L, lminiz_inflator_m); lua_setfield(L, -2, "__index"); lua_pushcfunction(L, lmz_inflator_gc); lua_setfield(L, -2, "__gc"); lua_pop(L, 1); + luaL_newlib(L, lminiz_f); return 1; }