├── lua_zip.def ├── test.zip ├── Makefile ├── .gitignore ├── rockspecs ├── lua-zip-0.1-0.rockspec ├── lua-zip-scm-1.rockspec └── lua-zip-0.2-0.rockspec ├── tap.lua ├── CMakeLists.txt ├── README.txt ├── test.lua └── lua_zip.c /lua_zip.def: -------------------------------------------------------------------------------- 1 | EXPORTS 2 | luaopen_zip 3 | -------------------------------------------------------------------------------- /test.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brimworks/lua-zip/HEAD/test.zip -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # If you put your libzip in a non-standard directory you can 3 | # configure it as such: 4 | # -DLIBZIP_LIBRARY=/opt/libzip/lib/libzip.dylib -DLIBZIP_INCLUDE_DIR=/opt/libzip/include 5 | CMAKE_FLAGS=-H. -Bbuild 6 | 7 | all: build 8 | cmake --build build 9 | 10 | build: 11 | cmake $(CMAKE_FLAGS) 12 | 13 | test: all 14 | cd build && ctest -V 15 | 16 | clean: 17 | rm -rf build 18 | 19 | .PHONY: clean test all 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | brimworks 2 | build 3 | 4 | # Created by https://www.gitignore.io/api/lua 5 | 6 | ### Lua ### 7 | # Compiled Lua sources 8 | luac.out 9 | 10 | # luarocks build files 11 | *.src.rock 12 | *.zip 13 | *.tar.gz 14 | 15 | # Object files 16 | *.o 17 | *.os 18 | *.ko 19 | *.obj 20 | *.elf 21 | 22 | # Precompiled Headers 23 | *.gch 24 | *.pch 25 | 26 | # Libraries 27 | *.lib 28 | *.a 29 | *.la 30 | *.lo 31 | *.def 32 | *.exp 33 | 34 | # Shared objects (inc. Windows DLLs) 35 | *.dll 36 | *.so 37 | *.so.* 38 | *.dylib 39 | 40 | # Executables 41 | *.exe 42 | *.out 43 | *.app 44 | *.i*86 45 | *.x86_64 46 | *.hex 47 | 48 | 49 | 50 | # End of https://www.gitignore.io/api/lua 51 | -------------------------------------------------------------------------------- /rockspecs/lua-zip-0.1-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-zip" 2 | version = "0.1-0" 3 | 4 | description = { 5 | summary = "Lua binding to libzip", 6 | detailed = [[ 7 | lua-zip is a binding to libzip, which you can get from: 8 | http://www.nih.at/libzip/ 9 | ]], 10 | homepage = "https://github.com/brimworks/lua-zip", 11 | license = "MIT" 12 | } 13 | 14 | source = { 15 | url = "git://github.com/brimworks/lua-zip.git", 16 | tag = "v0.1.0" 17 | } 18 | 19 | dependencies = { 20 | "lua" 21 | } 22 | 23 | external_dependencies = { 24 | ZIP = { 25 | header = "zip.h", 26 | library = "zip", 27 | } 28 | } 29 | build = { 30 | type = "builtin", 31 | modules = { 32 | ["brimworks.zip"] = { 33 | sources = { "lua_zip.c" }, 34 | incdirs = { "$(ZIP_INCDIR)" }, 35 | libdirs = { "$(ZIP_LIBDIR)" }, 36 | libraries = { "zip" }, 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /rockspecs/lua-zip-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-zip" 2 | version = "scm-1" 3 | 4 | description = { 5 | summary = "Lua binding to libzip", 6 | detailed = [[ 7 | lua-zip is a binding to libzip, which you can get from: 8 | http://www.nih.at/libzip/ 9 | ]], 10 | homepage = "https://github.com/brimworks/lua-zip", 11 | license = "MIT" 12 | } 13 | 14 | source = { 15 | url = "git://github.com/brimworks/lua-zip.git", 16 | dir = "lua-zip" 17 | } 18 | 19 | dependencies = { 20 | "lua" 21 | } 22 | 23 | external_dependencies = { 24 | ZIP = { 25 | header = "zip.h", 26 | library = "zip", 27 | } 28 | } 29 | build = { 30 | type = "builtin", 31 | modules = { 32 | ["brimworks.zip"] = { 33 | sources = { "lua_zip.c" }, 34 | incdirs = { "$(ZIP_INCDIR)" }, 35 | libdirs = { "$(ZIP_LIBDIR)" }, 36 | libraries = { "zip" }, 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /rockspecs/lua-zip-0.2-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-zip" 2 | version = "0.2-0" 3 | source = { 4 | url = "git://github.com/brimworks/lua-zip.git", 5 | tag = "v0.2.0" 6 | } 7 | description = { 8 | summary = "Lua binding to libzip", 9 | detailed = [[ 10 | lua-zip is a binding to libzip, which you can get from: 11 | http://www.nih.at/libzip/ 12 | ]], 13 | homepage = "https://github.com/brimworks/lua-zip", 14 | license = "MIT" 15 | } 16 | dependencies = { 17 | "lua" 18 | } 19 | external_dependencies = { 20 | ZIP = { 21 | header = "zip.h", 22 | library = "zip" 23 | } 24 | } 25 | build = { 26 | type = "builtin", 27 | modules = { 28 | ["brimworks.zip"] = { 29 | incdirs = { 30 | "$(ZIP_INCDIR)" 31 | }, 32 | libdirs = { 33 | "$(ZIP_LIBDIR)" 34 | }, 35 | libraries = { 36 | "zip" 37 | }, 38 | sources = { 39 | "lua_zip.c" 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tap.lua: -------------------------------------------------------------------------------- 1 | local os = require("os") 2 | 3 | local counter = 1 4 | local failed = false 5 | 6 | local function ok(assert_true, desc) 7 | local msg = ( assert_true and "ok " or "not ok " ) .. counter 8 | if ( not assert_true ) then 9 | failed = true 10 | end 11 | if ( desc ) then 12 | msg = msg .. " - " .. desc 13 | end 14 | print(msg) 15 | counter = counter + 1 16 | end 17 | 18 | local function is_deeply(got, expect, msg, context) 19 | if ( type(expect) ~= "table" ) then 20 | print("# Expected [" .. context .. "] to be a table") 21 | ok(false, msg) 22 | return false 23 | end 24 | for k, v in pairs(expect) do 25 | local ctx 26 | if ( nil == context ) then 27 | ctx = k 28 | else 29 | ctx = context .. "." .. k 30 | end 31 | if type(expect[k]) == "table" then 32 | if ( not is_deeply(got[k], expect[k], msg, ctx) ) then 33 | return false 34 | end 35 | else 36 | if ( got[k] ~= expect[k] ) then 37 | print("# Expected [" .. ctx .. "] to be '" 38 | .. tostring(expect[k]) .. "', but got '" 39 | .. tostring(got[k]) 40 | .. "'") 41 | ok(false, msg) 42 | return false 43 | end 44 | end 45 | end 46 | if ( nil == context ) then 47 | ok(true, msg); 48 | end 49 | return true 50 | end 51 | 52 | local function exit() 53 | os.exit(failed and 1 or 0) 54 | end 55 | 56 | return { 57 | ["ok"] = ok, 58 | ["is_deeply"] = is_deeply, 59 | ["exit"] = exit 60 | } 61 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2007-2009 LuaDist. 2 | # Submitted by David Manura 3 | # Redistribution and use of this file is allowed according to the 4 | # terms of the MIT license. 5 | # For details see the COPYRIGHT file distributed with LuaDist. 6 | # Please note that the package source code is licensed under its own 7 | # license. 8 | 9 | PROJECT(lua-zip C) 10 | CMAKE_MINIMUM_REQUIRED (VERSION 2.6) 11 | 12 | OPTION(LUA_ZIP_BUILD_SHARED "" ON) 13 | IF(LUA_ZIP_BUILD_SHARED) 14 | SET(LUA_ZIP_LIBRARY_TYPE MODULE) 15 | ENDIF() 16 | 17 | # Basic configurations 18 | SET(INSTALL_CMOD share/lua/cmod CACHE PATH "Directory to install Lua binary modules (configure lua via LUA_CPATH)") 19 | # / configs 20 | 21 | # Find libzip 22 | IF(NOT DEFINED LIBZIP_INCLUDE_DIR) 23 | FIND_LIBRARY (LIBZIP_LIBRARY NAMES zip) 24 | FIND_PATH (LIBZIP_INCLUDE_DIR zip.h 25 | PATH_SUFFIXES include/zip include 26 | ) # Find header 27 | INCLUDE(FindPackageHandleStandardArgs) 28 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(libzip DEFAULT_MSG LIBZIP_LIBRARY LIBZIP_INCLUDE_DIR) 29 | ENDIF() 30 | # / Find libzip 31 | 32 | # Find lua 33 | IF(NOT DEFINED LUA_INCLUDE_DIR) 34 | FIND_PACKAGE(Lua REQUIRED) 35 | ENDIF() 36 | # / Find lua 37 | 38 | # Define how to build zip.so: 39 | INCLUDE_DIRECTORIES(${LIBZIP_INCLUDE_DIR} ${LUA_INCLUDE_DIR}) 40 | ADD_LIBRARY(lua_zip ${LUA_ZIP_LIBRARY_TYPE} lua_zip.c lua_zip.def) 41 | SET_TARGET_PROPERTIES(lua_zip PROPERTIES PREFIX "") 42 | SET_TARGET_PROPERTIES(lua_zip PROPERTIES LIBRARY_OUTPUT_DIRECTORY brimworks) 43 | SET_TARGET_PROPERTIES(lua_zip PROPERTIES OUTPUT_NAME zip) 44 | TARGET_LINK_LIBRARIES(lua_zip ${LUA_LIBRARIES} ${LIBZIP_LIBRARY}) 45 | # / build zip.so 46 | 47 | # Define how to test zip.so: 48 | INCLUDE(CTest) 49 | FIND_PROGRAM(LUA NAMES lua lua.bat) 50 | ADD_TEST(basic ${LUA} ${CMAKE_CURRENT_SOURCE_DIR}/test.lua ${CMAKE_CURRENT_BINARY_DIR}) 51 | # / test zip.so 52 | 53 | # Where to install stuff 54 | INSTALL (TARGETS lua_zip DESTINATION ${INSTALL_CMOD}/brimworks) 55 | # / Where to install. 56 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | ********************************************************************** 2 | * Author : Brian Maher 3 | * Library : lua_zip - Lua 5.1 interface to libzip 4 | * 5 | * The MIT License 6 | * 7 | * Copyright (c) 2009 Brian Maher 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | ********************************************************************** 27 | 28 | To use this library, you need libzip, get it here: 29 | http://www.nih.at/libzip/ 30 | 31 | To build this library, you need CMake, get it here: 32 | http://www.cmake.org/cmake/resources/software.html 33 | 34 | Loading the library: 35 | 36 | If you built the library as a loadable package 37 | [local] zip = require 'brimworks.zip' 38 | 39 | If you compiled the package statically into your application, call 40 | the function "luaopen_brimworks_zip(L)". It will create a table 41 | with the zip functions and leave it on the stack. 42 | 43 | Note: 44 | 45 | There is not a "streaming" interface supplied by this library. If 46 | you want to work with zip files as streams, please see 47 | lua-archive. However, libarchive is currently not compatible with 48 | "office open xml", and therefore the author was motivated to write 49 | this zip specific binding. 50 | 51 | Why brimworks prefix? 52 | 53 | When this module was created, there was already a binding to 54 | zziplib named "zip" and since the author owns the brimworks.com 55 | domain he felt prefixing with brimworks would avoid in collisions 56 | in case people need to use the zziplib binding at the same time. 57 | 58 | -- zip functions -- 59 | 60 | zip.CREATE 61 | zip.EXCL 62 | zip.CHECKCONS 63 | 64 | Numbers that represent open "flags", see zip.open(). 65 | 66 | zip.FL_NOCASE 67 | zip.FL_NODIR 68 | 69 | Numbers that represent locate "flags", see zip_arc:name_locate(). 70 | 71 | zip.FL_COMPRESSED 72 | zip.FL_UNCHANGED 73 | 74 | Numbers that represent fopen "flags", see zip_arc:open(). 75 | 76 | flags = zip.OR(flag1[, flag2[, flag3 ...]]) 77 | 78 | Perform a bitwise or on all the flags. 79 | 80 | local zip_arc = zip.open(filename [, flags]) 81 | 82 | Open a zip archive optionally specifying a bitwise or of any of 83 | these flags: 84 | 85 | zip.CREATE 86 | Create the archive if it does not exist. 87 | 88 | zip.EXCL 89 | Error if archive already exists. 90 | 91 | zip.CHECKCONS 92 | Perform additional consistency checks on the archive, and 93 | error if they fail. 94 | 95 | If an error occurs, returns nil plus an error message. 96 | 97 | zip_arc:close() 98 | 99 | If any files within were changed, those changes are written to 100 | disk first. If writing changes fails, zip_arc:close() fails and 101 | archive is left unchanged. If archive contains no files, the file 102 | is completely removed (no empty archive is written). 103 | 104 | Unlike the other functions, this function will "throw" an error if 105 | there is any failure. The reason to be different is that it is 106 | easy to forget to check if close is successful, and a failure to 107 | close is truely an exceptional event. 108 | 109 | NOTE: If a zip_arc object is garbage collected without having 110 | called close(), then the memory associated with that object will 111 | be free'ed, but changes made to the archive are not committed. 112 | 113 | local last_file_idx = zip_arc:get_num_files() 114 | local last_file_idx = #zip_arc 115 | 116 | Return the number of files in this zip archive, and since the 117 | index is one based, it also is the last file index. 118 | 119 | local file_idx = zip_arc:name_locate(filename [, flags]) 120 | 121 | Returns the 1 based index for this file. The flags argument may 122 | be a bitwise or of these flags: 123 | 124 | zip.FL_NOCASE 125 | Ignore case distinctions. 126 | 127 | zip.FL_NODIR 128 | Ignore directory part of file name in archive. 129 | 130 | If it is not found, it returns nil plus an error message. 131 | 132 | local file = zip_arc:open(filename | file_idx [, flags]) 133 | 134 | Returns a new file handle for the specified filename or file 135 | index. The flags argument may be a bitwise or of these flags: 136 | 137 | zip.FL_COMPRESSED 138 | Read the compressed data. Otherwise the data is 139 | uncompressed by file:read(). 140 | 141 | zip.FL_UNCHANGED 142 | Read the original data from the zip archive, ignoring any 143 | changes made to the file. 144 | 145 | zip.FL_NOCASE 146 | zip.FL_NODIR 147 | See zip_arc:name_locate(). 148 | 149 | Note that this file handle can only be used for reading purposes. 150 | 151 | file:close() 152 | 153 | Close a file handle opened by zip_arc:open() 154 | 155 | local str = file:read(num) 156 | 157 | Read at most num characters from the file handle. 158 | 159 | local stat = zip_arc:stat(filename | file_idx [, flags]) 160 | 161 | Obtain information about the specified filename or file index. 162 | The flags may be a bitwise or of these flags: 163 | 164 | zip.FL_UNCHANGED 165 | See zip_arc:open(). 166 | 167 | zip.FL_NOCASE 168 | zip.FL_NODIR 169 | See zip_arc:name_locate(). 170 | 171 | The returned stat table contains the following fields: 172 | 173 | stat.name = name of the file 174 | stat.index = index within archive 175 | stat.crc = crc of file data 176 | stat.size = size of file (uncompressed) 177 | stat.mtime = modification time 178 | stat.comp_size = size of file (compressed) 179 | stat.comp_method = compression method used 180 | stat.encryption_method = encryption method used 181 | 182 | If an error occurs, this function returns nil and an error 183 | message. 184 | 185 | local filename = zip_arc:get_name(file_idx [, flags]) 186 | 187 | Returns the name of the file at the specified file index. The 188 | only valid flag is: 189 | 190 | zip.FL_UNCHANGED 191 | See zip_arc:open(). 192 | 193 | local comment = zip_arc:get_archive_comment([flags]) 194 | 195 | Return any comment contained in the archive. The only valid flag 196 | is: 197 | 198 | zip.FL_UNCHANGED 199 | See zip_arc:open(). 200 | 201 | zip_arc:set_archive_comment(comment) 202 | 203 | Sets the comment of an archive. May throw an error if the comment 204 | exceeds 65,535 bytes. 205 | 206 | local comment = zip_arc:get_file_comment(file_idx [, flags]) 207 | 208 | Return any comment about the specified file. The only valid flag 209 | is: 210 | 211 | zip.FL_UNCHANGED 212 | See zip_arc:open(). 213 | 214 | zip_arc:set_file_comment(file_idx, comment) 215 | 216 | Set the comment for a specified file index within the archive. 217 | Throws an error if input is invalid. 218 | 219 | zip_arc:add_dir(dirname) 220 | 221 | Creates a new directory within the archive. May throw an error if 222 | an entry already exists in the archive with that name or input is 223 | invalid. 224 | 225 | file_idx = zip_arc:add(filename, ...zip_source) 226 | 227 | Adds the specified filename to the archive from the specified 228 | "...zip_source" (see below). 229 | 230 | If an error occurs, throws an error. 231 | 232 | file_idx = zip_arc:replace(file_idx, ...zip_source) 233 | 234 | Replaces the specified file index with a new "...zip_source" 235 | (see below). 236 | 237 | If an error occurs, throws an error. 238 | 239 | zip_arc:rename(filename | file_idx, new_filename) 240 | 241 | Rename the specified file in the archive. May throw an error if 242 | the entry being renamed does not exist. 243 | 244 | zip_arc:delete(filename | file_idx) 245 | 246 | Delete the specified file from the archive. May throw an error if 247 | the specified filename or file index does not exist. 248 | 249 | ..zip_source = "string", str 250 | 251 | The source to use will come from the specified string. 252 | 253 | ...zip_source = "zip", other_zip_arc, file_idx[, flags[, start[, len]]]) 254 | 255 | The "...zip_source" is an archive and file index into that archive 256 | along with an optional flag, start file offset and length. The 257 | flags are an optional bitwise or of: 258 | 259 | zip.FL_UNCHANGED 260 | See zip_arc:open(). 261 | 262 | zip.FL_RECOMPRESS 263 | When adding the data from srcarchive, re-compress it using 264 | the current settings instead of copying the compressed 265 | data. 266 | 267 | Circular zip source references are not allowed. For example, if 268 | you add a file from ar2 into ar1, then you can't add a file from 269 | ar1 to ar2. Here is an example of this error: 270 | 271 | ar1:add("filename.txt", "zip", ar2, 1) 272 | ar2:add("filename.txt", "zip", ar1, 1) -- ERROR! 273 | 274 | 275 | ...zip_source = "file", filename[, start[, len]] 276 | 277 | Create a "zip_source" from a file on disk. Opens filename and 278 | reads len bytes from offset start from it. If len is 0 or -1, the 279 | whole file (starting from start) is used. 280 | 281 | ###################################################################### 282 | TODO: The following functions are not implemented yet: 283 | ###################################################################### 284 | 285 | ...zip_source = "object", obj 286 | 287 | The "...zip_source" is an object with any of these methods: 288 | 289 | success = obj:open() 290 | Prepare for reading. Return true on success, nil on 291 | error. 292 | 293 | str = obj:read(len) 294 | Read len bytes, returning it as a string. Return nil on 295 | error. 296 | 297 | obj:close() 298 | Reading is done. 299 | 300 | stat = obj:stat() 301 | Get meta information for the input data. See 302 | zip_arc:stat() for the table of fields that may be set. 303 | Usually, for uncompressed data, only the mtime and size 304 | fields will need to be set. 305 | 306 | libzip_err, system_err = obj:error() 307 | Get error information. Must return two integers whic 308 | correspond to the libzip error code and system error code 309 | for any error (see above functions that may cause errors) 310 | 311 | -------------------------------------------------------------------------------- /test.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | -- Global symbols: 4 | local _0 = string.sub(debug.getinfo(1,'S').source, 2) 5 | local tap 6 | local zip 7 | local ok 8 | local is_deeply 9 | local test_zip_file 10 | local tmp_dir 11 | 12 | function load_libs(build_dir) 13 | -- Set-up cpath and path properly: 14 | build_dir = build_dir:gsub("(.*)/*", "%1") 15 | local f=io.open(build_dir .. "/brimworks/zip.so", "r") 16 | if ( f ) then 17 | f:close() 18 | package.cpath = build_dir .. "/?.so;" .. package.cpath 19 | end 20 | 21 | package.path = _0:gsub("(.*/)(.*)", "%1?.lua;") .. package.path 22 | 23 | -- Load libraries: 24 | tap = require("tap") 25 | zip = require("brimworks.zip") 26 | ok = tap.ok 27 | is_deeply = tap.is_deeply 28 | 29 | -- Export some globals: 30 | test_zip_file = build_dir .. "/../" .. "test.zip" 31 | tmp_dir = build_dir .. "/test-tmp/" 32 | os.execute("mkdir -p " .. tmp_dir) 33 | end 34 | 35 | load_libs(...) 36 | 37 | 38 | 39 | function main() 40 | test_zip_source_circular() 41 | test_open_close() 42 | test_file_count() 43 | test_name_locate() 44 | test_read_file() 45 | test_stat() 46 | test_get_name() 47 | test_get_archive_comment() 48 | test_set_archive_comment() 49 | test_get_file_comment() 50 | test_set_file_comment() 51 | test_add_dir() 52 | test_add() 53 | test_replace() 54 | test_rename() 55 | test_delete() 56 | test_zip_source() 57 | test_file_source() 58 | end 59 | 60 | function test_file_source() 61 | local test_file_source = tmp_dir .. "test_file_source.zip" 62 | 63 | os.remove(test_file_source) 64 | 65 | local ar = assert(zip.open(test_file_source, 66 | zip.OR(zip.CREATE, zip.EXCL))); 67 | 68 | ar:add("dir/add.txt", "file", _0, 2, 12) 69 | ar:close() 70 | 71 | local ar = assert(zip.open(test_file_source, zip.CHECKCONS)) 72 | ok(1 == #ar, "Archive contains one entry: " .. #ar) 73 | 74 | local file = 75 | assert(ar:open("add.TXT", 76 | zip.OR(zip.FL_NOCASE, zip.FL_NODIR))) 77 | local str = assert(file:read(256)) 78 | ok(str == "/usr/bin/env", str .. " == '/usr/bin/env'") 79 | 80 | file:close() 81 | ar:close() 82 | end 83 | 84 | function test_zip_source_circular() 85 | -- What appens if two archives try to reference each other? Let's 86 | -- just make sure it doesn't crash. 87 | local test_zip_source1 = tmp_dir .. "test_zip_source1_gc.zip" 88 | local test_zip_source2 = tmp_dir .. "test_zip_source2_gc.zip" 89 | 90 | os.remove(test_zip_source1) 91 | os.remove(test_zip_source2) 92 | 93 | local ar1 = assert(zip.open(test_zip_source1, 94 | zip.OR(zip.CREATE, zip.EXCL))); 95 | local ar2 = assert(zip.open(test_zip_source2, 96 | zip.OR(zip.CREATE, zip.EXCL))); 97 | 98 | assert(ar1:add("file.txt", "string", "AR1")) 99 | assert(ar2:add("file.txt", "string", "AR2")) 100 | ar1:close() 101 | ar2:close() 102 | 103 | local ar1 = assert(zip.open(test_zip_source1)) 104 | local ar2 = assert(zip.open(test_zip_source2)) 105 | 106 | assert(ar1:add("other.txt", "zip", ar2, 1)) 107 | local isok, err = pcall(ar2.add, ar2, "other.txt", "zip", ar1, 1) 108 | ok(not isok, "Circular reference is error: " .. err) 109 | end 110 | 111 | function test_zip_source() 112 | local test_zip_source = tmp_dir .. "test_zip_source.zip" 113 | 114 | os.remove(test_zip_source) 115 | 116 | local ar_ro = assert(zip.open(test_zip_file)) 117 | 118 | local ar = assert(zip.open(test_zip_source, 119 | zip.OR(zip.CREATE, zip.EXCL))); 120 | 121 | ar:add("dir/add.txt", "zip", ar_ro, 2, 122 | zip.OR(zip.FL_UNCHANGED, 123 | zip.FL_RECOMPRESS), 124 | 4, 3) 125 | 126 | ar:close() 127 | 128 | ar_ro:close() 129 | 130 | local ar = assert(zip.open(test_zip_source, zip.CHECKCONS)) 131 | ok(1 == #ar, "Archive contains one entry: " .. #ar) 132 | 133 | local file = 134 | assert(ar:open("add.TXT", 135 | zip.OR(zip.FL_NOCASE, zip.FL_NODIR))) 136 | local str = assert(file:read(256)) 137 | ok(str == "two", str .. " == 'two'") 138 | 139 | file:close() 140 | ar:close() 141 | end 142 | 143 | function test_replace() 144 | -- Make sure we start with a clean slate: 145 | local test_replace_file = tmp_dir .. "test_replace.zip" 146 | os.remove(test_replace_file) 147 | local ar = assert(zip.open(test_replace_file, 148 | zip.OR(zip.CREATE, zip.EXCL))); 149 | 150 | local idx = ar:add("dir/test.txt", "string", "Contents") 151 | ar:replace(idx, "string", "Replacement") 152 | 153 | ar:close() 154 | 155 | local ar = assert(zip.open(test_replace_file, zip.CHECKCONS)) 156 | ok(1 == #ar, "Archive contains one entry: " .. #ar) 157 | 158 | local file = 159 | assert(ar:open("test.TXT", 160 | zip.OR(zip.FL_NOCASE, zip.FL_NODIR))) 161 | local str = assert(file:read(256)) 162 | ok(str == "Replacement", tostring(str) .. " == 'Replacement'") 163 | 164 | file:close() 165 | ar:close() 166 | end 167 | 168 | function test_rename() 169 | -- Make sure we start with a clean slate: 170 | local test_rename_file = tmp_dir .. "test_rename.zip" 171 | os.remove(test_rename_file) 172 | local ar = assert(zip.open(test_rename_file, 173 | zip.OR(zip.CREATE, zip.EXCL))); 174 | 175 | local idx = ar:add("dir/test.txt", "string", "Contents") 176 | local err = select(2, pcall(ar.rename, ar, "DNE", "new.txt")) 177 | ok(string.match(err, "No such file"), "Rename non-existant file error="..tostring(err)) 178 | 179 | ar:rename(idx, "temp.txt") 180 | ar:rename("temp.txt", "new.txt") 181 | ar:close() 182 | 183 | local ar = assert(zip.open(test_rename_file, zip.CHECKCONS)) 184 | ok(1 == #ar, "Archive contains one entry: " .. #ar) 185 | 186 | local file = 187 | assert(ar:open("new.TXT", 188 | zip.OR(zip.FL_NOCASE, zip.FL_NODIR))) 189 | local str = assert(file:read(256)) 190 | ok(str == "Contents", tostring(str) .. " == 'Contents'") 191 | 192 | file:close() 193 | ar:close() 194 | end 195 | 196 | function test_delete() 197 | -- Make sure we start with a clean slate: 198 | local test_delete_file = tmp_dir .. "test_delete.zip" 199 | os.remove(test_delete_file) 200 | local ar = assert(zip.open(test_delete_file, 201 | zip.OR(zip.CREATE, zip.EXCL))); 202 | 203 | ar:add_dir(".ignore") 204 | local idx = ar:add("dir/test.txt", "string", "Contents") 205 | local err = select(2, pcall(ar.delete, ar, "DNE")) 206 | ok(string.match(err, "No such file"), "Delete non-existant file error="..tostring(err)) 207 | 208 | -- Delete using the index: 209 | ar:delete(idx) 210 | 211 | -- Delete using the filename: 212 | idx = ar:add("dir/test2.txt", "string", "Content2") 213 | ar:delete("dir/test2.txt") 214 | ar:close() 215 | 216 | local ar = assert(zip.open(test_delete_file, zip.CHECKCONS)) 217 | ok(1 == #ar, "Archive contains one entry: " .. #ar) 218 | 219 | -- TODO: How do you determine if this is a directory? 220 | local sb = ar:stat(".ignore") 221 | 222 | ar:close() 223 | end 224 | 225 | function test_add() 226 | -- Make sure we start with a clean slate: 227 | local test_add_file = tmp_dir .. "test_add.zip" 228 | 229 | os.remove(test_add_file) 230 | local ar = assert(zip.open(test_add_file, 231 | zip.OR(zip.CREATE, zip.EXCL))); 232 | 233 | ar:add("dir/add.txt", "string", "Contents") 234 | 235 | ar:close() 236 | 237 | local ar = assert(zip.open(test_add_file, zip.CHECKCONS)) 238 | ok(1 == #ar, "Archive contains one entry: " .. #ar) 239 | 240 | local file = 241 | assert(ar:open("add.TXT", 242 | zip.OR(zip.FL_NOCASE, zip.FL_NODIR))) 243 | local str = assert(file:read(256)) 244 | ok(str == "Contents", str .. " == 'Contents'") 245 | 246 | file:close() 247 | ar:close() 248 | end 249 | 250 | function test_add_dir() 251 | -- Make sure we start with a clean slate: 252 | local test_add_dir = tmp_dir .. "test_add_dir.zip" 253 | 254 | os.remove(test_add_dir) 255 | local ar = assert(zip.open(test_add_dir, 256 | zip.OR(zip.CREATE, zip.EXCL))); 257 | 258 | ok(1 == ar:add_dir("arbitary/directory/name"), "add_dir returns 1") 259 | 260 | ar:close() 261 | 262 | local ar = assert(zip.open(test_add_dir, zip.CHECKCONS)) 263 | ok(1 == #ar, "Archive contains one entry: " .. #ar) 264 | 265 | -- "/" is always appended to the directory name 266 | ok(1 == ar:name_locate("arbitary/directory/name/"), "dir exists") 267 | ok(nil == ar:name_locate("arbitary/directory/name/", 268 | zip.FL_NODIR), 269 | "name_locate returns nil if FL_NODIR flag is passed") 270 | 271 | ar:close() 272 | end 273 | 274 | function test_set_file_comment() 275 | os.remove("test_set_file_comment.zip") 276 | local ar = assert(zip.open("test_set_file_comment.zip", 277 | zip.OR(zip.CREATE, zip.EXCL))); 278 | 279 | local err = select(2, pcall(ar.set_file_comment, ar, 1, 280 | "test\0fun")) 281 | 282 | -- TODO: Add better testing once we can 'add' archive entries. 283 | ok(err == "Invalid argument", tostring(err) .. " == 'Invalid argument'") 284 | 285 | ar:close() 286 | end 287 | 288 | function test_get_file_comment() 289 | local ar = assert(zip.open(test_zip_file)) 290 | 291 | local comment = ar:get_file_comment(2, zip.FL_UNCHANGED) 292 | 293 | ok("" == comment, "No comment set. TODO: test w/ real comment") 294 | 295 | ar:close() 296 | end 297 | 298 | function test_set_archive_comment() 299 | os.remove("test_set_archive_comment.zip") 300 | local ar = assert(zip.open("test_set_archive_comment.zip", 301 | zip.OR(zip.CREATE, zip.EXCL))); 302 | 303 | ar:set_archive_comment("test fun") 304 | 305 | ok("" == ar:get_archive_comment(zip.FL_UNCHANGED), 306 | "zip.FL_UNCHANGED works") 307 | 308 | ok("test fun" == ar:get_archive_comment(), 309 | tostring(ar:get_archive_comment()) .. " == 'test fun'") 310 | 311 | -- BUG in libzip? Version 1.0.1 of libzip gives an error if you 312 | -- don't add anything to the zip file. 313 | ar:add_dir("something") 314 | ar:close() 315 | end 316 | 317 | function test_get_archive_comment() 318 | local ar = assert(zip.open(test_zip_file)) 319 | 320 | local comment = ar:get_archive_comment(zip.FL_UNCHANGED); 321 | 322 | ok("" == comment, "No comment is set. TODO: test w/ real comment") 323 | 324 | ar:close() 325 | end 326 | 327 | function test_get_name() 328 | local ar = assert(zip.open(test_zip_file)) 329 | 330 | local name = ar:get_name(2, zip.FL_UNCHANGED); 331 | 332 | ok(name == "test/text.txt", tostring(name) .. " == 'test/text.txt'") 333 | 334 | ar:close() 335 | end 336 | 337 | function test_stat() 338 | local ar = assert(zip.open(test_zip_file)) 339 | 340 | local expect = { 341 | name = "test/text.txt", 342 | index = 2, 343 | crc = 635884982, 344 | size = 14, 345 | mtime = 1296450278, 346 | comp_size = 14, 347 | comp_method = 0, 348 | encryption_method = 0, 349 | } 350 | 351 | local stat = 352 | assert(ar:stat("TEXT.TXT", 353 | zip.OR(zip.FL_NOCASE, zip.FL_NODIR))) 354 | 355 | is_deeply(stat, expect, "TEST.TXT stat") 356 | 357 | stat = assert(ar:stat(2)) 358 | 359 | is_deeply(stat, expect, "index 2 stat") 360 | 361 | ar:close() 362 | end 363 | 364 | function test_read_file() 365 | local ar = assert(zip.open(test_zip_file)) 366 | 367 | local file = 368 | assert(ar:open("TEXT.TXT", 369 | zip.OR(zip.FL_NOCASE, zip.FL_NODIR))) 370 | 371 | local str = file:read(256) 372 | ok(str == "one\ntwo\nthree\n", 373 | "[" .. tostring(str) .. "] == [one\ntwo\nthree\n]") 374 | 375 | file:close() 376 | 377 | -- The data at index 2 is not compressed: 378 | file = assert(ar:open(2, zip.FL_COMPRESSED)) 379 | str = file:read(256) 380 | ok(str == "one\ntwo\nthree\n", 381 | "[" .. tostring(str) .. "] == [one\ntwo\nthree\n]") 382 | 383 | ar:close() 384 | 385 | -- Closing the file after the archive was closed! 386 | file:close() 387 | end 388 | 389 | function test_name_locate() 390 | local ar = assert(zip.open(test_zip_file)) 391 | 392 | ok(2 == ar:name_locate("test/text.txt"), 393 | tostring(ar:name_locate("test/text.txt")) .. " == 2"); 394 | 395 | local err = select(2, ar:name_locate("DNE")) 396 | ok(string.match(err, "No such file"), 397 | tostring(err) .. " matches 'No such file'") 398 | 399 | local idx = ar:name_locate("teST/teXT.txt", zip.FL_NOCASE) 400 | ok(2 == idx, tostring(idx) .. " == 2") 401 | 402 | idx = ar:name_locate("teXT.txt", zip.OR(zip.FL_NOCASE, zip.FL_NODIR)) 403 | ok(2 == idx, tostring(idx) .. " == 2") 404 | 405 | ar:close() 406 | end 407 | 408 | function test_file_count() 409 | local ar = assert(zip.open(test_zip_file)) 410 | ok(2 == #ar, tostring(#ar) .. " == 2") 411 | ok(2 == ar:get_num_files(), tostring(ar:get_num_files()) .. " == 2") 412 | ar:close() 413 | end 414 | 415 | function test_open_close() 416 | os.remove("test_open_close.zip") 417 | local ar = assert(zip.open("test_open_close.zip", zip.CREATE)) 418 | ar:close() 419 | 420 | local err = select(2, zip.open("test_open_close.zip")) 421 | ok(string.match(err, "No such file"), 422 | tostring(err) .. " matches 'No such file'") 423 | 424 | ar = assert(zip.open(test_zip_file)) 425 | ar:close() 426 | 427 | do 428 | local ar = assert(zip.open("test_open_close.zip", 429 | zip.OR(zip.CREATE, 430 | zip.EXCL, 431 | zip.CHECKCONS))) 432 | -- Purposfully do not close it so gc will close it. 433 | --ar:close() 434 | end 435 | 436 | collectgarbage"collect" 437 | 438 | ok(true, "test_open_close was successful") 439 | 440 | end 441 | 442 | main() 443 | tap.exit() 444 | -------------------------------------------------------------------------------- /lua_zip.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #if LUA_VERSION_NUM > 502 && !defined(LUA_COMPAT_APIINTCASTS) 9 | #define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n))) 10 | #endif 11 | 12 | #if LUA_VERSION_NUM > 501 13 | #if !defined(LUA_COMPAT_MODULE) 14 | #define luaL_register(L,_,funcs) luaL_setfuncs((L),funcs,0) 15 | #endif 16 | #if !defined(LUA_COMPAT_ALL) || !defined(LUA_COMPAT_5_1) 17 | #define lua_objlen(L,i) lua_rawlen(L, (i)) 18 | #define lua_equal(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPEQ) 19 | #endif 20 | #endif 21 | 22 | #define ARCHIVE_MT "zip{archive}" 23 | #define ARCHIVE_FILE_MT "zip{archive.file}" 24 | #define WEAK_MT "zip{weak}" 25 | 26 | #define check_archive_file(L, narg) \ 27 | ((struct zip_file**)luaL_checkudata((L), (narg), ARCHIVE_FILE_MT)) 28 | 29 | #define absindex(L,i) ((i)>0?(i):lua_gettop(L)+(i)+1) 30 | 31 | static int S_archive_gc(lua_State* L); 32 | static int S_archive_get_num_files(lua_State* L); 33 | 34 | static void stackdump(lua_State* l) 35 | { 36 | int i; 37 | int top = lua_gettop(l); 38 | 39 | for (i = 1; i <= top; i++) 40 | { /* repeat for each level */ 41 | int t = lua_type(l, i); 42 | switch (t) { 43 | case LUA_TSTRING: /* strings */ 44 | fprintf(stderr, "\tstring: '%s'\n", lua_tostring(l, i)); 45 | break; 46 | case LUA_TBOOLEAN: /* booleans */ 47 | fprintf(stderr, "\tboolean %s\n",lua_toboolean(l, i) ? "true" : "false"); 48 | break; 49 | case LUA_TNUMBER: /* numbers */ 50 | fprintf(stderr, "\tnumber: %g\n", lua_tonumber(l, i)); 51 | break; 52 | default: /* other values */ 53 | fprintf(stderr, "\t%s\n", lua_typename(l, t)); 54 | break; 55 | } 56 | } 57 | } 58 | 59 | static struct zip** check_archive(lua_State* L, int narg) { 60 | luaL_checktype(L, narg, LUA_TUSERDATA); 61 | if ( !lua_getmetatable(L, narg) ) { 62 | luaL_argerror(L, narg, "zip{archive} expected (missing metatable)"); 63 | return NULL; 64 | } 65 | lua_getfield(L, -1, "__index"); 66 | luaL_getmetatable(L, ARCHIVE_MT); 67 | if ( !lua_equal(L, -1, -2) ) { 68 | luaL_argerror(L, narg, "zip{archive} expected (__index field incorrect)"); 69 | return NULL; 70 | } 71 | lua_pop(L, 3); 72 | return (struct zip**)lua_touserdata(L, narg); 73 | } 74 | 75 | /* If zip_error is non-zero, then push an appropriate error message 76 | * onto the top of the Lua stack and return zip_error. Otherwise, 77 | * just return 0. 78 | */ 79 | static int S_push_error(lua_State* L, int zip_error, int sys_error) { 80 | char buff[1024]; 81 | if ( 0 == zip_error ) return 0; 82 | 83 | int len = zip_error_to_str(buff, sizeof(buff), zip_error, sys_error); 84 | if ( len >= sizeof(buff) ) len = sizeof(buff)-1; 85 | lua_pushlstring(L, buff, len); 86 | 87 | return zip_error; 88 | } 89 | 90 | static int S_archive_open(lua_State* L) { 91 | const char* path = luaL_checkstring(L, 1); 92 | int flags = (lua_gettop(L) < 2) ? 0 : luaL_checkint(L, 2); 93 | struct zip** ar = (struct zip**)lua_newuserdata(L, sizeof(struct zip*)); 94 | int err = 0; 95 | 96 | *ar = zip_open(path, flags, &err); 97 | 98 | if ( ! *ar ) { 99 | assert(err); 100 | S_push_error(L, err, errno); 101 | lua_pushnil(L); 102 | lua_insert(L, -2); 103 | return 2; 104 | } 105 | 106 | lua_newtable(L); 107 | 108 | luaL_getmetatable(L, ARCHIVE_MT); 109 | assert(!lua_isnil(L, -1)/* ARCHIVE_MT found? */); 110 | lua_setfield(L, -2, "__index"); 111 | 112 | lua_pushcfunction(L, S_archive_gc); 113 | lua_setfield(L, -2, "__gc"); 114 | 115 | lua_pushcfunction(L, S_archive_get_num_files); 116 | lua_setfield(L, -2, "__len"); 117 | 118 | /* Each archive has a weak table of objects that are invalidated 119 | * when the archive is closed or garbage collected. 120 | */ 121 | lua_newtable(L); 122 | luaL_getmetatable(L, WEAK_MT); 123 | assert(!lua_isnil(L, -1)/* WEAK_MT found? */); 124 | lua_setmetatable(L, -2); 125 | lua_setfield(L, -2, "refs"); 126 | 127 | lua_setmetatable(L, -2); 128 | 129 | return 1; 130 | } 131 | 132 | /* Push the refs weak table onto the stack for archive at index ar_idx. 133 | */ 134 | static void S_get_refs(lua_State* L, int ar_idx) { 135 | int ok = lua_getmetatable(L, ar_idx); 136 | assert(ok /* ar_idx has metatable */); 137 | lua_getfield(L, -1, "refs"); 138 | assert(!lua_isnil(L, -1)/* getmetatable(ar_idx).refs must exist! */); 139 | lua_remove(L, -2); 140 | } 141 | 142 | /* Invalidate all "weak" references. This should be done just before 143 | * zip_close() is called. Invalidation occurs by calling __gc 144 | * metamethod. 145 | */ 146 | static void S_archive_gc_refs(lua_State* L, int ar_idx) { 147 | ar_idx = absindex(L, ar_idx); 148 | S_get_refs(L, ar_idx); 149 | 150 | /* NULL this archive to prevent infinite recursion 151 | */ 152 | *((struct zip**)lua_touserdata(L, ar_idx)) = NULL; 153 | 154 | lua_pushnil(L); 155 | while ( lua_next(L, -2) != 0 ) { 156 | /* TODO: Exceptions when calling __gc meta method should be 157 | * handled, otherwise we get a memory leak. 158 | */ 159 | if ( luaL_callmeta(L, -2, "__gc") ) { 160 | lua_pop(L, 2); 161 | } else { 162 | lua_pop(L, 1); 163 | } 164 | } 165 | lua_pop(L, 1); /* Pop the refs */ 166 | } 167 | 168 | /* Adds a reference from the archive at ar_idx to the object at 169 | * obj_idx. This reference will be a "weak" reference if is_weak is 170 | * true, otherwise it will be a normal reference that prevents the 171 | * value from being GC'ed. 172 | * 173 | * We need to keep these references around so we can assert that the 174 | * lifetime of "child" objects is always shorter than the lifetime of 175 | * the "parent" archive object. We also need to assert that any zip 176 | * source has a lifetime that lasts at least until the zip_close() 177 | * function is called. 178 | */ 179 | static void S_archive_add_ref(lua_State* L, int is_weak, int ar_idx, int obj_idx) { 180 | obj_idx = absindex(L, obj_idx); 181 | S_get_refs(L, ar_idx); 182 | 183 | if ( is_weak ) { 184 | lua_pushvalue(L, obj_idx); 185 | lua_pushboolean(L, 1); 186 | lua_rawset(L, -3); 187 | } else { 188 | lua_pushvalue(L, obj_idx); 189 | lua_rawseti(L, -2, lua_objlen(L, -2)+1); 190 | } 191 | 192 | lua_pop(L, 1); /* Pop the refs */ 193 | } 194 | 195 | /* Explicitly close the archive, throwing an error if there are any 196 | * problems. 197 | */ 198 | static int S_archive_close(lua_State* L) { 199 | struct zip* ar = *check_archive(L, 1); 200 | int err; 201 | 202 | if ( ! ar ) return 0; 203 | 204 | S_archive_gc_refs(L, 1); 205 | 206 | err = zip_close(ar); 207 | if ( err != 0 ) { 208 | S_push_error(L, zip_error_code_zip(zip_get_error(ar)), errno); 209 | lua_error(L); 210 | } 211 | 212 | return 0; 213 | } 214 | 215 | /* Try to revert all changes and close the archive since the archive 216 | * was not explicitly closed. 217 | */ 218 | static int S_archive_gc(lua_State* L) { 219 | struct zip* ar = *check_archive(L, 1); 220 | 221 | if ( ! ar ) return 0; 222 | 223 | S_archive_gc_refs(L, 1); 224 | 225 | zip_unchange_all(ar); 226 | zip_close(ar); 227 | 228 | return 0; 229 | } 230 | 231 | static int S_archive_get_num_files(lua_State* L) { 232 | struct zip** ar = check_archive(L, 1); 233 | 234 | if ( ! *ar ) return 0; 235 | 236 | lua_pushinteger(L, zip_get_num_files(*ar)); 237 | 238 | return 1; 239 | } 240 | 241 | static int S_archive_name_locate(lua_State* L) { 242 | struct zip** ar = check_archive(L, 1); 243 | const char* fname = luaL_checkstring(L, 2); 244 | int flags = (lua_gettop(L) < 3) ? 0 : luaL_checkint(L, 3); 245 | int idx; 246 | 247 | if ( ! *ar ) return 0; 248 | 249 | idx = zip_name_locate(*ar, fname, flags); 250 | 251 | if ( idx < 0 ) { 252 | lua_pushnil(L); 253 | lua_pushstring(L, zip_strerror(*ar)); 254 | return 2; 255 | } 256 | 257 | lua_pushinteger(L, idx+1); 258 | return 1; 259 | } 260 | 261 | static int S_archive_stat(lua_State* L) { 262 | struct zip** ar = check_archive(L, 1); 263 | const char* path = (lua_isnumber(L, 2)) ? NULL : luaL_checkstring(L, 2); 264 | int path_idx = (lua_isnumber(L, 2)) ? luaL_checkint(L, 2)-1 : -1; 265 | int flags = (lua_gettop(L) < 3) ? 0 : luaL_checkint(L, 3); 266 | struct zip_stat stat; 267 | int result; 268 | 269 | if ( ! *ar ) return 0; 270 | 271 | if ( NULL == path ) { 272 | result = zip_stat_index(*ar, path_idx, flags, &stat); 273 | } else { 274 | result = zip_stat(*ar, path, flags, &stat); 275 | } 276 | 277 | if ( result != 0 ) { 278 | lua_pushnil(L); 279 | lua_pushstring(L, zip_strerror(*ar)); 280 | return 2; 281 | } 282 | 283 | lua_createtable(L, 0, 8); 284 | 285 | lua_pushstring(L, stat.name); 286 | lua_setfield(L, -2, "name"); 287 | 288 | lua_pushinteger(L, stat.index+1); 289 | lua_setfield(L, -2, "index"); 290 | 291 | lua_pushnumber(L, stat.crc); 292 | lua_setfield(L, -2, "crc"); 293 | 294 | lua_pushnumber(L, stat.size); 295 | lua_setfield(L, -2, "size"); 296 | 297 | lua_pushnumber(L, stat.mtime); 298 | lua_setfield(L, -2, "mtime"); 299 | 300 | lua_pushnumber(L, stat.comp_size); 301 | lua_setfield(L, -2, "comp_size"); 302 | 303 | lua_pushnumber(L, stat.comp_method); 304 | lua_setfield(L, -2, "comp_method"); 305 | 306 | lua_pushnumber(L, stat.encryption_method); 307 | lua_setfield(L, -2, "encryption_method"); 308 | 309 | return 1; 310 | } 311 | 312 | static int S_archive_get_external_attributes(lua_State* L) { 313 | struct zip** ar = check_archive(L, 1); 314 | int path_idx = luaL_checkint(L, 2)-1; 315 | int flags = (lua_gettop(L) < 3) ? 0 : luaL_checkint(L, 3); 316 | 317 | if ( ! *ar ) return 0; 318 | 319 | zip_uint32_t attributes; 320 | int result = zip_file_get_external_attributes(*ar, path_idx, flags, NULL, &attributes); 321 | 322 | if ( result != 0 ) { 323 | lua_pushnil(L); 324 | lua_pushstring(L, zip_strerror(*ar)); 325 | return 2; 326 | } 327 | 328 | lua_pushinteger(L, attributes); 329 | 330 | return 1; 331 | } 332 | 333 | static int S_archive_get_name(lua_State* L) { 334 | struct zip** ar = check_archive(L, 1); 335 | int path_idx = luaL_checkint(L, 2)-1; 336 | int flags = (lua_gettop(L) < 3) ? 0 : luaL_checkint(L, 3); 337 | const char* name; 338 | 339 | if ( ! *ar ) return 0; 340 | 341 | name = zip_get_name(*ar, path_idx, flags); 342 | 343 | if ( NULL == name ) { 344 | lua_pushnil(L); 345 | lua_pushstring(L, zip_strerror(*ar)); 346 | return 2; 347 | } 348 | 349 | lua_pushstring(L, name); 350 | return 1; 351 | } 352 | 353 | static int S_archive_get_archive_comment(lua_State* L) { 354 | struct zip** ar = check_archive(L, 1); 355 | int flags = (lua_gettop(L) < 2) ? 0 : luaL_checkint(L, 2); 356 | const char* comment; 357 | int comment_len; 358 | 359 | if ( ! *ar ) return 0; 360 | 361 | comment = zip_get_archive_comment(*ar, &comment_len, flags); 362 | 363 | if ( NULL == comment ) { 364 | lua_pushnil(L); 365 | } else { 366 | lua_pushlstring(L, comment, comment_len); 367 | } 368 | return 1; 369 | } 370 | 371 | static int S_archive_set_archive_comment(lua_State* L) { 372 | struct zip** ar = check_archive(L, 1); 373 | size_t comment_len = 0; 374 | const char* comment = lua_isnil(L, 2) ? NULL : luaL_checklstring(L, 2, &comment_len); 375 | 376 | if ( ! *ar ) return 0; 377 | 378 | if ( 0 != zip_set_archive_comment(*ar, comment, comment_len) ) { 379 | lua_pushstring(L, zip_strerror(*ar)); 380 | lua_error(L); 381 | } 382 | 383 | return 0; 384 | } 385 | 386 | static int S_archive_get_file_comment(lua_State* L) { 387 | struct zip** ar = check_archive(L, 1); 388 | int path_idx = luaL_checkint(L, 2)-1; 389 | int flags = (lua_gettop(L) < 3) ? 0 : luaL_checkint(L, 3); 390 | const char* comment; 391 | int comment_len; 392 | 393 | if ( ! *ar ) return 0; 394 | 395 | comment = zip_get_file_comment(*ar, path_idx, &comment_len, flags); 396 | 397 | if ( NULL == comment ) { 398 | lua_pushnil(L); 399 | } else { 400 | lua_pushlstring(L, comment, comment_len); 401 | } 402 | return 1; 403 | } 404 | 405 | static int S_archive_set_file_comment(lua_State* L) { 406 | struct zip** ar = check_archive(L, 1); 407 | int path_idx = luaL_checkint(L, 2)-1; 408 | size_t comment_len = 0; 409 | const char* comment = lua_isnil(L, 3) ? NULL : luaL_checklstring(L, 3, &comment_len); 410 | 411 | if ( ! *ar ) return 0; 412 | 413 | if ( 0 != zip_set_file_comment(*ar, path_idx, comment, comment_len) ) { 414 | lua_pushstring(L, zip_strerror(*ar)); 415 | lua_error(L); 416 | } 417 | 418 | return 0; 419 | } 420 | 421 | static int S_archive_add_dir(lua_State* L) { 422 | struct zip** ar = check_archive(L, 1); 423 | const char* path = luaL_checkstring(L, 2); 424 | int idx; 425 | 426 | if ( ! *ar ) return 0; 427 | 428 | 429 | idx = zip_add_dir(*ar, path) + 1; 430 | 431 | if ( 0 == idx ) { 432 | lua_pushstring(L, zip_strerror(*ar)); 433 | lua_error(L); 434 | return 0; 435 | } 436 | 437 | lua_pushinteger(L, idx); 438 | 439 | return 1; 440 | } 441 | 442 | static struct zip_source* S_create_source_string(lua_State* L, struct zip* ar) { 443 | size_t len; 444 | const char* str = luaL_checklstring(L, 4, &len); 445 | struct zip_source* src = zip_source_buffer(ar, str, len, 0); 446 | 447 | if ( NULL != src ) return src; 448 | 449 | S_archive_add_ref(L, 0, 1, 4); 450 | 451 | lua_pushstring(L, zip_strerror(ar)); 452 | lua_error(L); 453 | return NULL; 454 | } 455 | 456 | static struct zip_source* S_create_source_file(lua_State* L, struct zip* ar) { 457 | const char* fname = luaL_checkstring(L, 4); 458 | int start = lua_gettop(L) < 5 ? 0 : luaL_checkint(L, 5); 459 | int len = lua_gettop(L) < 6 ? -1 : luaL_checkint(L, 6); 460 | struct zip_source* src = zip_source_file(ar, fname, start, len); 461 | 462 | if ( NULL != src ) return src; 463 | 464 | lua_pushstring(L, zip_strerror(ar)); 465 | lua_error(L); 466 | return NULL; 467 | } 468 | 469 | static struct zip_source* S_create_source_zip(lua_State* L, struct zip* ar) { 470 | struct zip** other_ar = check_archive(L, 4); 471 | int file_idx = luaL_checkint(L, 5); 472 | int flags = lua_gettop(L) < 6 ? 0 : luaL_checkint(L, 6); 473 | int start = lua_gettop(L) < 7 ? 0 : luaL_checkint(L, 7); 474 | int len = lua_gettop(L) < 8 ? -1 : luaL_checkint(L, 8); 475 | struct zip_source* src = NULL; 476 | 477 | if ( ! *other_ar ) return NULL; 478 | 479 | /* Check for circular reference */ 480 | S_get_refs(L, 1); 481 | lua_pushvalue(L, 4); 482 | lua_rawget(L, -2); 483 | if ( ! lua_isnil(L, -1) ) { 484 | lua_pushliteral(L, "Circular reference of zip sources is not allowed"); 485 | lua_error(L); 486 | } 487 | lua_pop(L, 1); 488 | 489 | /* We store a strong reference to prevent the "other" archive 490 | * from getting prematurely gc'ed, we also have the other archive 491 | * store a weak reference so the __gc metamethod will be called 492 | * if the "other" archive is closed before we are closed. 493 | */ 494 | S_archive_add_ref(L, 0, 1, 4); 495 | S_archive_add_ref(L, 1, 4, 1); 496 | 497 | src = zip_source_zip(ar, *other_ar, file_idx-1, flags, start, len); 498 | if ( NULL != src ) return src; 499 | 500 | lua_pushstring(L, zip_strerror(ar)); 501 | lua_error(L); 502 | return NULL; 503 | } 504 | 505 | typedef struct zip_source* (S_src_t)(lua_State*, struct zip*); 506 | 507 | /* Dispatch to the proper function based on the type string. 508 | */ 509 | static struct zip_source* S_create_source(lua_State* L, struct zip* ar) { 510 | static const char* types[] = { 511 | "file", 512 | "string", 513 | "zip", 514 | }; 515 | static S_src_t* fns[] = { 516 | &S_create_source_file, 517 | &S_create_source_string, 518 | &S_create_source_zip, 519 | }; 520 | if ( NULL == ar ) return NULL; 521 | return fns[luaL_checkoption(L, 3, NULL, types)](L, ar); 522 | } 523 | 524 | static int S_archive_replace(lua_State* L) { 525 | struct zip** ar = check_archive(L, 1); 526 | int idx = luaL_checkinteger(L, 2); 527 | struct zip_source* src = S_create_source(L, *ar); 528 | 529 | if ( ! *ar ) return 0; 530 | 531 | idx = zip_replace(*ar, idx-1, src) + 1; 532 | 533 | if ( 0 == idx ) { 534 | zip_source_free(src); 535 | lua_pushstring(L, zip_strerror(*ar)); 536 | lua_error(L); 537 | } 538 | 539 | S_archive_add_ref(L, 0, 1, 4); 540 | 541 | lua_pushinteger(L, idx); 542 | 543 | return 1; 544 | } 545 | 546 | static int S_archive_rename(lua_State* L) { 547 | struct zip** ar = check_archive(L, 1); 548 | const char* path = (lua_isnumber(L, 2)) ? NULL : luaL_checkstring(L, 2); 549 | int path_idx = (lua_isnumber(L, 2)) ? luaL_checkint(L, 2)-1 : -1; 550 | const char* new_path = luaL_checkstring(L, 3); 551 | 552 | if ( ! *ar ) return 0; 553 | 554 | if ( NULL != path ) { 555 | path_idx = zip_name_locate(*ar, path, 0); 556 | if ( path_idx < 0 ) { 557 | lua_pushfstring(L, "%s '%s'", zip_strerror(*ar), path); 558 | lua_error(L); 559 | } 560 | } 561 | if ( zip_rename(*ar, path_idx, new_path) != 0 ) { 562 | lua_pushstring(L, zip_strerror(*ar)); 563 | lua_error(L); 564 | } 565 | return 0; 566 | } 567 | 568 | static int S_archive_delete(lua_State* L) { 569 | struct zip** ar = check_archive(L, 1); 570 | const char* path = (lua_isnumber(L, 2)) ? NULL : luaL_checkstring(L, 2); 571 | int path_idx = (lua_isnumber(L, 2)) ? luaL_checkint(L, 2)-1 : -1; 572 | 573 | if ( ! *ar ) return 0; 574 | 575 | if ( NULL != path ) { 576 | path_idx = zip_name_locate(*ar, path, 0); 577 | if ( path_idx < 0 ) { 578 | lua_pushfstring(L, "%s '%s'", zip_strerror(*ar), path); 579 | lua_error(L); 580 | } 581 | } 582 | if ( zip_delete(*ar, path_idx) != 0 ) { 583 | lua_pushstring(L, zip_strerror(*ar)); 584 | lua_error(L); 585 | } 586 | return 0; 587 | } 588 | 589 | static int S_archive_add(lua_State* L) { 590 | struct zip** ar = check_archive(L, 1); 591 | const char* path = luaL_checkstring(L, 2); 592 | struct zip_source* src = S_create_source(L, *ar); 593 | int idx; 594 | 595 | if ( ! *ar ) return 0; 596 | 597 | idx = zip_add(*ar, path, src) + 1; 598 | 599 | if ( 0 == idx ) { 600 | zip_source_free(src); 601 | lua_pushstring(L, zip_strerror(*ar)); 602 | lua_error(L); 603 | } 604 | 605 | S_archive_add_ref(L, 0, 1, 4); 606 | 607 | lua_pushinteger(L, idx); 608 | 609 | return 1; 610 | } 611 | 612 | static int S_archive_file_open(lua_State* L) { 613 | struct zip** ar = check_archive(L, 1); 614 | const char* path = (lua_isnumber(L, 2)) ? NULL : luaL_checkstring(L, 2); 615 | int path_idx = (lua_isnumber(L, 2)) ? luaL_checkint(L, 2)-1 : -1; 616 | int flags = (lua_gettop(L) < 3) ? 0 : luaL_checkint(L, 3); 617 | struct zip_file** file = (struct zip_file**) 618 | lua_newuserdata(L, sizeof(struct zip_file*)); 619 | 620 | *file = NULL; 621 | 622 | if ( ! *ar ) return 0; 623 | 624 | if ( NULL == path ) { 625 | *file = zip_fopen_index(*ar, path_idx, flags); 626 | } else { 627 | *file = zip_fopen(*ar, path, flags); 628 | } 629 | 630 | if ( ! *file ) { 631 | lua_pushnil(L); 632 | lua_pushstring(L, zip_strerror(*ar)); 633 | return 2; 634 | } 635 | 636 | luaL_getmetatable(L, ARCHIVE_FILE_MT); 637 | assert(!lua_isnil(L, -1)/* ARCHIVE_FILE_MT found? */); 638 | 639 | lua_setmetatable(L, -2); 640 | 641 | S_archive_add_ref(L, 1, 1, -1); 642 | 643 | return 1; 644 | } 645 | 646 | static int S_archive_file_close(lua_State* L) { 647 | struct zip_file** file = check_archive_file(L, 1); 648 | int err; 649 | 650 | if ( ! *file ) return 0; 651 | 652 | err = zip_fclose(*file); 653 | *file = NULL; 654 | 655 | if ( err ) { 656 | S_push_error(L, err, errno); 657 | lua_error(L); 658 | } 659 | 660 | return 0; 661 | } 662 | 663 | static int S_archive_file_gc(lua_State* L) { 664 | struct zip_file** file = check_archive_file(L, 1); 665 | 666 | if ( ! *file ) return 0; 667 | 668 | zip_fclose(*file); 669 | *file = NULL; 670 | 671 | return 0; 672 | } 673 | 674 | static int S_archive_file_read(lua_State* L) { 675 | struct zip_file** file = check_archive_file(L, 1); 676 | int len = luaL_checkint(L, 2); 677 | char* buff; 678 | 679 | if ( len <= 0 ) luaL_argerror(L, 2, "Must be > 0"); 680 | 681 | if ( ! *file ) return 0; 682 | 683 | buff = (char*)lua_newuserdata(L, len); 684 | 685 | len = zip_fread(*file, buff, len); 686 | 687 | if ( -1 == len ) { 688 | lua_pushnil(L); 689 | lua_pushstring(L, zip_file_strerror(*file)); 690 | return 2; 691 | } 692 | 693 | lua_pushlstring(L, buff, len); 694 | return 1; 695 | } 696 | 697 | static void S_register_archive(lua_State* L) { 698 | luaL_newmetatable(L, ARCHIVE_MT); 699 | 700 | lua_pushcfunction(L, S_archive_close); 701 | lua_setfield(L, -2, "close"); 702 | 703 | lua_pushcfunction(L, S_archive_get_num_files); 704 | lua_setfield(L, -2, "get_num_files"); 705 | 706 | lua_pushcfunction(L, S_archive_name_locate); 707 | lua_setfield(L, -2, "name_locate"); 708 | 709 | lua_pushcfunction(L, S_archive_file_open); 710 | lua_setfield(L, -2, "open"); 711 | 712 | lua_pushcfunction(L, S_archive_stat); 713 | lua_setfield(L, -2, "stat"); 714 | 715 | lua_pushcfunction(L, S_archive_get_external_attributes); 716 | lua_setfield(L, -2, "get_external_attributes"); 717 | 718 | lua_pushcfunction(L, S_archive_get_name); 719 | lua_setfield(L, -2, "get_name"); 720 | 721 | lua_pushcfunction(L, S_archive_get_archive_comment); 722 | lua_setfield(L, -2, "get_archive_comment"); 723 | 724 | lua_pushcfunction(L, S_archive_set_archive_comment); 725 | lua_setfield(L, -2, "set_archive_comment"); 726 | 727 | lua_pushcfunction(L, S_archive_get_file_comment); 728 | lua_setfield(L, -2, "get_file_comment"); 729 | 730 | lua_pushcfunction(L, S_archive_set_file_comment); 731 | lua_setfield(L, -2, "set_file_comment"); 732 | 733 | lua_pushcfunction(L, S_archive_add_dir); 734 | lua_setfield(L, -2, "add_dir"); 735 | 736 | lua_pushcfunction(L, S_archive_add); 737 | lua_setfield(L, -2, "add"); 738 | 739 | lua_pushcfunction(L, S_archive_replace); 740 | lua_setfield(L, -2, "replace"); 741 | 742 | lua_pushcfunction(L, S_archive_rename); 743 | lua_setfield(L, -2, "rename"); 744 | 745 | lua_pushcfunction(L, S_archive_delete); 746 | lua_setfield(L, -2, "delete"); 747 | 748 | lua_pop(L, 1); 749 | } 750 | 751 | static void S_register_archive_file(lua_State* L) { 752 | luaL_newmetatable(L, ARCHIVE_FILE_MT); 753 | 754 | lua_pushvalue(L, -1); 755 | lua_setfield(L, -2, "__index"); 756 | 757 | lua_pushcfunction(L, S_archive_file_close); 758 | lua_setfield(L, -2, "close"); 759 | 760 | lua_pushcfunction(L, S_archive_file_gc); 761 | lua_setfield(L, -2, "__gc"); 762 | 763 | lua_pushcfunction(L, S_archive_file_read); 764 | lua_setfield(L, -2, "read"); 765 | 766 | lua_pop(L, 1); 767 | } 768 | 769 | static void S_register_weak(lua_State* L) { 770 | luaL_newmetatable(L, WEAK_MT); 771 | 772 | lua_pushvalue(L, -1); 773 | lua_setfield(L, -2, "__index"); 774 | 775 | lua_pushliteral(L, "k"); 776 | lua_setfield(L, -2, "__mode"); 777 | 778 | lua_pop(L, 1); 779 | } 780 | 781 | static int S_OR(lua_State* L) { 782 | int result = 0; 783 | int top = lua_gettop(L); 784 | while ( top ) { 785 | result |= luaL_checkint(L, top--); 786 | } 787 | lua_pushinteger(L, result); 788 | return 1; 789 | } 790 | 791 | LUALIB_API int luaopen_brimworks_zip(lua_State* L) { 792 | static luaL_Reg fns[] = { 793 | { "open", S_archive_open }, 794 | { "OR", S_OR }, 795 | { NULL, NULL } 796 | }; 797 | 798 | lua_newtable(L); 799 | luaL_register(L, NULL, fns); 800 | 801 | #define EXPORT_CONSTANT(NAME) \ 802 | lua_pushnumber(L, ZIP_##NAME); \ 803 | lua_setfield(L, -2, #NAME) 804 | 805 | EXPORT_CONSTANT(CREATE); 806 | EXPORT_CONSTANT(EXCL); 807 | EXPORT_CONSTANT(CHECKCONS); 808 | EXPORT_CONSTANT(FL_NOCASE); 809 | EXPORT_CONSTANT(FL_NODIR); 810 | EXPORT_CONSTANT(FL_COMPRESSED); 811 | EXPORT_CONSTANT(FL_UNCHANGED); 812 | EXPORT_CONSTANT(FL_RECOMPRESS); 813 | 814 | S_register_archive(L); 815 | S_register_archive_file(L); 816 | S_register_weak(L); 817 | 818 | return 1; 819 | } 820 | --------------------------------------------------------------------------------