]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Minor] Add tests for encrypted zip support + fix some memory issues
authorVsevolod Stakhov <vsevolod@rspamd.com>
Tue, 23 Sep 2025 10:51:20 +0000 (11:51 +0100)
committerVsevolod Stakhov <vsevolod@rspamd.com>
Tue, 23 Sep 2025 10:51:20 +0000 (11:51 +0100)
src/lua/lua_archive.c
test/lua/unit/archive.lua

index 9bddb6ed3d96701cc2aa85ab012cd8de9b8a57ec..fa67d60617472db5236dfb021e0789099d10e672 100644 (file)
@@ -202,8 +202,14 @@ lua_archive_zip(lua_State *L)
        return lua_archive_pack(L);
 }
 
-/**
- * zip_encrypt(files, password) -> text
+/***
+ * @function archive.zip_encrypt(files[, password])
+ * Create a ZIP archive in-memory using Rspamd ZIP writer.
+ * If password is provided and non-empty, entries are encrypted via WinZip AES (AE-2).
+ * - AES-256-CTR with HMAC-SHA1 (10-byte tag), interoperable with 7-Zip/WinZip/libarchive
+ * @param {table} files array: { name = string, content = string|rspamd_text, [mode|perms] = int, [mtime] = int }
+ * @param {string} password optional password string
+ * @return {text} archive bytes
  */
 static int
 lua_archive_zip_encrypt(lua_State *L)
@@ -529,9 +535,11 @@ lua_archive_pack(lua_State *L)
        wctx.buf = g_byte_array_new();
 
        if (archive_write_open(a, &wctx, lua_archive_write_open, lua_archive_write_cb, lua_archive_write_close) != ARCHIVE_OK) {
+               const char *aerr = archive_error_string(a);
+               lua_pushfstring(L, "cannot open archive writer: %s", aerr ? aerr : "unknown error");
                g_byte_array_free(wctx.buf, TRUE);
                archive_write_free(a);
-               return luaL_error(L, "cannot open archive writer: %s", archive_error_string(a));
+               return lua_error(L);
        }
 
        /* Iterate files table */
@@ -556,21 +564,23 @@ lua_archive_pack(lua_State *L)
 
                int r = archive_write_header(a, ae);
                if (r != ARCHIVE_OK) {
-                       const char *err = archive_error_string(a);
+                       const char *aerr = archive_error_string(a);
+                       lua_pushfstring(L, "cannot write header: %s", aerr ? aerr : "unknown error");
                        archive_entry_free(ae);
                        archive_write_free(a);
                        g_byte_array_free(wctx.buf, TRUE);
-                       return luaL_error(L, "cannot write header: %s", err ? err : "unknown error");
+                       return lua_error(L);
                }
 
                if (dlen > 0) {
                        la_ssize_t wr = archive_write_data(a, data, dlen);
                        if (wr < 0 || (size_t) wr != dlen) {
-                               const char *err = archive_error_string(a);
+                               const char *aerr = archive_error_string(a);
+                               lua_pushfstring(L, "cannot write data: %s", aerr ? aerr : "unknown error");
                                archive_entry_free(ae);
                                archive_write_free(a);
                                g_byte_array_free(wctx.buf, TRUE);
-                               return luaL_error(L, "cannot write data: %s", err ? err : "unknown error");
+                               return lua_error(L);
                        }
                }
 
@@ -631,9 +641,10 @@ lua_archive_unpack(lua_State *L)
 
        int r = archive_read_open_memory(a, t->start, t->len);
        if (r != ARCHIVE_OK) {
-               const char *err = archive_error_string(a);
+               const char *aerr = archive_error_string(a);
+               lua_pushfstring(L, "cannot open archive: %s", aerr ? aerr : "unknown error");
                archive_read_free(a);
-               return luaL_error(L, "cannot open archive: %s", err ? err : "unknown error");
+               return lua_error(L);
        }
 
        lua_newtable(L);
@@ -659,10 +670,11 @@ lua_archive_unpack(lua_State *L)
                                        break;
                                }
                                else if (rr < 0) {
-                                       const char *err = archive_error_string(a);
+                                       const char *aerr = archive_error_string(a);
+                                       lua_pushfstring(L, "cannot read data: %s", aerr ? aerr : "unknown error");
                                        g_byte_array_free(ba, TRUE);
                                        archive_read_free(a);
-                                       return luaL_error(L, "cannot read data: %s", err ? err : "unknown error");
+                                       return lua_error(L);
                                }
                                g_byte_array_append(ba, (const guint8 *) buf, (guint) rr);
                        }
index 5c38c400503724f988d2fcb37da151243a483d38..fe194322c92a4bf5c0ad6dcf8d8d8546fd663472 100644 (file)
@@ -32,6 +32,35 @@ context("Lua archive bindings", function()
     assert_rspamd_eq({ actual = out[1].content, expect = rnd })
   end)
 
+  test("zip_encrypt without password == plain zip", function()
+    local files = {
+      { name = "a.txt", content = "Hello" },
+    }
+    local blob = archive.zip_encrypt(files) -- no password
+    assert_equal(type(blob), "userdata")
+    local out = archive.unzip(blob)
+    assert_equal(#out, 1)
+    assert_equal(out[1].name, "a.txt")
+    assert_rspamd_eq({ actual = out[1].content, expect = rspamd_text.fromstring("Hello") })
+  end)
+
+  test("zip_encrypt with password (AE-2) roundtrip via libarchive", function()
+    local files = {
+      { name = "dir/x.txt", content = "secret" },
+      { name = "y.bin",     content = rspamd_text.fromstring("\001\002\003") },
+    }
+    local pwd = "testpass123"
+    local blob = archive.zip_encrypt(files, pwd)
+    assert_equal(type(blob), "userdata")
+    -- libarchive can read AE-2, so unpack should succeed and yield the same files
+    local out = archive.unpack(blob, "zip")
+    assert_equal(#out, 2)
+    local names = {}
+    for _, f in ipairs(out) do names[f.name] = f.content end
+    assert_rspamd_eq({ actual = names["dir/x.txt"], expect = rspamd_text.fromstring("secret") })
+    assert_rspamd_eq({ actual = names["y.bin"], expect = rspamd_text.fromstring("\001\002\003") })
+  end)
+
   test("tar/untar helpers roundtrip (no compression)", function()
     local files = {
       { name = "x.txt", content = "X" },