From 4713f58427799f9d4974380f02421862878f4cf6 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 13 Feb 2026 22:07:49 -0500 Subject: [PATCH 1/3] feat: Add macOS support by renaming `index.ios.ts` to `index.apple.ts` and updating `nativescript.config.ts` and `package.json`. --- packages/zip/{index.ios.ts => index.apple.ts} | 0 packages/zip/nativescript.config.ts | 10 ++++++++++ packages/zip/package.json | 2 ++ 3 files changed, 12 insertions(+) rename packages/zip/{index.ios.ts => index.apple.ts} (100%) diff --git a/packages/zip/index.ios.ts b/packages/zip/index.apple.ts similarity index 100% rename from packages/zip/index.ios.ts rename to packages/zip/index.apple.ts diff --git a/packages/zip/nativescript.config.ts b/packages/zip/nativescript.config.ts index 9ae44336..dc5cff50 100644 --- a/packages/zip/nativescript.config.ts +++ b/packages/zip/nativescript.config.ts @@ -11,4 +11,14 @@ export default { }, ], }, + macos: { + SPMPackages: [ + { + name: 'ZipArchive', + libs: ['ZipArchive'], + repositoryURL: 'https://github.com/NathanWalker/ZipArchive', + version: '3.0.0', + }, + ], + }, } as NativeScriptConfig; diff --git a/packages/zip/package.json b/packages/zip/package.json index 87ee2b9c..4be2d22a 100644 --- a/packages/zip/package.json +++ b/packages/zip/package.json @@ -7,6 +7,7 @@ "nativescript": { "platforms": { "ios": "6.0.0", + "macos": "6.0.0", "android": "6.0.0" } }, @@ -19,6 +20,7 @@ "JavaScript", "TypeScript", "iOS", + "macOS", "Android" ], "author": { From 6a2496fed8631774cadb1835bc2ccca472d5eb63 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Fri, 13 Feb 2026 19:15:56 -0800 Subject: [PATCH 2/3] chore: zip 7.1.0-alpha.0 --- packages/zip/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zip/package.json b/packages/zip/package.json index 4be2d22a..56c579fb 100644 --- a/packages/zip/package.json +++ b/packages/zip/package.json @@ -1,6 +1,6 @@ { "name": "@nativescript/zip", - "version": "7.0.1", + "version": "7.1.0-alpha.0", "description": "A simple utility class for zipping and unzipping files in NativeScript", "main": "index", "typings": "index.d.ts", From fe03b818978e65e2e2bc6b69cc0cbf6070997fed Mon Sep 17 00:00:00 2001 From: Osei Fortune Date: Sat, 14 Nov 2026 14:12:53 -0400 Subject: [PATCH 3/3] feat(zip): windows and linux support Windows: managed C# engine (platforms/windows/src/ZipArchiver.cs) on .NET DeflateStream and BCL cryptography, running off the JS thread. Linux: native engine (platforms/linux) on a vendored, pinned minizip-ng 4.2.2 with system zlib, GLib and OpenSSL libcrypto. Work runs on a GThread and reports progress/completion on the GLib main context. Both stream data (constant memory, ZIP64), write WinZip AES-256 when a password is given, read AES and ZipCrypto archives, and extract into temporary files that are only moved into place after the size, CRC and AES authentication checks pass. --- packages/zip/README.md | 26 +- packages/zip/index.linux.ts | 68 + packages/zip/index.windows.ts | 57 + packages/zip/package.json | 8 +- packages/zip/platforms/linux/include/nszip.h | 28 + packages/zip/platforms/linux/native.json | 4 + .../platforms/linux/src/minizip-ng/LICENSE | 17 + .../linux/src/minizip-ng/VENDORED.md | 7 + .../zip/platforms/linux/src/minizip-ng/mz.h | 277 ++ .../linux/src/minizip-ng/mz_config.h | 12 + .../platforms/linux/src/minizip-ng/mz_crypt.c | 182 ++ .../platforms/linux/src/minizip-ng/mz_crypt.h | 65 + .../linux/src/minizip-ng/mz_crypt_openssl.c | 728 +++++ .../platforms/linux/src/minizip-ng/mz_os.c | 495 +++ .../platforms/linux/src/minizip-ng/mz_os.h | 201 ++ .../linux/src/minizip-ng/mz_os_posix.c | 445 +++ .../platforms/linux/src/minizip-ng/mz_strm.c | 545 ++++ .../platforms/linux/src/minizip-ng/mz_strm.h | 135 + .../linux/src/minizip-ng/mz_strm_buf.c | 381 +++ .../linux/src/minizip-ng/mz_strm_buf.h | 42 + .../linux/src/minizip-ng/mz_strm_mem.c | 265 ++ .../linux/src/minizip-ng/mz_strm_mem.h | 48 + .../linux/src/minizip-ng/mz_strm_os.h | 40 + .../linux/src/minizip-ng/mz_strm_os_posix.c | 212 ++ .../linux/src/minizip-ng/mz_strm_pkcrypt.c | 324 ++ .../linux/src/minizip-ng/mz_strm_pkcrypt.h | 46 + .../linux/src/minizip-ng/mz_strm_wzaes.c | 355 +++ .../linux/src/minizip-ng/mz_strm_wzaes.h | 46 + .../linux/src/minizip-ng/mz_strm_zlib.c | 378 +++ .../linux/src/minizip-ng/mz_strm_zlib.h | 43 + .../platforms/linux/src/minizip-ng/mz_zip.c | 2840 +++++++++++++++++ .../platforms/linux/src/minizip-ng/mz_zip.h | 257 ++ packages/zip/platforms/linux/src/nszip.c | 818 +++++ .../zip/platforms/linux/src/nszip_internal.h | 13 + .../zip/platforms/windows/src/ZipArchiver.cs | 1242 +++++++ 35 files changed, 10647 insertions(+), 3 deletions(-) create mode 100644 packages/zip/index.linux.ts create mode 100644 packages/zip/index.windows.ts create mode 100644 packages/zip/platforms/linux/include/nszip.h create mode 100644 packages/zip/platforms/linux/native.json create mode 100644 packages/zip/platforms/linux/src/minizip-ng/LICENSE create mode 100644 packages/zip/platforms/linux/src/minizip-ng/VENDORED.md create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz.h create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_config.h create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_crypt.c create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_crypt.h create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_crypt_openssl.c create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_os.c create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_os.h create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_os_posix.c create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_strm.c create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_strm.h create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_strm_buf.c create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_strm_buf.h create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_strm_mem.c create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_strm_mem.h create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_strm_os.h create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_strm_os_posix.c create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_strm_pkcrypt.c create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_strm_pkcrypt.h create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_strm_wzaes.c create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_strm_wzaes.h create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_strm_zlib.c create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_strm_zlib.h create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_zip.c create mode 100644 packages/zip/platforms/linux/src/minizip-ng/mz_zip.h create mode 100644 packages/zip/platforms/linux/src/nszip.c create mode 100644 packages/zip/platforms/linux/src/nszip_internal.h create mode 100644 packages/zip/platforms/windows/src/ZipArchiver.cs diff --git a/packages/zip/README.md b/packages/zip/README.md index 7b51af9c..54ce1cf0 100644 --- a/packages/zip/README.md +++ b/packages/zip/README.md @@ -11,6 +11,7 @@ A plugin to zip and unzip files. * [Zip a folder](#zip-a-folder) * [Get zip request progress](#get-zip-request-progress) * [Get unzip request progress](#get-unzip-request-progress) +* [Platform notes](#platform-notes) * [API](#api) * [Zip class](#zip-class) * [debugEnabled](#debugenabled) @@ -113,6 +114,29 @@ function onUnZipProgress(percent: number) { } ``` +## Platform notes + +Supported on iOS, macOS, Android, Windows and Linux. + +**Windows** uses a built-in C# engine, compiled into the app, that relies on .NET's `DeflateStream` and cryptography APIs. It needs no extra dependencies. + +**Linux** uses a bundled, pinned copy of [minizip-ng](https://github.com/zlib-ng/minizip-ng), linked against the system zlib, GLib and OpenSSL (`libcrypto`). These are part of the base system on current desktop distributions, so end users don't need to install anything. Building an app that uses this plugin requires the OpenSSL development headers: + +```bash +sudo apt install libssl-dev # Debian / Ubuntu / Pop!_OS +sudo dnf install openssl-devel # Fedora +``` + +On both platforms: + +- **Background work:** zipping and unzipping run on a background thread and stream data, so memory use doesn't depend on archive or file size. Archives over 4 GB (ZIP64) are supported. +- **Passwords:** + - Archives created with a `password` use WinZip AES-256 encryption, the same as iOS and Android. + - When unzipping, both AES and legacy ZipCrypto archives are supported, and AES entries are authenticated. +- **Extraction safety:** + - Entries that would land outside the destination folder are skipped, and so are symbolic links. + - Each file is written to a temporary file first and only moved into place after it has been verified, so a failed extraction never replaces an existing file. + ## API ### Zip class @@ -163,7 +187,7 @@ Extracts all the files from the zip file specified in the `archive` property of | `directory` | `string`| _Optional_: The path to extract the files to. | `archive` | `string` | _Optional_: The path of the zip file. | `onProgress` | `(progress: number) => void` | _Optional_: Unzip request progress listener. -| `overwrite`| `boolean` | _Optional_: (`iOS-only`) +| `overwrite`| `boolean` | _Optional_: Overwrite existing files (default `true`). Supported on iOS, macOS, Windows and Linux. | `password`| `string` | _Optional_: The password used to protect the zip file. ## License diff --git a/packages/zip/index.linux.ts b/packages/zip/index.linux.ts new file mode 100644 index 00000000..2a497c31 --- /dev/null +++ b/packages/zip/index.linux.ts @@ -0,0 +1,68 @@ +import { Folder, File, path, knownFolders } from '@nativescript/core'; +import { UnZipOptions, ZipOptions } from '.'; + +declare function nszip_zip(directory: string, archive: string, keepParent: number, password: string | null, progress: interop.Pointer, complete: interop.Pointer): void; +declare function nszip_unzip(archive: string, destination: string, overwrite: number, password: string | null, progress: interop.Pointer, complete: interop.Pointer): void; +declare const nativescript: { ref(): void; unref(): void }; + +function uuid(): string { + return (globalThis as any).crypto?.randomUUID?.() ?? `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}`; +} + +function run(onProgress: ((percent: number) => void) | undefined, start: (progress: interop.Pointer, complete: interop.Pointer) => void): Promise { + return new Promise((resolve, reject) => { + const native: any = interop; + const progressFn = (percent: number) => { + if (typeof onProgress === 'function') { + onProgress(percent); + } + }; + const completeFn = (error: any) => { + const failure = error ? native.stringFromCString(error) : null; + setTimeout(() => { + native.release(progressFn); + native.release(completeFn); + nativescript.unref(); + }); + if (failure) { + reject(failure); + } else { + resolve(); + } + }; + const progressPtr = native.callback(progressFn, { returns: native.types.void, args: [native.types.int32] }); + const completePtr = native.callback(completeFn, { returns: native.types.void, args: [native.types.pointer] }); + nativescript.ref(); + start(progressPtr, completePtr); + }); +} + +export class Zip { + static debugEnabled = false; + + private static debug(arg) { + if (this.debugEnabled && console && console.log) { + console.log(arg); + } + } + + public static zip(options: ZipOptions): Promise { + if (!options?.directory || !Folder.exists(options.directory)) { + return Promise.reject('Directory does not exist, invalid directory path: ' + options?.directory); + } + const archive = options.archive ?? path.join(knownFolders.temp().path, `${uuid()}_archive.zip`); + const keepParent = (options.keepParent ?? true) ? 1 : 0; + this.debug('Zip.zip - folder=' + options.directory + '\n' + 'Zip.zip - destinationPath=' + archive); + return run(options.onProgress, (progress, complete) => nszip_zip(options.directory, archive, keepParent, options.password || null, progress, complete)).then(() => archive); + } + + public static unzip(options: UnZipOptions): Promise { + if (!options?.archive || !File.exists(options.archive)) { + return Promise.reject(`File does not exist, invalid archive path: ${options?.archive}`); + } + const destination = options.directory ?? path.join(knownFolders.temp().path, uuid()); + const overwrite = (options.overwrite ?? true) ? 1 : 0; + this.debug(`Zip.unzip - archive=${options.archive}`); + return run(options.onProgress, (progress, complete) => nszip_unzip(options.archive, destination, overwrite, options.password || null, progress, complete)).then(() => destination); + } +} diff --git a/packages/zip/index.windows.ts b/packages/zip/index.windows.ts new file mode 100644 index 00000000..ab7df9fd --- /dev/null +++ b/packages/zip/index.windows.ts @@ -0,0 +1,57 @@ +import { Folder, File, path, knownFolders } from '@nativescript/core'; +import { UnZipOptions, ZipOptions } from '.'; + +declare const NativeScriptZip: any; +declare const NSWinRT: any; + +function uuid(): string { + return (globalThis as any).crypto?.randomUUID?.() ?? `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}`; +} + +function message(e: any): string { + return typeof e === 'string' ? e : (e?.message ?? String(e)); +} + +function progress(options: { onProgress?: (progress: number) => void }) { + return (percent: number) => { + if (typeof options.onProgress === 'function') { + options.onProgress(percent); + } + }; +} + +export class Zip { + static debugEnabled = false; + + private static debug(arg) { + if (this.debugEnabled && console && console.log) { + console.log(arg); + } + } + + public static zip(options: ZipOptions): Promise { + if (!options?.directory || !Folder.exists(options.directory)) { + return Promise.reject('Directory does not exist, invalid directory path: ' + options?.directory); + } + const archive = options.archive ?? path.join(knownFolders.temp().path, `${uuid()}_archive.zip`); + this.debug('Zip.zip - folder=' + options.directory + '\n' + 'Zip.zip - destinationPath=' + archive); + const task = NativeScriptZip.ZipArchiver.ZipAsync(options.directory, archive, options.keepParent ?? true, options.password ?? '', progress(options)); + return NSWinRT.dotnet.taskToPromise(task).then( + () => archive, + (e) => Promise.reject(message(e)), + ); + } + + public static unzip(options: UnZipOptions): Promise { + if (!options?.archive || !File.exists(options.archive)) { + return Promise.reject(`File does not exist, invalid archive path: ${options?.archive}`); + } + const destination = options.directory ?? path.join(knownFolders.temp().path, uuid()); + this.debug(`Zip.unzip - archive=${options.archive}`); + const task = NativeScriptZip.ZipArchiver.UnzipAsync(options.archive, destination, options.overwrite ?? true, options.password ?? '', progress(options)); + return NSWinRT.dotnet.taskToPromise(task).then( + () => destination, + (e) => Promise.reject(message(e)), + ); + } +} diff --git a/packages/zip/package.json b/packages/zip/package.json index 56c579fb..99cf9450 100644 --- a/packages/zip/package.json +++ b/packages/zip/package.json @@ -8,7 +8,9 @@ "platforms": { "ios": "6.0.0", "macos": "6.0.0", - "android": "6.0.0" + "android": "6.0.0", + "windows": "9.1.0", + "linux": "0.1.0-alpha.2" } }, "repository": { @@ -21,7 +23,9 @@ "TypeScript", "iOS", "macOS", - "Android" + "Android", + "Windows", + "Linux" ], "author": { "name": "NativeScript", diff --git a/packages/zip/platforms/linux/include/nszip.h b/packages/zip/platforms/linux/include/nszip.h new file mode 100644 index 00000000..ef21781c --- /dev/null +++ b/packages/zip/platforms/linux/include/nszip.h @@ -0,0 +1,28 @@ +#ifndef NSZIP_H +#define NSZIP_H + +#include + +#if defined(__GNUC__) +#define NSZIP_EXPORT __attribute__((visibility("default"))) +#else +#define NSZIP_EXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*nszip_progress_callback)(int32_t percent); + +typedef void (*nszip_complete_callback)(const char *error); + +NSZIP_EXPORT void nszip_zip(const char *directory, const char *archive, int32_t keep_parent, const char *password, nszip_progress_callback progress, nszip_complete_callback complete); + +NSZIP_EXPORT void nszip_unzip(const char *archive, const char *destination, int32_t overwrite, const char *password, nszip_progress_callback progress, nszip_complete_callback complete); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/packages/zip/platforms/linux/native.json b/packages/zip/platforms/linux/native.json new file mode 100644 index 00000000..ca3cd5b4 --- /dev/null +++ b/packages/zip/platforms/linux/native.json @@ -0,0 +1,4 @@ +{ + "pkg-config": ["zlib", "glib-2.0", "libcrypto"], + "cflags": ["-O2", "-std=gnu11", "-fvisibility=hidden", "-Wno-deprecated-declarations", "-DHAVE_ZLIB", "-DZLIB_COMPAT", "-DHAVE_PKCRYPT", "-DHAVE_WZAES", "-DHAVE_GETRANDOM", "-D_POSIX_C_SOURCE=200809L", "-D_DEFAULT_SOURCE", "-D_BSD_SOURCE"] +} diff --git a/packages/zip/platforms/linux/src/minizip-ng/LICENSE b/packages/zip/platforms/linux/src/minizip-ng/LICENSE new file mode 100644 index 00000000..3b6c4e14 --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/LICENSE @@ -0,0 +1,17 @@ +Condition of use and distribution are the same as zlib: + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgement in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. diff --git a/packages/zip/platforms/linux/src/minizip-ng/VENDORED.md b/packages/zip/platforms/linux/src/minizip-ng/VENDORED.md new file mode 100644 index 00000000..b5c59a49 --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/VENDORED.md @@ -0,0 +1,7 @@ +minizip-ng 4.2.2 (https://github.com/zlib-ng/minizip-ng/releases/tag/4.2.2), zlib license (see LICENSE). + +Only the sources for zlib + OpenSSL + PKWARE/WinZip AES encryption are included; `mz_config.h` replaces the CMake-generated header. + +Local changes: +- `mz_zip.c`: `mz_zip_entry_read_close` also closes the WinZip AES stream. +- `mz_crypt_openssl.c`: `openssl/engine.h` is only included for OpenSSL < 1.1 (it is absent on systems built without the ENGINE API). diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz.h b/packages/zip/platforms/linux/src/minizip-ng/mz.h new file mode 100644 index 00000000..b2637813 --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz.h @@ -0,0 +1,277 @@ +/* mz.h -- Errors codes, zip flags and magic + part of the minizip-ng project + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#ifndef MZ_H +#define MZ_H + +/***************************************************************************/ + +/* MZ_VERSION */ +#define MZ_VERSION ("4.2.2") +#define MZ_VERSION_BUILD (0x040202) + +/* MZ_ERROR */ +#define MZ_OK (0) /* zlib */ +#define MZ_STREAM_ERROR (-1) /* zlib */ +#define MZ_DATA_ERROR (-3) /* zlib */ +#define MZ_MEM_ERROR (-4) /* zlib */ +#define MZ_BUF_ERROR (-5) /* zlib */ +#define MZ_VERSION_ERROR (-6) /* zlib */ + +#define MZ_END_OF_LIST (-100) +#define MZ_END_OF_STREAM (-101) + +#define MZ_PARAM_ERROR (-102) +#define MZ_FORMAT_ERROR (-103) +#define MZ_INTERNAL_ERROR (-104) +#define MZ_CRC_ERROR (-105) +#define MZ_CRYPT_ERROR (-106) +#define MZ_EXIST_ERROR (-107) +#define MZ_PASSWORD_ERROR (-108) +#define MZ_SUPPORT_ERROR (-109) +#define MZ_HASH_ERROR (-110) +#define MZ_OPEN_ERROR (-111) +#define MZ_CLOSE_ERROR (-112) +#define MZ_SEEK_ERROR (-113) +#define MZ_TELL_ERROR (-114) +#define MZ_READ_ERROR (-115) +#define MZ_WRITE_ERROR (-116) +#define MZ_SIGN_ERROR (-117) +#define MZ_SYMLINK_ERROR (-118) + +/* MZ_OPEN */ +#define MZ_OPEN_MODE_READ (0x01) +#define MZ_OPEN_MODE_WRITE (0x02) +#define MZ_OPEN_MODE_READWRITE (MZ_OPEN_MODE_READ | MZ_OPEN_MODE_WRITE) +#define MZ_OPEN_MODE_APPEND (0x04) +#define MZ_OPEN_MODE_CREATE (0x08) +#define MZ_OPEN_MODE_EXISTING (0x10) +#define MZ_OPEN_MODE_NOFOLLOW (0x20) + +/* MZ_SEEK */ +#define MZ_SEEK_SET (0) +#define MZ_SEEK_CUR (1) +#define MZ_SEEK_END (2) + +/* MZ_COMPRESS */ +#define MZ_COMPRESS_METHOD_STORE (0) +#define MZ_COMPRESS_METHOD_DEFLATE (8) +#define MZ_COMPRESS_METHOD_BZIP2 (12) +#define MZ_COMPRESS_METHOD_LZMA (14) +#define MZ_COMPRESS_METHOD_ZSTD (93) +#define MZ_COMPRESS_METHOD_XZ (95) +#define MZ_COMPRESS_METHOD_PPMD (98) +#define MZ_COMPRESS_METHOD_AES (99) + +#define MZ_COMPRESS_LEVEL_DEFAULT (-1) +#define MZ_COMPRESS_LEVEL_FAST (2) +#define MZ_COMPRESS_LEVEL_NORMAL (6) +#define MZ_COMPRESS_LEVEL_BEST (9) + +/* MZ_ZIP_FLAG */ +#define MZ_ZIP_FLAG_ENCRYPTED (1 << 0) +#define MZ_ZIP_FLAG_LZMA_EOS_MARKER (1 << 1) +#define MZ_ZIP_FLAG_DEFLATE_MAX (1 << 1) +#define MZ_ZIP_FLAG_DEFLATE_NORMAL (0) +#define MZ_ZIP_FLAG_DEFLATE_FAST (1 << 2) +#define MZ_ZIP_FLAG_DEFLATE_SUPER_FAST (MZ_ZIP_FLAG_DEFLATE_FAST | MZ_ZIP_FLAG_DEFLATE_MAX) +#define MZ_ZIP_FLAG_DATA_DESCRIPTOR (1 << 3) +#define MZ_ZIP_FLAG_UTF8 (1 << 11) +#define MZ_ZIP_FLAG_MASK_LOCAL_INFO (1 << 13) + +/* MZ_ZIP_EXTENSION */ +#define MZ_ZIP_EXTENSION_ZIP64 (0x0001) +#define MZ_ZIP_EXTENSION_NTFS (0x000a) +#define MZ_ZIP_EXTENSION_AES (0x9901) +#define MZ_ZIP_EXTENSION_UNIX1 (0x000d) +#define MZ_ZIP_EXTENSION_SIGN (0x10c5) +#define MZ_ZIP_EXTENSION_HASH (0x1a51) +#define MZ_ZIP_EXTENSION_CDCD (0xcdcd) + +/* MZ_ZIP64 */ +#define MZ_ZIP64_AUTO (0) +#define MZ_ZIP64_FORCE (1) +#define MZ_ZIP64_DISABLE (2) + +/* MZ_HOST_SYSTEM */ +#define MZ_HOST_SYSTEM(VERSION_MADEBY) ((uint8_t)(VERSION_MADEBY >> 8)) +#define MZ_HOST_SYSTEM_MSDOS (0) +#define MZ_HOST_SYSTEM_UNIX (3) +#define MZ_HOST_SYSTEM_WINDOWS_NTFS (10) +#define MZ_HOST_SYSTEM_RISCOS (13) +#define MZ_HOST_SYSTEM_OSX_DARWIN (19) + +/* MZ_PKCRYPT */ +#define MZ_PKCRYPT_HEADER_SIZE (12) + +/* MZ_AES */ +#define MZ_AES_VERSION (1) +#define MZ_AES_MODE_ECB (0) +#define MZ_AES_MODE_CBC (1) +#define MZ_AES_MODE_GCM (2) +#define MZ_AES_STRENGTH_128 (1) +#define MZ_AES_STRENGTH_192 (2) +#define MZ_AES_STRENGTH_256 (3) +#define MZ_AES_KEY_LENGTH_MAX (32) +#define MZ_AES_BLOCK_SIZE (16) +#define MZ_AES_FOOTER_SIZE (10) + +/* MZ_HASH */ +#define MZ_HASH_MD5 (10) +#define MZ_HASH_MD5_SIZE (16) +#define MZ_HASH_SHA1 (20) +#define MZ_HASH_SHA1_SIZE (20) +#define MZ_HASH_SHA224 (22) +#define MZ_HASH_SHA224_SIZE (28) +#define MZ_HASH_SHA256 (23) +#define MZ_HASH_SHA256_SIZE (32) +#define MZ_HASH_SHA384 (24) +#define MZ_HASH_SHA384_SIZE (48) +#define MZ_HASH_SHA512 (25) +#define MZ_HASH_SHA512_SIZE (64) +#define MZ_HASH_MAX_SIZE (256) + +/* MZ_ENCODING */ +#define MZ_ENCODING_CODEPAGE_437 (437) +#define MZ_ENCODING_CODEPAGE_932 (932) +#define MZ_ENCODING_CODEPAGE_936 (936) +#define MZ_ENCODING_CODEPAGE_950 (950) +#define MZ_ENCODING_UTF8 (65001) + +/* MZ_UTILITY */ +#define MZ_UNUSED(SYMBOL) ((void)SYMBOL) + +#if defined(_WIN32) && defined(MZ_EXPORTS) +# define MZ_EXPORT __declspec(dllexport) +#else +# define MZ_EXPORT +#endif + +/***************************************************************************/ + +#include /* size_t, NULL, malloc */ +#include /* time_t, time() */ +#include /* memset, strncpy, strlen */ +#include + +#include "mz_config.h" + +#if HAVE_STDINT_H +# include +#elif defined(__has_include) +# if __has_include() +# include +# endif +#endif + +#ifndef INT8_MAX +typedef signed char int8_t; +#endif +#ifndef INT16_MAX +typedef short int16_t; +#endif +#ifndef INT32_MAX +typedef int int32_t; +#endif +#ifndef INT64_MAX +typedef long long int64_t; +#endif +#ifndef UINT8_MAX +typedef unsigned char uint8_t; +#endif +#ifndef UINT16_MAX +typedef unsigned short uint16_t; +#endif +#ifndef UINT32_MAX +typedef unsigned int uint32_t; +#endif +#ifndef UINT64_MAX +typedef unsigned long long uint64_t; +#endif + +#if HAVE_INTTYPES_H +# include +#elif defined(__has_include) +# if __has_include() +# include +# endif +#endif + +#ifndef PRId8 +# define PRId8 "hhd" +#endif +#ifndef PRIu8 +# define PRIu8 "hhu" +#endif +#ifndef PRIx8 +# define PRIx8 "hhx" +#endif +#ifndef PRId16 +# define PRId16 "hd" +#endif +#ifndef PRIu16 +# define PRIu16 "hu" +#endif +#ifndef PRIx16 +# define PRIx16 "hx" +#endif +#ifndef PRId32 +# define PRId32 "d" +#endif +#ifndef PRIu32 +# define PRIu32 "u" +#endif +#ifndef PRIx32 +# define PRIx32 "x" +#endif +#if ULONG_MAX == 0xfffffffful +# ifndef PRId64 +# define PRId64 "ld" +# endif +# ifndef PRIu64 +# define PRIu64 "lu" +# endif +# ifndef PRIx64 +# define PRIx64 "lx" +# endif +#else +# ifndef PRId64 +# define PRId64 "lld" +# endif +# ifndef PRIu64 +# define PRIu64 "llu" +# endif +# ifndef PRIx64 +# define PRIx64 "llx" +# endif +#endif + +#ifndef INT16_MAX +# define INT16_MAX 32767 +#endif +#ifndef INT32_MAX +# define INT32_MAX 2147483647L +#endif +#ifndef INT64_MAX +# define INT64_MAX 9223372036854775807LL +#endif +#ifndef UINT16_MAX +# define UINT16_MAX 65535U +#endif +#ifndef UINT32_MAX +# define UINT32_MAX 4294967295UL +#endif +#ifndef UINT64_MAX +# define UINT64_MAX 18446744073709551615ULL +#endif + +/***************************************************************************/ + +#endif diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_config.h b/packages/zip/platforms/linux/src/minizip-ng/mz_config.h new file mode 100644 index 00000000..e58c3220 --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_config.h @@ -0,0 +1,12 @@ +#ifndef MZ_CONFIG_H +#define MZ_CONFIG_H + +#define HAVE_DIRENT_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_PDIR 1 +#define HAVE_FSEEKO 1 +#define HAVE_SYMLINK 1 +#define HAVE_READLINK 1 + +#endif diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_crypt.c b/packages/zip/platforms/linux/src/minizip-ng/mz_crypt.c new file mode 100644 index 00000000..997adf1a --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_crypt.c @@ -0,0 +1,182 @@ +/* mz_crypt.c -- Crypto/hash functions + part of the minizip-ng project + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#include "mz.h" +#include "mz_os.h" +#include "mz_crypt.h" + +#if defined(HAVE_ZLIB) +# if !defined(ZLIB_COMPAT) +# include "zlib-ng.h" +# define ZLIB_PREFIX(x) zng_##x +# else +# include "zlib.h" +# define ZLIB_PREFIX(x) x +# endif +#elif defined(HAVE_LZMA) +# include "lzma.h" +#endif + +/***************************************************************************/ + +#if defined(MZ_ZIP_NO_CRYPTO) +int32_t mz_crypt_rand(uint8_t *buf, int32_t size) { + return mz_os_rand(buf, size); +} +#endif + +uint32_t mz_crypt_crc32_update(uint32_t value, const uint8_t *buf, int32_t size) { +#if defined(HAVE_ZLIB) +# ifndef ZLIB_VERNUM + /* HAVE_ZLIB but no ZLIB_VERNUM? */ + typedef uint32_t z_crc_t; +# elif (ZLIB_VERNUM & 0xf != 0xf) && (ZLIB_VERNUM < 0x1270) + /* Define z_crc_t in zlib 1.2.6 and less */ + typedef unsigned long z_crc_t; +# elif (ZLIB_VERNUM & 0xf == 0xf) && (ZLIB_VERNUM < 0x12df) + /* Define z_crc_t in zlib-ng 2.0.7 and less */ + typedef unsigned int z_crc_t; +# endif + return (uint32_t)ZLIB_PREFIX(crc32)((z_crc_t)value, buf, (uInt)size); +#elif defined(HAVE_LZMA) + return (uint32_t)lzma_crc32(buf, (size_t)size, (uint32_t)value); +#else + static uint32_t crc32_table[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, + 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, + 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, + 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, + 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, + 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, + 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, + 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, + 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, + 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, + 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, + 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, + 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, + 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, + 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d}; + value = ~value; + + while (size > 0) { + value = (value >> 8) ^ crc32_table[(value ^ *buf) & 0xFF]; + + buf += 1; + size -= 1; + } + + return ~value; +#endif +} + +#if defined(HAVE_WZAES) +int32_t mz_crypt_pbkdf2(const uint8_t *password, int32_t password_length, const uint8_t *salt, int32_t salt_length, + uint32_t iteration_count, uint8_t *key, uint16_t key_length) { + void *hmac1 = NULL; + void *hmac2 = NULL; + void *hmac3 = NULL; + int32_t err = MZ_OK; + uint16_t i = 0; + uint32_t j = 0; + uint16_t k = 0; + uint16_t block_count = 0; + uint8_t uu[MZ_HASH_SHA1_SIZE]; + uint8_t ux[MZ_HASH_SHA1_SIZE]; + + if (!password || !salt || !key) + return MZ_PARAM_ERROR; + + memset(key, 0, key_length); + + hmac1 = mz_crypt_hmac_create(); + hmac2 = mz_crypt_hmac_create(); + hmac3 = mz_crypt_hmac_create(); + + if (!hmac1 || !hmac2 || !hmac3) { + err = MZ_MEM_ERROR; + goto pbkdf2_cleanup; + } + + mz_crypt_hmac_set_algorithm(hmac1, MZ_HASH_SHA1); + mz_crypt_hmac_set_algorithm(hmac2, MZ_HASH_SHA1); + mz_crypt_hmac_set_algorithm(hmac3, MZ_HASH_SHA1); + + err = mz_crypt_hmac_init(hmac1, password, password_length); + if (err == MZ_OK) + err = mz_crypt_hmac_init(hmac2, password, password_length); + if (err == MZ_OK) + err = mz_crypt_hmac_update(hmac2, salt, salt_length); + + block_count = 1 + ((uint16_t)key_length - 1) / MZ_HASH_SHA1_SIZE; + + for (i = 0; (err == MZ_OK) && (i < block_count); i += 1) { + memset(ux, 0, sizeof(ux)); + + err = mz_crypt_hmac_copy(hmac2, hmac3); + if (err != MZ_OK) + break; + + uu[0] = (uint8_t)((i + 1) >> 24); + uu[1] = (uint8_t)((i + 1) >> 16); + uu[2] = (uint8_t)((i + 1) >> 8); + uu[3] = (uint8_t)(i + 1); + + for (j = 0, k = 4; j < iteration_count; j += 1) { + err = mz_crypt_hmac_update(hmac3, uu, k); + if (err == MZ_OK) + err = mz_crypt_hmac_end(hmac3, uu, sizeof(uu)); + if (err != MZ_OK) + break; + + for (k = 0; k < MZ_HASH_SHA1_SIZE; k += 1) + ux[k] ^= uu[k]; + + err = mz_crypt_hmac_copy(hmac1, hmac3); + if (err != MZ_OK) + break; + } + + if (err != MZ_OK) + break; + + j = 0; + k = i * MZ_HASH_SHA1_SIZE; + + while (j < MZ_HASH_SHA1_SIZE && k < key_length) + key[k++] = ux[j++]; + } + +pbkdf2_cleanup: + /* hmac3 uses the same provider as hmac2, so it must be deleted + before the context is destroyed. */ + mz_crypt_hmac_delete(&hmac3); + mz_crypt_hmac_delete(&hmac1); + mz_crypt_hmac_delete(&hmac2); + + return err; +} +#endif + +/***************************************************************************/ diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_crypt.h b/packages/zip/platforms/linux/src/minizip-ng/mz_crypt.h new file mode 100644 index 00000000..65e05987 --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_crypt.h @@ -0,0 +1,65 @@ +/* mz_crypt.h -- Crypto/hash functions + part of the minizip-ng project + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#ifndef MZ_CRYPT_H +#define MZ_CRYPT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/***************************************************************************/ + +uint32_t mz_crypt_crc32_update(uint32_t value, const uint8_t *buf, int32_t size); + +int32_t mz_crypt_pbkdf2(const uint8_t *password, int32_t password_length, const uint8_t *salt, int32_t salt_length, + uint32_t iteration_count, uint8_t *key, uint16_t key_length); + +/***************************************************************************/ + +int32_t mz_crypt_rand(uint8_t *buf, int32_t size); + +void mz_crypt_sha_reset(void *handle); +int32_t mz_crypt_sha_begin(void *handle); +int32_t mz_crypt_sha_update(void *handle, const void *buf, int32_t size); +int32_t mz_crypt_sha_end(void *handle, uint8_t *digest, int32_t digest_size); +int32_t mz_crypt_sha_set_algorithm(void *handle, uint16_t algorithm); +void *mz_crypt_sha_create(void); +void mz_crypt_sha_delete(void **handle); + +void mz_crypt_aes_reset(void *handle); +int32_t mz_crypt_aes_encrypt(void *handle, const void *aad, int32_t aad_size, uint8_t *buf, int32_t size); +int32_t mz_crypt_aes_encrypt_final(void *handle, uint8_t *buf, int32_t size, uint8_t *tag, int32_t tag_size); +int32_t mz_crypt_aes_decrypt(void *handle, const void *aad, int32_t aad_size, uint8_t *buf, int32_t size); +int32_t mz_crypt_aes_decrypt_final(void *handle, uint8_t *buf, int32_t size, const uint8_t *tag, int32_t tag_size); +int32_t mz_crypt_aes_set_encrypt_key(void *handle, const void *key, int32_t key_length, const void *iv, + int32_t iv_length); +int32_t mz_crypt_aes_set_decrypt_key(void *handle, const void *key, int32_t key_length, const void *iv, + int32_t iv_length); +void mz_crypt_aes_set_mode(void *handle, int32_t mode); +void *mz_crypt_aes_create(void); +void mz_crypt_aes_delete(void **handle); + +void mz_crypt_hmac_reset(void *handle); +int32_t mz_crypt_hmac_init(void *handle, const void *key, int32_t key_length); +int32_t mz_crypt_hmac_update(void *handle, const void *buf, int32_t size); +int32_t mz_crypt_hmac_end(void *handle, uint8_t *digest, int32_t digest_size); +int32_t mz_crypt_hmac_copy(void *src_handle, void *target_handle); +void mz_crypt_hmac_set_algorithm(void *handle, uint16_t algorithm); +void *mz_crypt_hmac_create(void); +void mz_crypt_hmac_delete(void **handle); + +/***************************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_crypt_openssl.c b/packages/zip/platforms/linux/src/minizip-ng/mz_crypt_openssl.c new file mode 100644 index 00000000..bd0ae3bf --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_crypt_openssl.c @@ -0,0 +1,728 @@ +/* mz_crypt_openssl.c -- Crypto/hash functions for OpenSSL + part of the minizip-ng project + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#include "mz.h" +#include "mz_crypt.h" + +#include +#include +#if OPENSSL_VERSION_NUMBER < 0x10100000L +#include +#endif +#include +#include +#include +#include +#include +#include + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +# include +#endif + +/***************************************************************************/ + +#define MZ_AES_GCM_IV_SIZE (12) + +/***************************************************************************/ + +static void mz_crypt_init(void) { + static int32_t openssl_initialized = 0; + if (!openssl_initialized) { +#if OPENSSL_VERSION_NUMBER < 0x10100000L + OpenSSL_add_all_algorithms(); + + ERR_load_BIO_strings(); + ERR_load_crypto_strings(); + + ENGINE_load_builtin_engines(); + ENGINE_register_all_complete(); +#else + OPENSSL_init_crypto(OPENSSL_INIT_ENGINE_ALL_BUILTIN, NULL); +#endif + + openssl_initialized = 1; + } +} + +int32_t mz_crypt_rand(uint8_t *buf, int32_t size) { + if (!RAND_bytes(buf, size)) + return MZ_CRYPT_ERROR; + + return size; +} + +/***************************************************************************/ + +typedef struct mz_crypt_sha_s { +#if OPENSSL_VERSION_NUMBER < 0x10100000L + union { + SHA512_CTX ctx512; + SHA256_CTX ctx256; + SHA_CTX ctx1; + }; +#else + EVP_MD_CTX *ctx; +#endif + unsigned long error; + int32_t initialized; + uint16_t algorithm; +} mz_crypt_sha; + +/***************************************************************************/ + +static const uint8_t mz_crypt_sha_digest_size[] = { + MZ_HASH_SHA1_SIZE, 0, MZ_HASH_SHA224_SIZE, MZ_HASH_SHA256_SIZE, MZ_HASH_SHA384_SIZE, MZ_HASH_SHA512_SIZE}; + +/***************************************************************************/ + +static void mz_crypt_sha_free(void *handle) { +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + mz_crypt_sha *sha = (mz_crypt_sha *)handle; + if (sha->ctx) + EVP_MD_CTX_free(sha->ctx); + sha->ctx = NULL; +#else + MZ_UNUSED(handle); +#endif +} + +void mz_crypt_sha_reset(void *handle) { + mz_crypt_sha *sha = (mz_crypt_sha *)handle; + + mz_crypt_init(); + mz_crypt_sha_free(handle); + + sha->error = 0; + sha->initialized = 0; +} + +int32_t mz_crypt_sha_begin(void *handle) { + mz_crypt_sha *sha = (mz_crypt_sha *)handle; + int32_t result = 0; + + if (!sha) + return MZ_PARAM_ERROR; + + mz_crypt_sha_reset(handle); + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + switch (sha->algorithm) { + case MZ_HASH_SHA1: + result = SHA1_Init(&sha->ctx1); + break; + case MZ_HASH_SHA224: + result = SHA224_Init(&sha->ctx256); + break; + case MZ_HASH_SHA256: + result = SHA256_Init(&sha->ctx256); + break; + case MZ_HASH_SHA384: + result = SHA384_Init(&sha->ctx512); + break; + case MZ_HASH_SHA512: + result = SHA512_Init(&sha->ctx512); + break; + } +#else + const EVP_MD *md = NULL; + switch (sha->algorithm) { + case MZ_HASH_SHA1: + md = EVP_sha1(); + break; + case MZ_HASH_SHA224: + md = EVP_sha224(); + break; + case MZ_HASH_SHA256: + md = EVP_sha256(); + break; + case MZ_HASH_SHA384: + md = EVP_sha384(); + break; + case MZ_HASH_SHA512: + md = EVP_sha512(); + break; + } + if (!md) + return MZ_PARAM_ERROR; + + sha->ctx = EVP_MD_CTX_new(); + if (!sha->ctx) + return MZ_MEM_ERROR; + result = EVP_DigestInit_ex(sha->ctx, md, NULL); +#endif + + if (!result) { + sha->error = ERR_get_error(); + return MZ_HASH_ERROR; + } + + sha->initialized = 1; + return MZ_OK; +} + +int32_t mz_crypt_sha_update(void *handle, const void *buf, int32_t size) { + mz_crypt_sha *sha = (mz_crypt_sha *)handle; + int32_t result = 0; + + if (!sha || !buf || !sha->initialized) + return MZ_PARAM_ERROR; + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + switch (sha->algorithm) { + case MZ_HASH_SHA1: + result = SHA1_Update(&sha->ctx1, buf, size); + break; + case MZ_HASH_SHA224: + result = SHA224_Update(&sha->ctx256, buf, size); + break; + case MZ_HASH_SHA256: + result = SHA256_Update(&sha->ctx256, buf, size); + break; + case MZ_HASH_SHA384: + result = SHA384_Update(&sha->ctx512, buf, size); + break; + case MZ_HASH_SHA512: + result = SHA512_Update(&sha->ctx512, buf, size); + break; + } +#else + result = EVP_DigestUpdate(sha->ctx, buf, size); +#endif + + if (!result) { + sha->error = ERR_get_error(); + return MZ_HASH_ERROR; + } + + return size; +} + +int32_t mz_crypt_sha_end(void *handle, uint8_t *digest, int32_t digest_size) { + mz_crypt_sha *sha = (mz_crypt_sha *)handle; + int32_t result = 0; + + if (!sha || !digest || !sha->initialized) + return MZ_PARAM_ERROR; + if (digest_size < mz_crypt_sha_digest_size[sha->algorithm - MZ_HASH_SHA1]) + return MZ_PARAM_ERROR; + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + switch (sha->algorithm) { + case MZ_HASH_SHA1: + result = SHA1_Final(digest, &sha->ctx1); + break; + case MZ_HASH_SHA224: + result = SHA224_Final(digest, &sha->ctx256); + break; + case MZ_HASH_SHA256: + result = SHA256_Final(digest, &sha->ctx256); + break; + case MZ_HASH_SHA384: + result = SHA384_Final(digest, &sha->ctx512); + break; + case MZ_HASH_SHA512: + result = SHA512_Final(digest, &sha->ctx512); + break; + } +#else + result = EVP_DigestFinal_ex(sha->ctx, digest, NULL); +#endif + + if (!result) { + sha->error = ERR_get_error(); + return MZ_HASH_ERROR; + } + + return MZ_OK; +} + +int32_t mz_crypt_sha_set_algorithm(void *handle, uint16_t algorithm) { + mz_crypt_sha *sha = (mz_crypt_sha *)handle; + if (algorithm < MZ_HASH_SHA1 || algorithm > MZ_HASH_SHA512) + return MZ_PARAM_ERROR; + sha->algorithm = algorithm; + return MZ_OK; +} + +void *mz_crypt_sha_create(void) { + mz_crypt_sha *sha = (mz_crypt_sha *)calloc(1, sizeof(mz_crypt_sha)); + if (sha) + sha->algorithm = MZ_HASH_SHA256; + return sha; +} + +void mz_crypt_sha_delete(void **handle) { + mz_crypt_sha *sha = NULL; + if (!handle) + return; + sha = (mz_crypt_sha *)*handle; + if (sha) { + mz_crypt_sha_free(*handle); + free(sha); + } + *handle = NULL; +} + +/***************************************************************************/ + +typedef struct mz_crypt_aes_s { + int32_t mode; + unsigned long error; + EVP_CIPHER_CTX *ctx; +} mz_crypt_aes; + +/***************************************************************************/ + +static void mz_crypt_aes_free(void *handle) { + mz_crypt_aes *aes = (mz_crypt_aes *)handle; + if (aes->ctx) + EVP_CIPHER_CTX_free(aes->ctx); + aes->ctx = NULL; +} + +void mz_crypt_aes_reset(void *handle) { + mz_crypt_init(); + mz_crypt_aes_free(handle); +} + +int32_t mz_crypt_aes_encrypt(void *handle, const void *aad, int32_t aad_size, uint8_t *buf, int32_t size) { + mz_crypt_aes *aes = (mz_crypt_aes *)handle; + + if (!aes || !buf || size % MZ_AES_BLOCK_SIZE != 0 || !aes->ctx) + return MZ_PARAM_ERROR; + if (aes->mode != MZ_AES_MODE_GCM && aad && aad_size > 0) + return MZ_PARAM_ERROR; + + if (aad && aad_size > 0) { + int32_t how_many = 0; + if (!EVP_EncryptUpdate(aes->ctx, NULL, &how_many, aad, aad_size)) + return MZ_CRYPT_ERROR; + } + + if (!EVP_EncryptUpdate(aes->ctx, buf, &size, buf, size)) + return MZ_CRYPT_ERROR; + + return size; +} + +int32_t mz_crypt_aes_encrypt_final(void *handle, uint8_t *buf, int32_t size, uint8_t *tag, int32_t tag_size) { + mz_crypt_aes *aes = (mz_crypt_aes *)handle; + int result = 0; + int out_len = 0; + + if (!aes || !tag || !tag_size || !aes->ctx || aes->mode != MZ_AES_MODE_GCM) + return MZ_PARAM_ERROR; + + if (buf && size) { + if (!EVP_EncryptUpdate(aes->ctx, buf, &size, buf, size)) + return MZ_CRYPT_ERROR; + } + + /* Must call EncryptFinal for tag to be calculated */ + result = EVP_EncryptFinal_ex(aes->ctx, NULL, &out_len); + + if (result) + result = EVP_CIPHER_CTX_ctrl(aes->ctx, EVP_CTRL_GCM_GET_TAG, tag_size, tag); + + if (!result) { + aes->error = ERR_get_error(); + return MZ_CRYPT_ERROR; + } + + return size; +} + +int32_t mz_crypt_aes_decrypt(void *handle, const void *aad, int32_t aad_size, uint8_t *buf, int32_t size) { + mz_crypt_aes *aes = (mz_crypt_aes *)handle; + + if (!aes || !buf || size % MZ_AES_BLOCK_SIZE != 0 || !aes->ctx) + return MZ_PARAM_ERROR; + if (aes->mode != MZ_AES_MODE_GCM && aad && aad_size > 0) + return MZ_PARAM_ERROR; + + if (aad && aad_size > 0) { + int32_t how_many = 0; + if (!EVP_DecryptUpdate(aes->ctx, NULL, &how_many, aad, aad_size)) + return MZ_CRYPT_ERROR; + } + + if (!EVP_DecryptUpdate(aes->ctx, buf, &size, buf, size)) + return MZ_CRYPT_ERROR; + + return size; +} + +int32_t mz_crypt_aes_decrypt_final(void *handle, uint8_t *buf, int32_t size, const uint8_t *tag, int32_t tag_length) { + mz_crypt_aes *aes = (mz_crypt_aes *)handle; + int out_len = 0; + + if (!aes || !tag || !tag_length || !aes->ctx || aes->mode != MZ_AES_MODE_GCM) + return MZ_PARAM_ERROR; + + if (buf && size) { + if (!EVP_DecryptUpdate(aes->ctx, buf, &size, buf, size)) + return MZ_CRYPT_ERROR; + } + + /* Set expected tag */ + if (!EVP_CIPHER_CTX_ctrl(aes->ctx, EVP_CTRL_GCM_SET_TAG, tag_length, (void *)tag)) { + aes->error = ERR_get_error(); + return MZ_CRYPT_ERROR; + } + + /* Must call DecryptFinal for tag verification */ + if (!EVP_DecryptFinal_ex(aes->ctx, NULL, &out_len)) { + aes->error = ERR_get_error(); + return MZ_CRYPT_ERROR; + } + + return size; +} + +static int32_t mz_crypt_aes_set_key(void *handle, const void *key, int32_t key_length, const void *iv, + int32_t iv_length, int32_t encrypt) { + mz_crypt_aes *aes = (mz_crypt_aes *)handle; + const EVP_CIPHER *type = NULL; + + switch (aes->mode) { + case MZ_AES_MODE_CBC: + if (key_length == 16) + type = EVP_aes_128_cbc(); + else if (key_length == 24) + type = EVP_aes_192_cbc(); + else if (key_length == 32) + type = EVP_aes_256_cbc(); + break; + case MZ_AES_MODE_ECB: + if (key_length == 16) + type = EVP_aes_128_ecb(); + else if (key_length == 24) + type = EVP_aes_192_ecb(); + else if (key_length == 32) + type = EVP_aes_256_ecb(); + break; + case MZ_AES_MODE_GCM: + if (key_length == 16) + type = EVP_aes_128_gcm(); + else if (key_length == 24) + type = EVP_aes_192_gcm(); + else if (key_length == 32) + type = EVP_aes_256_gcm(); + break; + } + if (!type) + return MZ_PARAM_ERROR; + + aes->ctx = EVP_CIPHER_CTX_new(); + if (!aes->ctx) + return MZ_MEM_ERROR; + + if (!EVP_CipherInit_ex(aes->ctx, type, NULL, NULL, NULL, encrypt)) { + aes->error = ERR_get_error(); + return MZ_INTERNAL_ERROR; + } + + if (aes->mode == MZ_AES_MODE_GCM && iv_length > MZ_AES_GCM_IV_SIZE) { + /* Init with non default GCM IV length */ + if (!EVP_CIPHER_CTX_ctrl(aes->ctx, EVP_CTRL_GCM_SET_IVLEN, iv_length, NULL)) { + aes->error = ERR_get_error(); + return MZ_SUPPORT_ERROR; + } + } + + if (!EVP_CipherInit_ex(aes->ctx, NULL, NULL, key, iv, encrypt)) { + aes->error = ERR_get_error(); + return MZ_HASH_ERROR; + } + + EVP_CIPHER_CTX_set_padding(aes->ctx, aes->mode == MZ_AES_MODE_GCM); + + return MZ_OK; +} + +int32_t mz_crypt_aes_set_encrypt_key(void *handle, const void *key, int32_t key_length, const void *iv, + int32_t iv_length) { + mz_crypt_aes *aes = (mz_crypt_aes *)handle; + + if (!aes || !key || !key_length) + return MZ_PARAM_ERROR; + if (key_length != 16 && key_length != 24 && key_length != 32) + return MZ_PARAM_ERROR; + if (aes->mode != MZ_AES_MODE_GCM) { + if (iv && iv_length != MZ_AES_BLOCK_SIZE) + return MZ_PARAM_ERROR; + } else { + if (!iv || iv_length < MZ_AES_GCM_IV_SIZE) + return MZ_PARAM_ERROR; + } + + mz_crypt_aes_reset(handle); + + return mz_crypt_aes_set_key(handle, key, key_length, iv, iv_length, 1); +} + +int32_t mz_crypt_aes_set_decrypt_key(void *handle, const void *key, int32_t key_length, const void *iv, + int32_t iv_length) { + mz_crypt_aes *aes = (mz_crypt_aes *)handle; + + if (!aes || !key || !key_length) + return MZ_PARAM_ERROR; + if (key_length != 16 && key_length != 24 && key_length != 32) + return MZ_PARAM_ERROR; + if (aes->mode != MZ_AES_MODE_GCM) { + if (iv && iv_length != MZ_AES_BLOCK_SIZE) + return MZ_PARAM_ERROR; + } else { + if (!iv || iv_length < MZ_AES_GCM_IV_SIZE) + return MZ_PARAM_ERROR; + } + + mz_crypt_aes_reset(handle); + + return mz_crypt_aes_set_key(handle, key, key_length, iv, iv_length, 0); +} + +void mz_crypt_aes_set_mode(void *handle, int32_t mode) { + mz_crypt_aes *aes = (mz_crypt_aes *)handle; + aes->mode = mode; +} + +void *mz_crypt_aes_create(void) { + mz_crypt_aes *aes = (mz_crypt_aes *)calloc(1, sizeof(mz_crypt_aes)); + return aes; +} + +void mz_crypt_aes_delete(void **handle) { + mz_crypt_aes *aes = NULL; + if (!handle) + return; + aes = (mz_crypt_aes *)*handle; + if (aes) { + mz_crypt_aes_free(*handle); + free(aes); + } + *handle = NULL; +} + +/***************************************************************************/ + +typedef struct mz_crypt_hmac_s { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + HMAC_CTX *ctx; +#else + EVP_MAC *mac; + EVP_MAC_CTX *ctx; +#endif + unsigned long error; + int32_t initialized; + uint16_t algorithm; +} mz_crypt_hmac; + +/***************************************************************************/ + +#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \ + (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x2070000fL)) +static HMAC_CTX *HMAC_CTX_new(void) { + HMAC_CTX *ctx = OPENSSL_malloc(sizeof(HMAC_CTX)); + if (ctx) + HMAC_CTX_init(ctx); + return ctx; +} + +static void HMAC_CTX_free(HMAC_CTX *ctx) { + if (ctx) { + HMAC_CTX_cleanup(ctx); + OPENSSL_free(ctx); + } +} +#endif + +/***************************************************************************/ + +static void mz_crypt_hmac_free(void *handle) { + mz_crypt_hmac *hmac = (mz_crypt_hmac *)handle; + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + HMAC_CTX_free(hmac->ctx); +#else + if (hmac->ctx) + EVP_MAC_CTX_free(hmac->ctx); + if (hmac->mac) + EVP_MAC_free(hmac->mac); + hmac->mac = NULL; +#endif + + hmac->ctx = NULL; +} + +void mz_crypt_hmac_reset(void *handle) { + mz_crypt_hmac *hmac = (mz_crypt_hmac *)handle; + + mz_crypt_init(); + mz_crypt_hmac_free(handle); + + hmac->error = 0; +} + +int32_t mz_crypt_hmac_init(void *handle, const void *key, int32_t key_length) { + mz_crypt_hmac *hmac = (mz_crypt_hmac *)handle; + int32_t result = 0; + + if (!hmac || !key) + return MZ_PARAM_ERROR; + + mz_crypt_hmac_reset(handle); + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const EVP_MD *evp_md = NULL; + + if (hmac->algorithm == MZ_HASH_SHA1) + evp_md = EVP_sha1(); + else + evp_md = EVP_sha256(); + + hmac->ctx = HMAC_CTX_new(); + if (!hmac->ctx) + return MZ_MEM_ERROR; + + result = HMAC_Init_ex(hmac->ctx, key, key_length, evp_md, NULL); +#else + char *digest_algorithm = NULL; + OSSL_PARAM params[2]; + + if (hmac->algorithm == MZ_HASH_SHA1) + digest_algorithm = "sha1"; + else + digest_algorithm = "sha256"; + + params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST, digest_algorithm, 0); + params[1] = OSSL_PARAM_construct_end(); + + hmac->mac = EVP_MAC_fetch(NULL, "HMAC", NULL); + if (!hmac->mac) + return MZ_MEM_ERROR; + hmac->ctx = EVP_MAC_CTX_new(hmac->mac); + if (!hmac->ctx) + return MZ_MEM_ERROR; + result = EVP_MAC_init(hmac->ctx, key, key_length, params); +#endif + + if (!result) { + hmac->error = ERR_get_error(); + return MZ_HASH_ERROR; + } + + return MZ_OK; +} + +int32_t mz_crypt_hmac_update(void *handle, const void *buf, int32_t size) { + mz_crypt_hmac *hmac = (mz_crypt_hmac *)handle; + int32_t result = 0; + + if (!hmac || !buf) + return MZ_PARAM_ERROR; + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + result = HMAC_Update(hmac->ctx, buf, size); +#else + result = EVP_MAC_update(hmac->ctx, buf, size); +#endif + if (!result) { + hmac->error = ERR_get_error(); + return MZ_HASH_ERROR; + } + + return MZ_OK; +} + +int32_t mz_crypt_hmac_end(void *handle, uint8_t *digest, int32_t digest_size) { + mz_crypt_hmac *hmac = (mz_crypt_hmac *)handle; + int32_t result = 0; + + if (!hmac || !digest) + return MZ_PARAM_ERROR; + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + if (hmac->algorithm == MZ_HASH_SHA1) { + if (digest_size < MZ_HASH_SHA1_SIZE) + return MZ_BUF_ERROR; + + result = HMAC_Final(hmac->ctx, digest, (uint32_t *)&digest_size); + } else { + if (digest_size < MZ_HASH_SHA256_SIZE) + return MZ_BUF_ERROR; + result = HMAC_Final(hmac->ctx, digest, (uint32_t *)&digest_size); + } +#else + { + size_t digest_outsize = digest_size; + result = EVP_MAC_final(hmac->ctx, digest, &digest_outsize, digest_size); + } +#endif + + if (!result) { + hmac->error = ERR_get_error(); + return MZ_HASH_ERROR; + } + + return MZ_OK; +} + +void mz_crypt_hmac_set_algorithm(void *handle, uint16_t algorithm) { + mz_crypt_hmac *hmac = (mz_crypt_hmac *)handle; + hmac->algorithm = algorithm; +} + +int32_t mz_crypt_hmac_copy(void *src_handle, void *target_handle) { + mz_crypt_hmac *source = (mz_crypt_hmac *)src_handle; + mz_crypt_hmac *target = (mz_crypt_hmac *)target_handle; + + if (!source || !target) + return MZ_PARAM_ERROR; + + mz_crypt_hmac_reset(target_handle); + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + if (!target->ctx) + target->ctx = HMAC_CTX_new(); + + if (!HMAC_CTX_copy(target->ctx, source->ctx)) { + target->error = ERR_get_error(); + return MZ_HASH_ERROR; + } +#else + if (!target->ctx) + target->ctx = EVP_MAC_CTX_dup(source->ctx); + if (!target->ctx) + return MZ_MEM_ERROR; +#endif + + return MZ_OK; +} + +void *mz_crypt_hmac_create(void) { + mz_crypt_hmac *hmac = (mz_crypt_hmac *)calloc(1, sizeof(mz_crypt_hmac)); + if (hmac) + hmac->algorithm = MZ_HASH_SHA256; + return hmac; +} + +void mz_crypt_hmac_delete(void **handle) { + mz_crypt_hmac *hmac = NULL; + if (!handle) + return; + hmac = (mz_crypt_hmac *)*handle; + if (hmac) { + mz_crypt_hmac_free(*handle); + free(hmac); + } + *handle = NULL; +} diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_os.c b/packages/zip/platforms/linux/src/minizip-ng/mz_os.c new file mode 100644 index 00000000..4250f875 --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_os.c @@ -0,0 +1,495 @@ +/* mz_os.c -- System functions + part of the minizip-ng project + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + Copyright (C) 1998-2010 Gilles Vollant + https://www.winimage.com/zLibDll/minizip.html + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#include "mz.h" +#include "mz_crypt.h" +#include "mz_os.h" +#include "mz_strm.h" +#include "mz_strm_os.h" + +#include /* tolower */ +#include + +/***************************************************************************/ + +int32_t mz_path_combine(char *path, const char *join, int32_t max_path) { + int32_t path_len = 0; + + if (!path || !join || !max_path) + return MZ_PARAM_ERROR; + + path_len = (int32_t)strlen(path); + + if (path_len == 0) { + strncpy(path, join, max_path - 1); + path[max_path - 1] = 0; + } else { + mz_path_append_slash(path, max_path, MZ_PATH_SLASH_PLATFORM); + path_len = (int32_t)strlen(path); + if (max_path > path_len) + strncat(path, join, max_path - path_len - 1); + } + + return MZ_OK; +} + +int32_t mz_path_append_slash(char *path, int32_t max_path, char slash) { + int32_t path_len = (int32_t)strlen(path); + if ((path_len + 2) >= max_path) + return MZ_BUF_ERROR; + if (!mz_os_is_dir_separator(path[path_len - 1])) { + path[path_len] = slash; + path[path_len + 1] = 0; + } + return MZ_OK; +} + +int32_t mz_path_remove_slash(char *path) { + int32_t path_len = (int32_t)strlen(path); + while (path_len > 0) { + if (mz_os_is_dir_separator(path[path_len - 1])) + path[path_len - 1] = 0; + else + break; + + path_len -= 1; + } + return MZ_OK; +} + +int32_t mz_path_has_slash(const char *path) { + int32_t path_len = (int32_t)strlen(path); + if (path_len > 0 && !mz_os_is_dir_separator(path[path_len - 1])) + return MZ_EXIST_ERROR; + return MZ_OK; +} + +int32_t mz_path_convert_slashes(char *path, char slash) { + int32_t i = 0; + + for (i = 0; i < (int32_t)strlen(path); i += 1) { + if (mz_os_is_dir_separator(path[i])) + path[i] = slash; + } + return MZ_OK; +} + +int32_t mz_path_compare_wc(const char *path, const char *wildcard, uint8_t ignore_case) { + while (*path != 0) { + switch (*wildcard) { + case '*': + + if (*(wildcard + 1) == 0) + return MZ_OK; + + while (*path != 0) { + if (mz_path_compare_wc(path, (wildcard + 1), ignore_case) == MZ_OK) + return MZ_OK; + + path += 1; + } + + return MZ_EXIST_ERROR; + + default: + /* Ignore differences in path slashes on platforms */ + if ((*path == '\\' && *wildcard == '/') || (*path == '/' && *wildcard == '\\')) + break; + + if (ignore_case) { + if (tolower(*path) != tolower(*wildcard)) + return MZ_EXIST_ERROR; + } else { + if (*path != *wildcard) + return MZ_EXIST_ERROR; + } + + break; + } + + path += 1; + wildcard += 1; + } + + if ((*wildcard != 0) && (*wildcard != '*')) + return MZ_EXIST_ERROR; + + return MZ_OK; +} + +int32_t mz_path_resolve(const char *path, char *output, int32_t max_output) { + const char *source = path; + const char *check = output; + char *target = output; + + if (max_output <= 0) + return MZ_PARAM_ERROR; + + while (*source != 0 && max_output > 1) { + check = source; + if (mz_os_is_dir_separator(*check)) + check += 1; + + if (source == path || target == output || check != source) { + /* Skip double paths */ + if (mz_os_is_dir_separator(*check)) { + source += 1; + continue; + } + if (*check == '.') { + check += 1; + + /* Remove . if at end of string and not at the beginning */ + if (*check == 0 && source != path && target != output) { + /* Copy last slash */ + *target = *source; + target += 1; + max_output -= 1; + source += (check - source); + continue; + } + /* Remove . if not at end of string */ + else if (mz_os_is_dir_separator(*check)) { + source += (check - source); + /* Skip slash if at beginning of string */ + if (target == output && *source != 0) + source += 1; + continue; + } + /* Go to parent directory .. */ + else if (*check == '.') { + check += 1; + if (*check == 0 || mz_os_is_dir_separator(*check)) { + source += (check - source); + + /* Search backwards for previous slash or the start of the output string */ + if (target != output) { + target -= 1; + do { + if (target == output || mz_os_is_dir_separator(*target)) + break; + + target -= 1; + max_output += 1; + } while (target > output); + } + + if ((target == output) && *source != 0) + source += 1; + if (mz_os_is_dir_separator(*target) && *source == 0) + target += 1; + + *target = 0; + continue; + } + } + } + } + + *target = *source; + + source += 1; + target += 1; + max_output -= 1; + } + + *target = 0; + + if (*path == 0) + return MZ_INTERNAL_ERROR; + + return MZ_OK; +} + +int32_t mz_path_remove_filename(char *path) { + char *path_ptr = NULL; + + if (!path) + return MZ_PARAM_ERROR; + + path_ptr = path + strlen(path) - 1; + + while (path_ptr > path) { + if (mz_os_is_dir_separator(*path_ptr)) { + *path_ptr = 0; + break; + } + + path_ptr -= 1; + } + + if (path_ptr == path) + *path_ptr = 0; + + return MZ_OK; +} + +int32_t mz_path_remove_extension(char *path) { + char *path_ptr = NULL; + + if (!path) + return MZ_PARAM_ERROR; + + path_ptr = path + strlen(path) - 1; + + while (path_ptr > path) { + if (mz_os_is_dir_separator(*path_ptr)) + break; + if (*path_ptr == '.') { + *path_ptr = 0; + break; + } + + path_ptr -= 1; + } + + if (path_ptr == path) + *path_ptr = 0; + + return MZ_OK; +} + +int32_t mz_path_get_filename(const char *path, const char **filename) { + const char *match = NULL; + + if (!path || !filename) + return MZ_PARAM_ERROR; + + *filename = NULL; + + for (match = path; *match != 0; match += 1) { + if (mz_os_is_dir_separator(*match)) + *filename = match + 1; + } + + if (!*filename) + return MZ_EXIST_ERROR; + + return MZ_OK; +} + +int32_t mz_path_is_symlink_target_safe(const char *link_path, const char *target, const char *base_path) { + char *combined = NULL; + char *resolved = NULL; + size_t max_path = 1024; + size_t base_len = 0; + size_t parent_len = 0; + int32_t err = MZ_OK; + + if (!link_path || !target || !base_path) + return MZ_PARAM_ERROR; + + /* Absolute symlink targets are not allowed */ + if (mz_os_is_dir_separator(target[0])) + return MZ_EXIST_ERROR; + + base_len = strlen(base_path); + + /* Remove trailing slash from base_path for comparison */ + while (base_len > 0 && mz_os_is_dir_separator(base_path[base_len - 1])) + base_len--; + + combined = (char *)calloc(1, max_path); + resolved = (char *)calloc(1, max_path); + + if (!combined || !resolved) { + err = MZ_MEM_ERROR; + goto target_cleanup; + } + + /* Find parent directory length by scanning backwards past filename and trailing slashes */ + parent_len = strlen(link_path); + while (parent_len > 0 && !mz_os_is_dir_separator(link_path[parent_len - 1])) + parent_len--; + while (parent_len > 0 && mz_os_is_dir_separator(link_path[parent_len - 1])) + parent_len--; + + /* Combine parent + target */ + combined[0] = 0; + if (parent_len > 0) { + strncpy(combined, link_path, parent_len); + combined[parent_len] = 0; + mz_path_append_slash(combined, (int32_t)max_path, MZ_PATH_SLASH_PLATFORM); + } + strncat(combined, target, max_path - strlen(combined) - 1); + + /* Resolve the combined path to eliminate .. */ + if (mz_path_resolve(combined, resolved, (int32_t)max_path) != MZ_OK) { + err = MZ_EXIST_ERROR; + goto target_cleanup; + } + + /* Check that resolved path stays within base_path */ + if (strlen(resolved) < base_len || strncmp(resolved, base_path, base_len) != 0 || + (resolved[base_len] != 0 && !mz_os_is_dir_separator(resolved[base_len]))) + err = MZ_EXIST_ERROR; + +target_cleanup: + free(combined); + free(resolved); + + return err; +} + +int32_t mz_dir_has_unsafe_symlink(const char *path, const char *base_path) { + char *check_path = NULL; + char *symlink_target = NULL; + size_t path_len = 0; + size_t base_len = 0; + size_t max_path = 1024; + size_t pos = 0; + size_t cmp_len = 0; + int32_t err = MZ_OK; + + if (!path || *path == 0 || !base_path) + return MZ_PARAM_ERROR; + + path_len = strlen(path); + base_len = strlen(base_path); + + /* Remove trailing slash from base_path for comparison */ + while (base_len > 0 && mz_os_is_dir_separator(base_path[base_len - 1])) + base_len--; + + check_path = (char *)calloc(1, path_len + 1); + if (!check_path) + return MZ_MEM_ERROR; + + /* Walk through each path component */ + while (err == MZ_OK && pos < path_len) { + /* Copy separator if present */ + if (mz_os_is_dir_separator(path[pos])) { + check_path[pos] = path[pos]; + pos++; + } + + /* Copy next path component */ + while (pos < path_len && !mz_os_is_dir_separator(path[pos])) { + check_path[pos] = path[pos]; + pos++; + } + check_path[pos] = 0; + + /* Check if this existing path component is a symlink */ + if (mz_os_is_symlink(check_path) != MZ_OK) + continue; + + /* Skip components at or above the base dir. */ + cmp_len = pos; + if (mz_path_has_slash(check_path) == MZ_OK) + cmp_len--; + if (cmp_len <= base_len && strncmp(check_path, base_path, cmp_len) == 0) { + /* Verify that the prefix match is on a directory boundary. */ + if (cmp_len == base_len || mz_os_is_dir_separator(base_path[cmp_len])) + continue; + } + + /* Allocate symlink target buffer on first use */ + if (!symlink_target) { + symlink_target = (char *)calloc(1, max_path); + if (!symlink_target) { + err = MZ_MEM_ERROR; + break; + } + } + + if (mz_os_read_symlink(check_path, symlink_target, max_path) != MZ_OK) { + err = MZ_EXIST_ERROR; + break; + } + + /* Reject the component if its symlink target escapes the base path */ + err = mz_path_is_symlink_target_safe(check_path, symlink_target, base_path); + if (err != MZ_OK) + break; + } + + free(check_path); + free(symlink_target); + + return err; +} + +int32_t mz_dir_make(const char *path) { + int32_t err = MZ_OK; + char *current_dir = NULL; + char *match = NULL; + char hold = 0; + + if (!*path) + return MZ_OK; + + current_dir = strdup(path); + if (!current_dir) + return MZ_MEM_ERROR; + + mz_path_remove_slash(current_dir); + + err = mz_os_make_dir(current_dir); + if (err != MZ_OK) { + match = current_dir + 1; + while (1) { + while (*match != 0 && !mz_os_is_dir_separator(*match)) + match += 1; + hold = *match; + *match = 0; + + err = mz_os_make_dir(current_dir); + if (err != MZ_OK) + break; + if (hold == 0) + break; + + *match = hold; + match += 1; + } + } + + free(current_dir); + return err; +} + +int32_t mz_file_get_crc(const char *path, uint32_t *result_crc) { + void *stream = NULL; + uint32_t crc32 = 0; + int32_t read = 0; + int32_t err = MZ_OK; + uint8_t buf[16384]; + + stream = mz_stream_os_create(); + if (!stream) + return MZ_MEM_ERROR; + + err = mz_stream_os_open(stream, path, MZ_OPEN_MODE_READ); + if (err == MZ_OK) { + do { + read = mz_stream_os_read(stream, buf, sizeof(buf)); + + if (read < 0) { + err = read; + break; + } + + crc32 = mz_crypt_crc32_update(crc32, buf, read); + } while ((err == MZ_OK) && (read > 0)); + + mz_stream_os_close(stream); + } + + *result_crc = crc32; + + mz_stream_os_delete(&stream); + + return err; +} + +/***************************************************************************/ diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_os.h b/packages/zip/platforms/linux/src/minizip-ng/mz_os.h new file mode 100644 index 00000000..19c269d4 --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_os.h @@ -0,0 +1,201 @@ +/* mz_os.h -- System functions + part of the minizip-ng project + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#ifndef MZ_OS_H +#define MZ_OS_H + +/***************************************************************************/ + +#if defined(__APPLE__) +# define MZ_VERSION_MADEBY_HOST_SYSTEM (MZ_HOST_SYSTEM_OSX_DARWIN) +#elif defined(__riscos__) +# define MZ_VERSION_MADEBY_HOST_SYSTEM (MZ_HOST_SYSTEM_RISCOS) +#elif defined(_WIN32) +# define MZ_VERSION_MADEBY_HOST_SYSTEM (MZ_HOST_SYSTEM_WINDOWS_NTFS) +#else +# define MZ_VERSION_MADEBY_HOST_SYSTEM (MZ_HOST_SYSTEM_UNIX) +#endif + +#if defined(HAVE_LZMA) || defined(HAVE_LIBCOMP) || defined(HAVE_PPMD) +# define MZ_VERSION_MADEBY_ZIP_VERSION (63) +#elif defined(HAVE_WZAES) +# define MZ_VERSION_MADEBY_ZIP_VERSION (51) +#elif defined(HAVE_BZIP2) +# define MZ_VERSION_MADEBY_ZIP_VERSION (46) +#else +# define MZ_VERSION_MADEBY_ZIP_VERSION (45) +#endif + +#define MZ_VERSION_MADEBY ((MZ_VERSION_MADEBY_HOST_SYSTEM << 8) | (MZ_VERSION_MADEBY_ZIP_VERSION)) + +#define MZ_PATH_SLASH_UNIX ('/') +#define MZ_PATH_SLASH_WINDOWS ('\\') +#if defined(_WIN32) +# define MZ_PATH_SLASH_PLATFORM (MZ_PATH_SLASH_WINDOWS) +#else +# define MZ_PATH_SLASH_PLATFORM (MZ_PATH_SLASH_UNIX) +#endif + +/***************************************************************************/ + +#include "mz_config.h" + +#if HAVE_DIRENT_H +# include +#elif HAVE_SYS_DIRENT_H +# include +#else +struct dirent { + char d_name[256]; +}; +#endif + +#if !HAVE_PDIR +typedef struct DIR DIR; +#endif + +/***************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +/***************************************************************************/ +/* Shared functions */ + +int32_t mz_path_combine(char *path, const char *join, int32_t max_path); +/* Combines two paths */ + +int32_t mz_path_append_slash(char *path, int32_t max_path, char slash); +/* Appends a path slash on to the end of the path */ + +int32_t mz_path_remove_slash(char *path); +/* Removes a path slash from the end of the path */ + +int32_t mz_path_has_slash(const char *path); +/* Returns whether or not the path ends with slash */ + +int32_t mz_path_convert_slashes(char *path, char slash); +/* Converts the slashes in a path */ + +int32_t mz_path_compare_wc(const char *path, const char *wildcard, uint8_t ignore_case); +/* Compare two paths with wildcard */ + +int32_t mz_path_resolve(const char *path, char *target, int32_t max_target); +/* Resolves path */ + +int32_t mz_path_remove_filename(char *path); +/* Remove the filename from a path */ + +int32_t mz_path_remove_extension(char *path); +/* Remove the extension from a path */ + +int32_t mz_path_get_filename(const char *path, const char **filename); +/* Get the filename from a path */ + +int32_t mz_path_is_symlink_target_safe(const char *link_path, const char *target, const char *base_path); +/* Checks if a symlink target resolves within base path. */ + +int32_t mz_dir_has_unsafe_symlink(const char *path, const char *base_path); +/* Checks if any existing component of path is a symlink that escapes base path. */ + +int32_t mz_dir_make(const char *path); +/* Creates a directory recursively */ + +int32_t mz_file_get_crc(const char *path, uint32_t *result_crc); +/* Gets the crc32 hash of a file */ + +/***************************************************************************/ +/* Platform specific functions */ + +wchar_t *mz_os_unicode_string_create(const char *string, int32_t encoding); +/* Create unicode string from a utf8 string */ + +void mz_os_unicode_string_delete(wchar_t **string); +/* Delete a unicode string that was created */ + +char *mz_os_utf8_string_create(const char *string, int32_t encoding); +/* Create a utf8 string from a string with another encoding */ + +void mz_os_utf8_string_delete(char **string); +/* Delete a utf8 string that was created */ + +int32_t mz_os_rand(uint8_t *buf, int32_t size); +/* Random number generator (not cryptographically secure) */ + +int32_t mz_os_rename(const char *source_path, const char *target_path); +/* Rename a file */ + +int32_t mz_os_unlink(const char *path); +/* Delete an existing file */ + +int32_t mz_os_file_exists(const char *path); +/* Check to see if a file exists */ + +int64_t mz_os_get_file_size(const char *path); +/* Gets the length of a file */ + +int32_t mz_os_get_file_date(const char *path, time_t *modified_date, time_t *accessed_date, time_t *creation_date); +/* Gets a file's modified, access, and creation dates if supported */ + +int32_t mz_os_set_file_date(const char *path, time_t modified_date, time_t accessed_date, time_t creation_date); +/* Sets a file's modified, access, and creation dates if supported */ + +int32_t mz_os_get_file_attribs(const char *path, uint32_t *attributes); +/* Gets a file's attributes, following symbolic links */ + +int32_t mz_os_set_file_attribs(const char *path, uint32_t attributes); +/* Sets a file's attributes */ + +int32_t mz_os_get_temp_path(char *path, int32_t max_path, const char *prefix); +/* Gets a unique temporary file path */ + +int32_t mz_os_path_same_fs(const char *path_a, const char *path_b); +/* Checks if both paths are on the same filesystem */ + +int32_t mz_os_make_dir(const char *path); +/* Recursively creates a directory */ + +DIR *mz_os_open_dir(const char *path); +/* Opens a directory for listing */ +struct dirent *mz_os_read_dir(DIR *dir); +/* Reads a directory listing entry */ + +int32_t mz_os_close_dir(DIR *dir); +/* Closes a directory that has been opened for listing */ + +int32_t mz_os_is_dir_separator(char c); +/* Checks to see if character is a directory separator */ + +int32_t mz_os_is_dir(const char *path); +/* Checks to see if path is a directory */ + +int32_t mz_os_is_symlink(const char *path); +/* Checks to see if path is a symbolic link */ + +int32_t mz_os_get_link_attribs(const char *path, uint32_t *attributes); +/* Gets a symbolic link's attributes */ + +int32_t mz_os_make_symlink(const char *path, const char *target_path); +/* Creates a symbolic link pointing to a target */ + +int32_t mz_os_read_symlink(const char *path, char *target_path, int32_t max_target_path); +/* Gets the target path for a symbolic link */ + +uint64_t mz_os_ms_time(void); +/* Gets the time in milliseconds */ + +/***************************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_os_posix.c b/packages/zip/platforms/linux/src/minizip-ng/mz_os_posix.c new file mode 100644 index 00000000..b0378a24 --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_os_posix.c @@ -0,0 +1,445 @@ +/* mz_os_posix.c -- System functions for posix + part of the minizip-ng project + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#include "mz.h" +#include "mz_config.h" +#include "mz_strm.h" +#include "mz_os.h" + +#include /* rename */ +#include +#if defined(HAVE_ICONV) +# include +#endif +#include +#include +#include + +#ifndef _WIN32 +# include +# include +#endif +#if defined(__APPLE__) +# include +# include +#endif + +#if defined(HAVE_GETRANDOM) +# include +#endif +#if defined(HAVE_LIBBSD) +# include /* arc4random_buf */ +#endif + +#ifndef MZ_PRESERVE_NATIVE_STRUCTURE +# define MZ_PRESERVE_NATIVE_STRUCTURE 1 +#endif + +/***************************************************************************/ + +#if defined(HAVE_ICONV) +char *mz_os_utf8_string_create(const char *string, int32_t encoding) { + iconv_t cd; + /// up to CP2147483647 + char string_encoding[13]; + const char *from_encoding = NULL; + size_t result = 0; + size_t string_length = 0; + size_t string_utf8_size = 0; + char *string_utf8 = NULL; + char *string_utf8_ptr = NULL; + + if (!string || encoding <= 0) + return NULL; + + if (encoding == MZ_ENCODING_UTF8) + from_encoding = "UTF-8"; + else { + snprintf(string_encoding, sizeof(string_encoding), "CP%03" PRId32, encoding); + from_encoding = string_encoding; + } + + cd = iconv_open("UTF-8", from_encoding); + if (cd == (iconv_t)-1) + return NULL; + + string_length = strlen(string); + string_utf8_size = string_length * 2; + string_utf8 = (char *)calloc((int32_t)(string_utf8_size + 1), sizeof(char)); + string_utf8_ptr = string_utf8; + + if (string_utf8) { + result = iconv(cd, (char **)&string, &string_length, (char **)&string_utf8_ptr, &string_utf8_size); + } + + iconv_close(cd); + + if (result == (size_t)-1) { + free(string_utf8); + string_utf8 = NULL; + } + + return string_utf8; +} +#else +char *mz_os_utf8_string_create(const char *string, int32_t encoding) { + return strdup(string); +} +#endif + +void mz_os_utf8_string_delete(char **string) { + if (string) { + free(*string); + *string = NULL; + } +} + +/***************************************************************************/ + +#if defined(HAVE_GETRANDOM) +int32_t mz_os_rand(uint8_t *buf, int32_t size) { + int32_t left = size; + int32_t written = 0; + + while (left > 0) { + written = getrandom(buf, left, 0); + if (written < 0) + return MZ_INTERNAL_ERROR; + + buf += written; + left -= written; + } + return size - left; +} +#elif defined(HAVE_ARC4RANDOM_BUF) +int32_t mz_os_rand(uint8_t *buf, int32_t size) { + if (size < 0) + return 0; + arc4random_buf(buf, (uint32_t)size); + return size; +} +#elif defined(HAVE_ARC4RANDOM) +int32_t mz_os_rand(uint8_t *buf, int32_t size) { + int32_t left = size; + for (; left > 2; left -= 3, buf += 3) { + uint32_t val = arc4random(); + + buf[0] = (val) & 0xFF; + buf[1] = (val >> 8) & 0xFF; + buf[2] = (val >> 16) & 0xFF; + } + for (; left > 0; left--, buf++) { + *buf = arc4random() & 0xFF; + } + return size - left; +} +#else +int32_t mz_os_rand(uint8_t *buf, int32_t size) { + static unsigned calls = 0; + int32_t i = 0; + + /* Ensure different random header each time */ + if (++calls == 1) { +# define PI_SEED 3141592654UL + srand((unsigned)(time(NULL) ^ PI_SEED)); + } + + while (i < size) + buf[i++] = (rand() >> 7) & 0xff; + + return size; +} +#endif + +int32_t mz_os_rename(const char *source_path, const char *target_path) { + if (rename(source_path, target_path) == -1) + return MZ_EXIST_ERROR; + + return MZ_OK; +} + +int32_t mz_os_path_same_fs(const char *path_a, const char *path_b) { + struct stat sa, sb; + if (!path_a || !path_b) + return MZ_PARAM_ERROR; + if (stat(path_a, &sa) != 0 || stat(path_b, &sb) != 0) + return MZ_EXIST_ERROR; + return (sa.st_dev == sb.st_dev) ? MZ_OK : MZ_EXIST_ERROR; +} + +int32_t mz_os_unlink(const char *path) { + if (unlink(path) == -1) + return MZ_EXIST_ERROR; + + return MZ_OK; +} + +int32_t mz_os_file_exists(const char *path) { + struct stat path_stat; + + memset(&path_stat, 0, sizeof(path_stat)); + if (stat(path, &path_stat) == 0) + return MZ_OK; + return MZ_EXIST_ERROR; +} + +int64_t mz_os_get_file_size(const char *path) { + struct stat path_stat; + + memset(&path_stat, 0, sizeof(path_stat)); + if (stat(path, &path_stat) == 0) { + /* Stat returns size taken up by directory entry, so return 0 */ + if (S_ISDIR(path_stat.st_mode)) + return 0; + + return path_stat.st_size; + } + + return 0; +} + +int32_t mz_os_get_file_date(const char *path, time_t *modified_date, time_t *accessed_date, time_t *creation_date) { + struct stat path_stat; + char *name = NULL; + int32_t err = MZ_INTERNAL_ERROR; + + memset(&path_stat, 0, sizeof(path_stat)); + + if (strcmp(path, "-") != 0) { + /* Not all systems allow stat'ing a file with / appended */ + name = strdup(path); + mz_path_remove_slash(name); + + if (stat(name, &path_stat) == 0) { + if (modified_date) + *modified_date = path_stat.st_mtime; + if (accessed_date) + *accessed_date = path_stat.st_atime; + /* Creation date not supported */ + if (creation_date) + *creation_date = 0; + + err = MZ_OK; + } + + free(name); + } + + return err; +} + +int32_t mz_os_set_file_date(const char *path, time_t modified_date, time_t accessed_date, time_t creation_date) { + struct utimbuf ut; + + ut.actime = accessed_date; + ut.modtime = modified_date; + + /* Creation date not supported */ + MZ_UNUSED(creation_date); + + if (utime(path, &ut) != 0) + return MZ_INTERNAL_ERROR; + + return MZ_OK; +} + +int32_t mz_os_get_file_attribs(const char *path, uint32_t *attributes) { + struct stat path_stat; + int32_t err = MZ_OK; + + memset(&path_stat, 0, sizeof(path_stat)); + if (stat(path, &path_stat) == -1) + err = MZ_INTERNAL_ERROR; + *attributes = path_stat.st_mode; + return err; +} + +int32_t mz_os_set_file_attribs(const char *path, uint32_t attributes) { + int32_t err = MZ_OK; + + if (chmod(path, (mode_t)attributes) == -1) + err = MZ_INTERNAL_ERROR; + + return err; +} + +int32_t mz_os_make_dir(const char *path) { + int32_t err = 0; + + err = mkdir(path, 0755); + + if (err != 0 && errno != EEXIST) + return MZ_INTERNAL_ERROR; + + return MZ_OK; +} + +DIR *mz_os_open_dir(const char *path) { + return opendir(path); +} + +struct dirent *mz_os_read_dir(DIR *dir) { + if (!dir) + return NULL; + return readdir(dir); +} + +int32_t mz_os_close_dir(DIR *dir) { + if (!dir) + return MZ_PARAM_ERROR; + if (closedir(dir) == -1) + return MZ_INTERNAL_ERROR; + return MZ_OK; +} + +int32_t mz_os_is_dir_separator(char c) { +#if MZ_PRESERVE_NATIVE_STRUCTURE + // While not strictly adhering to 4.4.17.1, + // this preserves UNIX filesystem structure. + return c == '/'; +#else + // While strictly adhering to 4.4.17.1, + // this corrupts UNIX filesystem structure (a filename with a '\\' will become a folder + a file). + return c == '\\' || c == '/'; +#endif +} + +int32_t mz_os_is_dir(const char *path) { + struct stat path_stat; + + memset(&path_stat, 0, sizeof(path_stat)); + stat(path, &path_stat); + if (S_ISDIR(path_stat.st_mode)) + return MZ_OK; + + return MZ_EXIST_ERROR; +} + +int32_t mz_os_is_symlink(const char *path) { + struct stat path_stat; + + memset(&path_stat, 0, sizeof(path_stat)); + lstat(path, &path_stat); + if (S_ISLNK(path_stat.st_mode)) + return MZ_OK; + + return MZ_EXIST_ERROR; +} + +int32_t mz_os_get_link_attribs(const char *path, uint32_t *attributes) { + struct stat path_stat; + int32_t err = MZ_OK; + + memset(&path_stat, 0, sizeof(path_stat)); + if (lstat(path, &path_stat) == -1) + err = MZ_INTERNAL_ERROR; + *attributes = path_stat.st_mode; + return err; +} + +int32_t mz_os_make_symlink(const char *path, const char *target_path) { +#if !HAVE_SYMLINK + return MZ_SUPPORT_ERROR; +#else + if (symlink(target_path, path) != 0) + return MZ_INTERNAL_ERROR; + return MZ_OK; +#endif +} + +int32_t mz_os_read_symlink(const char *path, char *target_path, int32_t max_target_path) { +#if !HAVE_READLINK + return MZ_SUPPORT_ERROR; +#else + size_t length = 0; + + length = (size_t)readlink(path, target_path, max_target_path - 1); + if (length == (size_t)-1) + return MZ_EXIST_ERROR; + if (length >= (size_t)(max_target_path - 1)) + return MZ_BUF_ERROR; + + target_path[length] = 0; + return MZ_OK; +#endif +} + +int32_t mz_os_get_temp_path(char *path, int32_t max_path, const char *prefix) { + const char *tmp_dir = NULL; + char *temp_path; + int32_t result = 0; + + if (!path || max_path <= 0) + return MZ_PARAM_ERROR; + + tmp_dir = getenv("TMPDIR"); + if (!tmp_dir) + tmp_dir = getenv("TMP"); + if (!tmp_dir) + tmp_dir = getenv("TEMP"); + if (!tmp_dir) + tmp_dir = "/tmp"; + + /* Construct path for mkdtemp in the form /XXXXXX */ + temp_path = (char *)calloc(max_path, sizeof(char)); + if (!temp_path) + return MZ_MEM_ERROR; + + /* mkdtemp replaces XXXXXX with unique characters */ + result = snprintf(temp_path, max_path, "%s/%sXXXXXX", tmp_dir, prefix ? prefix : ""); + if (result < 0 || result >= max_path) { + free(temp_path); + return MZ_BUF_ERROR; + } + + /* Create a temporary directory. */ + if (!mkdtemp(temp_path)) { + free(temp_path); + return MZ_INTERNAL_ERROR; + } + + /* Create a filename inside the temporary directory using current time */ + result = snprintf(path, max_path, "%s/%lux", temp_path, time(NULL)); + if (result < 0 || result >= max_path) { + rmdir(temp_path); + free(temp_path); + return MZ_BUF_ERROR; + } + + free(temp_path); + return MZ_OK; +} + +uint64_t mz_os_ms_time(void) { + struct timespec ts; + +#if defined(__APPLE__) + clock_serv_t cclock; + mach_timespec_t mts; + + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + + ts.tv_sec = mts.tv_sec; + ts.tv_nsec = mts.tv_nsec; +#elif !defined(_POSIX_MONOTONIC_CLOCK) || _POSIX_MONOTONIC_CLOCK < 0 + clock_gettime(CLOCK_REALTIME, &ts); +#elif _POSIX_MONOTONIC_CLOCK > 0 + clock_gettime(CLOCK_MONOTONIC, &ts); +#else + if (sysconf(_SC_MONOTONIC_CLOCK) > 0) + clock_gettime(CLOCK_MONOTONIC, &ts); + else + clock_gettime(CLOCK_REALTIME, &ts); +#endif + + return ((uint64_t)ts.tv_sec * 1000) + ((uint64_t)ts.tv_nsec / 1000000); +} diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_strm.c b/packages/zip/platforms/linux/src/minizip-ng/mz_strm.c new file mode 100644 index 00000000..1bacb2ed --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_strm.c @@ -0,0 +1,545 @@ +/* mz_strm.c -- Stream interface + part of the minizip-ng project + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#include "mz.h" +#include "mz_strm.h" + +/***************************************************************************/ + +#define MZ_STREAM_FIND_SIZE (1024) + +/***************************************************************************/ + +int32_t mz_stream_open(void *stream, const char *path, int32_t mode) { + mz_stream *strm = (mz_stream *)stream; + if (!strm || !strm->vtbl || !strm->vtbl->open) + return MZ_STREAM_ERROR; + return strm->vtbl->open(strm, path, mode); +} + +int32_t mz_stream_is_open(void *stream) { + mz_stream *strm = (mz_stream *)stream; + if (!strm || !strm->vtbl || !strm->vtbl->is_open) + return MZ_STREAM_ERROR; + return strm->vtbl->is_open(strm); +} + +int32_t mz_stream_read(void *stream, void *buf, int32_t size) { + mz_stream *strm = (mz_stream *)stream; + if (!strm || !strm->vtbl || !strm->vtbl->read) + return MZ_PARAM_ERROR; + if (mz_stream_is_open(strm) != MZ_OK) + return MZ_STREAM_ERROR; + return strm->vtbl->read(strm, buf, size); +} + +static int32_t mz_stream_read_value(void *stream, uint64_t *value, int32_t len) { + uint8_t buf[8]; + int32_t n = 0; + int32_t i = 0; + + *value = 0; + if (mz_stream_read(stream, buf, len) == len) { + for (n = 0; n < len; n += 1, i += 8) + *value += ((uint64_t)buf[n]) << i; + } else if (mz_stream_error(stream)) + return MZ_STREAM_ERROR; + else + return MZ_END_OF_STREAM; + + return MZ_OK; +} + +int32_t mz_stream_read_uint8(void *stream, uint8_t *value) { + int32_t err = MZ_OK; + uint64_t value64 = 0; + + *value = 0; + err = mz_stream_read_value(stream, &value64, sizeof(uint8_t)); + if (err == MZ_OK) + *value = (uint8_t)value64; + return err; +} + +int32_t mz_stream_read_uint16(void *stream, uint16_t *value) { + int32_t err = MZ_OK; + uint64_t value64 = 0; + + *value = 0; + err = mz_stream_read_value(stream, &value64, sizeof(uint16_t)); + if (err == MZ_OK) + *value = (uint16_t)value64; + return err; +} + +int32_t mz_stream_read_uint32(void *stream, uint32_t *value) { + int32_t err = MZ_OK; + uint64_t value64 = 0; + + *value = 0; + err = mz_stream_read_value(stream, &value64, sizeof(uint32_t)); + if (err == MZ_OK) + *value = (uint32_t)value64; + return err; +} + +int32_t mz_stream_read_int64(void *stream, int64_t *value) { + return mz_stream_read_value(stream, (uint64_t *)value, sizeof(uint64_t)); +} + +int32_t mz_stream_read_uint64(void *stream, uint64_t *value) { + return mz_stream_read_value(stream, value, sizeof(uint64_t)); +} + +int32_t mz_stream_write(void *stream, const void *buf, int32_t size) { + mz_stream *strm = (mz_stream *)stream; + if (size == 0) + return size; + if (!strm || !strm->vtbl || !strm->vtbl->write) + return MZ_PARAM_ERROR; + if (mz_stream_is_open(strm) != MZ_OK) + return MZ_STREAM_ERROR; + return strm->vtbl->write(strm, buf, size); +} + +static int32_t mz_stream_write_value(void *stream, uint64_t value, int32_t len) { + mz_stream *strm = (mz_stream *)stream; + uint8_t buf[8]; + int32_t n = 0; + + if (!strm) + return MZ_PARAM_ERROR; + for (n = 0; n < len; n += 1) { + buf[n] = (uint8_t)(value & 0xff); + value >>= 8; + } + + if (value != 0) { + /* Data overflow - hack for ZIP64 (X Roche) */ + for (n = 0; n < len; n += 1) + buf[n] = 0xff; + } + + if (mz_stream_write(strm, buf, len) != len) + return MZ_STREAM_ERROR; + + return MZ_OK; +} + +int32_t mz_stream_write_uint8(void *stream, uint8_t value) { + return mz_stream_write_value(stream, value, sizeof(uint8_t)); +} + +int32_t mz_stream_write_uint16(void *stream, uint16_t value) { + return mz_stream_write_value(stream, value, sizeof(uint16_t)); +} + +int32_t mz_stream_write_uint32(void *stream, uint32_t value) { + return mz_stream_write_value(stream, value, sizeof(uint32_t)); +} + +int32_t mz_stream_write_int64(void *stream, int64_t value) { + return mz_stream_write_value(stream, (uint64_t)value, sizeof(uint64_t)); +} + +int32_t mz_stream_write_uint64(void *stream, uint64_t value) { + return mz_stream_write_value(stream, value, sizeof(uint64_t)); +} + +int32_t mz_stream_copy(void *target, void *source, int32_t len) { + return mz_stream_copy_stream(target, NULL, source, NULL, len); +} + +int32_t mz_stream_copy_to_end(void *target, void *source) { + return mz_stream_copy_stream_to_end(target, NULL, source, NULL); +} + +int32_t mz_stream_copy_stream(void *target, mz_stream_write_cb write_cb, void *source, mz_stream_read_cb read_cb, + int32_t len) { + uint8_t buf[16384]; + int32_t bytes_to_copy = 0; + int32_t read = 0; + int32_t written = 0; + + if (!write_cb) + write_cb = mz_stream_write; + if (!read_cb) + read_cb = mz_stream_read; + + while (len > 0) { + bytes_to_copy = len; + if (bytes_to_copy > (int32_t)sizeof(buf)) + bytes_to_copy = sizeof(buf); + read = read_cb(source, buf, bytes_to_copy); + if (read <= 0) + return MZ_STREAM_ERROR; + written = write_cb(target, buf, read); + if (written != read) + return MZ_STREAM_ERROR; + len -= read; + } + + return MZ_OK; +} + +int32_t mz_stream_copy_stream_to_end(void *target, mz_stream_write_cb write_cb, void *source, + mz_stream_read_cb read_cb) { + uint8_t buf[16384]; + int32_t read = 0; + int32_t written = 0; + + if (!write_cb) + write_cb = mz_stream_write; + if (!read_cb) + read_cb = mz_stream_read; + + read = read_cb(source, buf, sizeof(buf)); + while (read > 0) { + written = write_cb(target, buf, read); + if (written != read) + return MZ_STREAM_ERROR; + read = read_cb(source, buf, sizeof(buf)); + } + + if (read < 0) + return MZ_STREAM_ERROR; + + return MZ_OK; +} + +int64_t mz_stream_tell(void *stream) { + mz_stream *strm = (mz_stream *)stream; + if (!strm || !strm->vtbl || !strm->vtbl->tell) + return MZ_PARAM_ERROR; + if (mz_stream_is_open(strm) != MZ_OK) + return MZ_STREAM_ERROR; + return strm->vtbl->tell(strm); +} + +int32_t mz_stream_seek(void *stream, int64_t offset, int32_t origin) { + mz_stream *strm = (mz_stream *)stream; + if (!strm || !strm->vtbl || !strm->vtbl->seek) + return MZ_PARAM_ERROR; + if (mz_stream_is_open(strm) != MZ_OK) + return MZ_STREAM_ERROR; + if (origin == MZ_SEEK_SET && offset < 0) + return MZ_SEEK_ERROR; + return strm->vtbl->seek(strm, offset, origin); +} + +int32_t mz_stream_find(void *stream, const void *find, int32_t find_size, int64_t max_seek, int64_t *position) { + uint8_t buf[MZ_STREAM_FIND_SIZE]; + int32_t buf_pos = 0; + int32_t read_size = sizeof(buf); + int32_t read = 0; + int64_t read_pos = 0; + int64_t start_pos = 0; + int64_t disk_pos = 0; + int32_t i = 0; + uint8_t first = 1; + int32_t err = MZ_OK; + + if (!stream || !find || !position) + return MZ_PARAM_ERROR; + if (find_size < 0 || find_size >= (int32_t)sizeof(buf)) + return MZ_PARAM_ERROR; + + *position = -1; + + start_pos = mz_stream_tell(stream); + + while (read_pos < max_seek) { + if (read_size > (int32_t)(max_seek - read_pos - buf_pos) && + (max_seek - read_pos - buf_pos) < (int64_t)sizeof(buf)) { + read_size = (int32_t)(max_seek - read_pos - buf_pos); + } + + read = mz_stream_read(stream, buf + buf_pos, read_size); + if ((read <= 0) || (read + buf_pos < find_size)) + break; + + for (i = 0; i <= read + buf_pos - find_size; i += 1) { + if (memcmp(&buf[i], find, find_size) != 0) + continue; + + disk_pos = mz_stream_tell(stream); + + /* Seek to position on disk where the data was found */ + err = mz_stream_seek(stream, disk_pos - ((int64_t)read + buf_pos - i), MZ_SEEK_SET); + if (err != MZ_OK) + return MZ_EXIST_ERROR; + + *position = start_pos + read_pos + i; + return MZ_OK; + } + + if (first) { + read -= find_size; + read_size -= find_size; + buf_pos = find_size; + first = 0; + } + + memmove(buf, buf + read, find_size); + read_pos += read; + } + + return MZ_EXIST_ERROR; +} + +int32_t mz_stream_find_reverse(void *stream, const void *find, int32_t find_size, int64_t max_seek, int64_t *position) { + uint8_t buf[MZ_STREAM_FIND_SIZE]; + int32_t buf_pos = 0; + int32_t read_size = MZ_STREAM_FIND_SIZE; + int64_t read_pos = 0; + int32_t read = 0; + int64_t start_pos = 0; + int64_t disk_pos = 0; + uint8_t first = 1; + int32_t i = 0; + int32_t err = MZ_OK; + + if (!stream || !find || !position) + return MZ_PARAM_ERROR; + if (find_size < 0 || find_size >= (int32_t)sizeof(buf)) + return MZ_PARAM_ERROR; + + *position = -1; + + start_pos = mz_stream_tell(stream); + + while (read_pos < max_seek) { + if (read_size > (int32_t)(max_seek - read_pos) && (max_seek - read_pos) < (int64_t)sizeof(buf)) + read_size = (int32_t)(max_seek - read_pos); + + if (mz_stream_seek(stream, start_pos - (read_pos + read_size), MZ_SEEK_SET) != MZ_OK) + break; + read = mz_stream_read(stream, buf, read_size); + if ((read <= 0) || (read + buf_pos < find_size)) + break; + if (read + buf_pos < MZ_STREAM_FIND_SIZE) + memmove(buf + MZ_STREAM_FIND_SIZE - (read + buf_pos), buf, read); + + for (i = find_size; i <= (read + buf_pos); i += 1) { + if (memcmp(&buf[MZ_STREAM_FIND_SIZE - i], find, find_size) != 0) + continue; + + disk_pos = mz_stream_tell(stream); + + /* Seek to position on disk where the data was found */ + err = mz_stream_seek(stream, disk_pos + buf_pos - i, MZ_SEEK_SET); + if (err != MZ_OK) + return MZ_EXIST_ERROR; + + *position = start_pos - (read_pos - buf_pos + i); + return MZ_OK; + } + + if (first) { + read -= find_size; + read_size -= find_size; + buf_pos = find_size; + first = 0; + } + + if (read == 0) + break; + + memmove(buf + read_size, buf, find_size); + read_pos += read; + } + + return MZ_EXIST_ERROR; +} + +int32_t mz_stream_close(void *stream) { + mz_stream *strm = (mz_stream *)stream; + if (!strm || !strm->vtbl || !strm->vtbl->close) + return MZ_PARAM_ERROR; + if (mz_stream_is_open(stream) != MZ_OK) + return MZ_STREAM_ERROR; + return strm->vtbl->close(strm); +} + +int32_t mz_stream_error(void *stream) { + mz_stream *strm = (mz_stream *)stream; + if (!strm || !strm->vtbl || !strm->vtbl->error) + return MZ_PARAM_ERROR; + return strm->vtbl->error(strm); +} + +int32_t mz_stream_set_base(void *stream, void *base) { + mz_stream *strm = (mz_stream *)stream; + strm->base = (mz_stream *)base; + return MZ_OK; +} + +void *mz_stream_get_interface(void *stream) { + mz_stream *strm = (mz_stream *)stream; + if (!strm || !strm->vtbl) + return NULL; + return (void *)strm->vtbl; +} + +int32_t mz_stream_get_prop_int64(void *stream, int32_t prop, int64_t *value) { + mz_stream *strm = (mz_stream *)stream; + if (!strm || !strm->vtbl || !strm->vtbl->get_prop_int64) + return MZ_PARAM_ERROR; + return strm->vtbl->get_prop_int64(strm, prop, value); +} + +int32_t mz_stream_set_prop_int64(void *stream, int32_t prop, int64_t value) { + mz_stream *strm = (mz_stream *)stream; + if (!strm || !strm->vtbl || !strm->vtbl->set_prop_int64) + return MZ_PARAM_ERROR; + return strm->vtbl->set_prop_int64(strm, prop, value); +} + +void *mz_stream_create(mz_stream_vtbl *vtbl) { + if (!vtbl || !vtbl->create) + return NULL; + return vtbl->create(); +} + +void mz_stream_delete(void **stream) { + mz_stream *strm = NULL; + if (!stream) + return; + strm = (mz_stream *)*stream; + if (strm && strm->vtbl && strm->vtbl->destroy) + strm->vtbl->destroy(stream); + *stream = NULL; +} + +/***************************************************************************/ + +typedef struct mz_stream_raw_s { + mz_stream stream; + int64_t total_in; + int64_t total_out; + int64_t max_total_in; +} mz_stream_raw; + +/***************************************************************************/ + +int32_t mz_stream_raw_open(void *stream, const char *path, int32_t mode) { + MZ_UNUSED(stream); + MZ_UNUSED(path); + MZ_UNUSED(mode); + + return MZ_OK; +} + +int32_t mz_stream_raw_is_open(void *stream) { + mz_stream_raw *raw = (mz_stream_raw *)stream; + return mz_stream_is_open(raw->stream.base); +} + +int32_t mz_stream_raw_read(void *stream, void *buf, int32_t size) { + mz_stream_raw *raw = (mz_stream_raw *)stream; + int32_t bytes_to_read = size; + int32_t read = 0; + + if (raw->max_total_in > 0) { + if ((int64_t)bytes_to_read > (raw->max_total_in - raw->total_in)) + bytes_to_read = (int32_t)(raw->max_total_in - raw->total_in); + } + + read = mz_stream_read(raw->stream.base, buf, bytes_to_read); + + if (read > 0) { + raw->total_in += read; + raw->total_out += read; + } + + return read; +} + +int32_t mz_stream_raw_write(void *stream, const void *buf, int32_t size) { + mz_stream_raw *raw = (mz_stream_raw *)stream; + int32_t written = 0; + + written = mz_stream_write(raw->stream.base, buf, size); + + if (written > 0) { + raw->total_out += written; + raw->total_in += written; + } + + return written; +} + +int64_t mz_stream_raw_tell(void *stream) { + mz_stream_raw *raw = (mz_stream_raw *)stream; + return mz_stream_tell(raw->stream.base); +} + +int32_t mz_stream_raw_seek(void *stream, int64_t offset, int32_t origin) { + mz_stream_raw *raw = (mz_stream_raw *)stream; + return mz_stream_seek(raw->stream.base, offset, origin); +} + +int32_t mz_stream_raw_close(void *stream) { + MZ_UNUSED(stream); + return MZ_OK; +} + +int32_t mz_stream_raw_error(void *stream) { + mz_stream_raw *raw = (mz_stream_raw *)stream; + return mz_stream_error(raw->stream.base); +} + +int32_t mz_stream_raw_get_prop_int64(void *stream, int32_t prop, int64_t *value) { + mz_stream_raw *raw = (mz_stream_raw *)stream; + switch (prop) { + case MZ_STREAM_PROP_TOTAL_IN: + *value = raw->total_in; + return MZ_OK; + case MZ_STREAM_PROP_TOTAL_OUT: + *value = raw->total_out; + return MZ_OK; + } + return MZ_EXIST_ERROR; +} + +int32_t mz_stream_raw_set_prop_int64(void *stream, int32_t prop, int64_t value) { + mz_stream_raw *raw = (mz_stream_raw *)stream; + switch (prop) { + case MZ_STREAM_PROP_TOTAL_IN_MAX: + raw->max_total_in = value; + return MZ_OK; + } + return MZ_EXIST_ERROR; +} + +/***************************************************************************/ + +static mz_stream_vtbl mz_stream_raw_vtbl = { + mz_stream_raw_open, mz_stream_raw_is_open, mz_stream_raw_read, mz_stream_raw_write, + mz_stream_raw_tell, mz_stream_raw_seek, mz_stream_raw_close, mz_stream_raw_error, + mz_stream_raw_create, mz_stream_raw_delete, mz_stream_raw_get_prop_int64, mz_stream_raw_set_prop_int64}; + +/***************************************************************************/ + +void *mz_stream_raw_create(void) { + mz_stream_raw *raw = (mz_stream_raw *)calloc(1, sizeof(mz_stream_raw)); + if (raw) + raw->stream.vtbl = &mz_stream_raw_vtbl; + return raw; +} + +void mz_stream_raw_delete(void **stream) { + mz_stream_raw *raw = NULL; + if (!stream) + return; + raw = (mz_stream_raw *)*stream; + free(raw); + *stream = NULL; +} diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_strm.h b/packages/zip/platforms/linux/src/minizip-ng/mz_strm.h new file mode 100644 index 00000000..fcbdda4a --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_strm.h @@ -0,0 +1,135 @@ +/* mz_strm.h -- Stream interface + part of the minizip-ng project + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#ifndef MZ_STREAM_H +#define MZ_STREAM_H + +#ifdef __cplusplus +extern "C" { +#endif + +/***************************************************************************/ + +#define MZ_STREAM_PROP_TOTAL_IN (1) +#define MZ_STREAM_PROP_TOTAL_IN_MAX (2) +#define MZ_STREAM_PROP_TOTAL_OUT (3) +#define MZ_STREAM_PROP_TOTAL_OUT_MAX (4) +#define MZ_STREAM_PROP_HEADER_SIZE (5) +#define MZ_STREAM_PROP_FOOTER_SIZE (6) +#define MZ_STREAM_PROP_DISK_SIZE (7) +#define MZ_STREAM_PROP_DISK_NUMBER (8) +#define MZ_STREAM_PROP_COMPRESS_LEVEL (9) +#define MZ_STREAM_PROP_COMPRESS_METHOD (10) +#define MZ_STREAM_PROP_COMPRESS_WINDOW (11) +#define MZ_STREAM_PROP_COMPRESS_THREADS (12) + +/***************************************************************************/ + +typedef int32_t (*mz_stream_open_cb)(void *stream, const char *path, int32_t mode); +typedef int32_t (*mz_stream_is_open_cb)(void *stream); +typedef int32_t (*mz_stream_read_cb)(void *stream, void *buf, int32_t size); +typedef int32_t (*mz_stream_write_cb)(void *stream, const void *buf, int32_t size); +typedef int64_t (*mz_stream_tell_cb)(void *stream); +typedef int32_t (*mz_stream_seek_cb)(void *stream, int64_t offset, int32_t origin); +typedef int32_t (*mz_stream_close_cb)(void *stream); +typedef int32_t (*mz_stream_error_cb)(void *stream); +typedef void *(*mz_stream_create_cb)(void); +typedef void (*mz_stream_destroy_cb)(void **stream); + +typedef int32_t (*mz_stream_get_prop_int64_cb)(void *stream, int32_t prop, int64_t *value); +typedef int32_t (*mz_stream_set_prop_int64_cb)(void *stream, int32_t prop, int64_t value); + +typedef int32_t (*mz_stream_find_cb)(void *stream, const void *find, int32_t find_size, int64_t max_seek, + int64_t *position); + +/***************************************************************************/ + +typedef struct mz_stream_vtbl_s { + mz_stream_open_cb open; + mz_stream_is_open_cb is_open; + mz_stream_read_cb read; + mz_stream_write_cb write; + mz_stream_tell_cb tell; + mz_stream_seek_cb seek; + mz_stream_close_cb close; + mz_stream_error_cb error; + mz_stream_create_cb create; + mz_stream_destroy_cb destroy; + + mz_stream_get_prop_int64_cb get_prop_int64; + mz_stream_set_prop_int64_cb set_prop_int64; +} mz_stream_vtbl; + +typedef struct mz_stream_s { + mz_stream_vtbl *vtbl; + struct mz_stream_s *base; +} mz_stream; + +/***************************************************************************/ + +int32_t mz_stream_open(void *stream, const char *path, int32_t mode); +int32_t mz_stream_is_open(void *stream); +int32_t mz_stream_read(void *stream, void *buf, int32_t size); +int32_t mz_stream_read_uint8(void *stream, uint8_t *value); +int32_t mz_stream_read_uint16(void *stream, uint16_t *value); +int32_t mz_stream_read_uint32(void *stream, uint32_t *value); +int32_t mz_stream_read_int64(void *stream, int64_t *value); +int32_t mz_stream_read_uint64(void *stream, uint64_t *value); +int32_t mz_stream_write(void *stream, const void *buf, int32_t size); +int32_t mz_stream_write_uint8(void *stream, uint8_t value); +int32_t mz_stream_write_uint16(void *stream, uint16_t value); +int32_t mz_stream_write_uint32(void *stream, uint32_t value); +int32_t mz_stream_write_int64(void *stream, int64_t value); +int32_t mz_stream_write_uint64(void *stream, uint64_t value); +int32_t mz_stream_copy(void *target, void *source, int32_t len); +int32_t mz_stream_copy_to_end(void *target, void *source); +int32_t mz_stream_copy_stream(void *target, mz_stream_write_cb write_cb, void *source, mz_stream_read_cb read_cb, + int32_t len); +int32_t mz_stream_copy_stream_to_end(void *target, mz_stream_write_cb write_cb, void *source, + mz_stream_read_cb read_cb); +int64_t mz_stream_tell(void *stream); +int32_t mz_stream_seek(void *stream, int64_t offset, int32_t origin); +int32_t mz_stream_find(void *stream, const void *find, int32_t find_size, int64_t max_seek, int64_t *position); +int32_t mz_stream_find_reverse(void *stream, const void *find, int32_t find_size, int64_t max_seek, int64_t *position); +int32_t mz_stream_close(void *stream); +int32_t mz_stream_error(void *stream); + +int32_t mz_stream_set_base(void *stream, void *base); +void *mz_stream_get_interface(void *stream); +int32_t mz_stream_get_prop_int64(void *stream, int32_t prop, int64_t *value); +int32_t mz_stream_set_prop_int64(void *stream, int32_t prop, int64_t value); + +void *mz_stream_create(mz_stream_vtbl *vtbl); +void mz_stream_delete(void **stream); + +/***************************************************************************/ + +int32_t mz_stream_raw_open(void *stream, const char *filename, int32_t mode); +int32_t mz_stream_raw_is_open(void *stream); +int32_t mz_stream_raw_read(void *stream, void *buf, int32_t size); +int32_t mz_stream_raw_write(void *stream, const void *buf, int32_t size); +int64_t mz_stream_raw_tell(void *stream); +int32_t mz_stream_raw_seek(void *stream, int64_t offset, int32_t origin); +int32_t mz_stream_raw_close(void *stream); +int32_t mz_stream_raw_error(void *stream); + +int32_t mz_stream_raw_get_prop_int64(void *stream, int32_t prop, int64_t *value); +int32_t mz_stream_raw_set_prop_int64(void *stream, int32_t prop, int64_t value); + +void *mz_stream_raw_create(void); +void mz_stream_raw_delete(void **stream); + +/***************************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_strm_buf.c b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_buf.c new file mode 100644 index 00000000..2ca81970 --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_buf.c @@ -0,0 +1,381 @@ +/* mz_strm_buf.c -- Stream for buffering reads/writes + part of the minizip-ng project + + This version of ioapi is designed to buffer IO. + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#include "mz.h" +#include "mz_strm.h" +#include "mz_strm_buf.h" + +/***************************************************************************/ + +static mz_stream_vtbl mz_stream_buffered_vtbl = {mz_stream_buffered_open, + mz_stream_buffered_is_open, + mz_stream_buffered_read, + mz_stream_buffered_write, + mz_stream_buffered_tell, + mz_stream_buffered_seek, + mz_stream_buffered_close, + mz_stream_buffered_error, + mz_stream_buffered_create, + mz_stream_buffered_delete, + NULL, + NULL}; + +/***************************************************************************/ + +typedef struct mz_stream_buffered_s { + mz_stream stream; + int32_t error; + char readbuf[INT16_MAX]; + int32_t readbuf_len; + int32_t readbuf_pos; + int32_t readbuf_hits; + int32_t readbuf_misses; + char writebuf[INT16_MAX]; + int32_t writebuf_len; + int32_t writebuf_pos; + int32_t writebuf_hits; + int32_t writebuf_misses; + int64_t position; +} mz_stream_buffered; + +/***************************************************************************/ + +#if 0 +# define mz_stream_buffered_print printf +#else +# define mz_stream_buffered_print(fmt, ...) +#endif + +/***************************************************************************/ + +static int32_t mz_stream_buffered_reset(void *stream) { + mz_stream_buffered *buffered = (mz_stream_buffered *)stream; + + buffered->readbuf_len = 0; + buffered->readbuf_pos = 0; + buffered->writebuf_len = 0; + buffered->writebuf_pos = 0; + buffered->position = 0; + + return MZ_OK; +} + +int32_t mz_stream_buffered_open(void *stream, const char *path, int32_t mode) { + mz_stream_buffered *buffered = (mz_stream_buffered *)stream; + mz_stream_buffered_print("Buffered - Open (mode %" PRId32 ")\n", mode); + mz_stream_buffered_reset(buffered); + return mz_stream_open(buffered->stream.base, path, mode); +} + +int32_t mz_stream_buffered_is_open(void *stream) { + mz_stream_buffered *buffered = (mz_stream_buffered *)stream; + return mz_stream_is_open(buffered->stream.base); +} + +static int32_t mz_stream_buffered_flush(void *stream, int32_t *written) { + mz_stream_buffered *buffered = (mz_stream_buffered *)stream; + int32_t total_bytes_written = 0; + int32_t bytes_to_write = buffered->writebuf_len; + int32_t bytes_left_to_write = buffered->writebuf_len; + int32_t bytes_written = 0; + + *written = 0; + + while (bytes_left_to_write > 0) { + bytes_written = mz_stream_write( + buffered->stream.base, buffered->writebuf + (bytes_to_write - bytes_left_to_write), bytes_left_to_write); + + if (bytes_written != bytes_left_to_write) + return MZ_WRITE_ERROR; + + buffered->writebuf_misses += 1; + + mz_stream_buffered_print("Buffered - Write flush (%" PRId32 ":%" PRId32 " len %" PRId32 ")\n", bytes_to_write, + bytes_left_to_write, buffered->writebuf_len); + + total_bytes_written += bytes_written; + bytes_left_to_write -= bytes_written; + buffered->position += bytes_written; + } + + buffered->writebuf_len = 0; + buffered->writebuf_pos = 0; + + *written = total_bytes_written; + return MZ_OK; +} + +int32_t mz_stream_buffered_read(void *stream, void *buf, int32_t size) { + mz_stream_buffered *buffered = (mz_stream_buffered *)stream; + int32_t buf_len = 0; + int32_t bytes_to_read = 0; + int32_t bytes_to_copy = 0; + int32_t bytes_left_to_read = size; + int32_t bytes_read = 0; + int32_t bytes_flushed = 0; + + mz_stream_buffered_print("Buffered - Read (size %" PRId32 " pos %" PRId64 ")\n", size, buffered->position); + + if (buffered->writebuf_len > 0) { + int64_t position = buffered->position + buffered->writebuf_pos; + + mz_stream_buffered_print("Buffered - Switch from write to read, flushing (pos %" PRId64 ")\n", position); + + mz_stream_buffered_flush(stream, &bytes_flushed); + mz_stream_buffered_seek(stream, position, MZ_SEEK_SET); + } + + while (bytes_left_to_read > 0) { + if ((buffered->readbuf_len == 0) || (buffered->readbuf_pos == buffered->readbuf_len)) { + if (buffered->readbuf_len == sizeof(buffered->readbuf)) { + buffered->readbuf_pos = 0; + buffered->readbuf_len = 0; + } + + bytes_to_read = (int32_t)sizeof(buffered->readbuf) - (buffered->readbuf_len - buffered->readbuf_pos); + bytes_read = + mz_stream_read(buffered->stream.base, buffered->readbuf + buffered->readbuf_pos, bytes_to_read); + if (bytes_read < 0) + return bytes_read; + + buffered->readbuf_misses += 1; + buffered->readbuf_len += bytes_read; + buffered->position += bytes_read; + + mz_stream_buffered_print( + "Buffered - Filled (read %" PRId32 "/%" PRId32 " buf %" PRId32 ":%" PRId32 " pos %" PRId64 ")\n", + bytes_read, bytes_to_read, buffered->readbuf_pos, buffered->readbuf_len, buffered->position); + + if (bytes_read == 0) + break; + } + + if ((buffered->readbuf_len - buffered->readbuf_pos) > 0) { + bytes_to_copy = buffered->readbuf_len - buffered->readbuf_pos; + if (bytes_to_copy > bytes_left_to_read) + bytes_to_copy = bytes_left_to_read; + + memcpy((char *)buf + buf_len, buffered->readbuf + buffered->readbuf_pos, bytes_to_copy); + + buf_len += bytes_to_copy; + bytes_left_to_read -= bytes_to_copy; + + buffered->readbuf_hits += 1; + buffered->readbuf_pos += bytes_to_copy; + + mz_stream_buffered_print("Buffered - Emptied (copied %" PRId32 " remaining %" PRId32 " buf %" PRId32 + ":%" PRId32 " pos %" PRId64 ")\n", + bytes_to_copy, bytes_left_to_read, buffered->readbuf_pos, buffered->readbuf_len, + buffered->position); + } + } + + return size - bytes_left_to_read; +} + +int32_t mz_stream_buffered_write(void *stream, const void *buf, int32_t size) { + mz_stream_buffered *buffered = (mz_stream_buffered *)stream; + int32_t bytes_to_write = size; + int32_t bytes_left_to_write = size; + int32_t bytes_to_copy = 0; + int32_t bytes_used = 0; + int32_t bytes_flushed = 0; + int32_t err = MZ_OK; + + mz_stream_buffered_print("Buffered - Write (size %" PRId32 " len %" PRId32 " pos %" PRId64 ")\n", size, + buffered->writebuf_len, buffered->position); + + if (buffered->readbuf_len > 0) { + buffered->position -= buffered->readbuf_len; + buffered->position += buffered->readbuf_pos; + + buffered->readbuf_len = 0; + buffered->readbuf_pos = 0; + + mz_stream_buffered_print("Buffered - Switch from read to write (pos %" PRId64 ")\n", buffered->position); + + err = mz_stream_seek(buffered->stream.base, buffered->position, MZ_SEEK_SET); + if (err != MZ_OK) + return err; + } + + while (bytes_left_to_write > 0) { + bytes_used = buffered->writebuf_len; + if (bytes_used > buffered->writebuf_pos) + bytes_used = buffered->writebuf_pos; + bytes_to_copy = (int32_t)sizeof(buffered->writebuf) - bytes_used; + if (bytes_to_copy > bytes_left_to_write) + bytes_to_copy = bytes_left_to_write; + + if (bytes_to_copy == 0) { + err = mz_stream_buffered_flush(stream, &bytes_flushed); + if (err != MZ_OK) + return err; + if (bytes_flushed == 0) + return 0; + + continue; + } + + memcpy(buffered->writebuf + buffered->writebuf_pos, (const char *)buf + (bytes_to_write - bytes_left_to_write), + bytes_to_copy); + + mz_stream_buffered_print("Buffered - Write copy (remaining %" PRId32 " write %" PRId32 ":%" PRId32 + " len %" PRId32 ")\n", + bytes_to_copy, bytes_to_write, bytes_left_to_write, buffered->writebuf_len); + + bytes_left_to_write -= bytes_to_copy; + + buffered->writebuf_pos += bytes_to_copy; + buffered->writebuf_hits += 1; + if (buffered->writebuf_pos > buffered->writebuf_len) + buffered->writebuf_len += buffered->writebuf_pos - buffered->writebuf_len; + } + + return size - bytes_left_to_write; +} + +int64_t mz_stream_buffered_tell(void *stream) { + mz_stream_buffered *buffered = (mz_stream_buffered *)stream; + int64_t position = mz_stream_tell(buffered->stream.base); + + buffered->position = position; + + mz_stream_buffered_print("Buffered - Tell (pos %" PRId64 " readpos %" PRId32 " writepos %" PRId32 ")\n", + buffered->position, buffered->readbuf_pos, buffered->writebuf_pos); + + if (buffered->readbuf_len > 0) + position -= ((int64_t)buffered->readbuf_len - buffered->readbuf_pos); + if (buffered->writebuf_len > 0) + position += buffered->writebuf_pos; + return position; +} + +int32_t mz_stream_buffered_seek(void *stream, int64_t offset, int32_t origin) { + mz_stream_buffered *buffered = (mz_stream_buffered *)stream; + int32_t bytes_flushed = 0; + int32_t err = MZ_OK; + + mz_stream_buffered_print("Buffered - Seek (origin %" PRId32 " offset %" PRId64 " pos %" PRId64 ")\n", origin, + offset, buffered->position); + + switch (origin) { + case MZ_SEEK_SET: + + if ((buffered->readbuf_len > 0) && (offset < buffered->position) && + (offset >= buffered->position - buffered->readbuf_len)) { + buffered->readbuf_pos = (int32_t)(offset - (buffered->position - buffered->readbuf_len)); + return MZ_OK; + } + if (buffered->writebuf_len > 0) { + if ((offset >= buffered->position) && (offset <= buffered->position + buffered->writebuf_len)) { + buffered->writebuf_pos = (int32_t)(offset - buffered->position); + return MZ_OK; + } + } + + err = mz_stream_buffered_flush(stream, &bytes_flushed); + if (err != MZ_OK) + return err; + + buffered->position = offset; + break; + + case MZ_SEEK_CUR: + + if (buffered->readbuf_len > 0) { + if (offset <= ((int64_t)buffered->readbuf_len - buffered->readbuf_pos)) { + buffered->readbuf_pos += (uint32_t)offset; + return MZ_OK; + } + offset -= ((int64_t)buffered->readbuf_len - buffered->readbuf_pos); + buffered->position += offset; + } + if (buffered->writebuf_len > 0) { + if (offset <= ((int64_t)buffered->writebuf_len - buffered->writebuf_pos)) { + buffered->writebuf_pos += (uint32_t)offset; + return MZ_OK; + } + /* offset -= (buffered->writebuf_len - buffered->writebuf_pos); */ + } + + err = mz_stream_buffered_flush(stream, &bytes_flushed); + if (err != MZ_OK) + return err; + + break; + + case MZ_SEEK_END: + + if (buffered->writebuf_len > 0) { + buffered->writebuf_pos = buffered->writebuf_len; + return MZ_OK; + } + break; + } + + buffered->readbuf_len = 0; + buffered->readbuf_pos = 0; + buffered->writebuf_len = 0; + buffered->writebuf_pos = 0; + + return mz_stream_seek(buffered->stream.base, offset, origin); +} + +int32_t mz_stream_buffered_close(void *stream) { + mz_stream_buffered *buffered = (mz_stream_buffered *)stream; + int32_t bytes_flushed = 0; + + mz_stream_buffered_flush(stream, &bytes_flushed); + mz_stream_buffered_print("Buffered - Close (flushed %" PRId32 ")\n", bytes_flushed); + + if (buffered->readbuf_hits + buffered->readbuf_misses > 0) { + mz_stream_buffered_print( + "Buffered - Read efficiency %.02f%%\n", + (buffered->readbuf_hits / ((float)buffered->readbuf_hits + buffered->readbuf_misses)) * 100); + } + + if (buffered->writebuf_hits + buffered->writebuf_misses > 0) { + mz_stream_buffered_print( + "Buffered - Write efficiency %.02f%%\n", + (buffered->writebuf_hits / ((float)buffered->writebuf_hits + buffered->writebuf_misses)) * 100); + } + + mz_stream_buffered_reset(buffered); + + return mz_stream_close(buffered->stream.base); +} + +int32_t mz_stream_buffered_error(void *stream) { + mz_stream_buffered *buffered = (mz_stream_buffered *)stream; + return mz_stream_error(buffered->stream.base); +} + +void *mz_stream_buffered_create(void) { + mz_stream_buffered *buffered = (mz_stream_buffered *)calloc(1, sizeof(mz_stream_buffered)); + if (buffered) + buffered->stream.vtbl = &mz_stream_buffered_vtbl; + return buffered; +} + +void mz_stream_buffered_delete(void **stream) { + mz_stream_buffered *buffered = NULL; + if (!stream) + return; + buffered = (mz_stream_buffered *)*stream; + free(buffered); + *stream = NULL; +} + +void *mz_stream_buffered_get_interface(void) { + return (void *)&mz_stream_buffered_vtbl; +} diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_strm_buf.h b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_buf.h new file mode 100644 index 00000000..61b382f3 --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_buf.h @@ -0,0 +1,42 @@ +/* mz_strm_buf.h -- Stream for buffering reads/writes + part of the minizip-ng project + + This version of ioapi is designed to buffer IO. + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#ifndef MZ_STREAM_BUFFERED_H +#define MZ_STREAM_BUFFERED_H + +#ifdef __cplusplus +extern "C" { +#endif + +/***************************************************************************/ + +int32_t mz_stream_buffered_open(void *stream, const char *path, int32_t mode); +int32_t mz_stream_buffered_is_open(void *stream); +int32_t mz_stream_buffered_read(void *stream, void *buf, int32_t size); +int32_t mz_stream_buffered_write(void *stream, const void *buf, int32_t size); +int64_t mz_stream_buffered_tell(void *stream); +int32_t mz_stream_buffered_seek(void *stream, int64_t offset, int32_t origin); +int32_t mz_stream_buffered_close(void *stream); +int32_t mz_stream_buffered_error(void *stream); + +void *mz_stream_buffered_create(void); +void mz_stream_buffered_delete(void **stream); + +void *mz_stream_buffered_get_interface(void); + +/***************************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_strm_mem.c b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_mem.c new file mode 100644 index 00000000..7669712b --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_mem.c @@ -0,0 +1,265 @@ +/* mz_strm_mem.c -- Stream for memory access + part of the minizip-ng project + + This interface is designed to access memory rather than files. + We do use a region of memory to put data in to and take it out of. + + Based on Unzip ioapi.c version 0.22, May 19th, 2003 + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + Copyright (C) 2003 Justin Fletcher + Copyright (C) 1998-2003 Gilles Vollant + https://www.winimage.com/zLibDll/minizip.html + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#include "mz.h" +#include "mz_strm.h" +#include "mz_strm_mem.h" + +/***************************************************************************/ + +static mz_stream_vtbl mz_stream_mem_vtbl = {mz_stream_mem_open, + mz_stream_mem_is_open, + mz_stream_mem_read, + mz_stream_mem_write, + mz_stream_mem_tell, + mz_stream_mem_seek, + mz_stream_mem_close, + mz_stream_mem_error, + mz_stream_mem_create, + mz_stream_mem_delete, + NULL, + NULL}; + +/***************************************************************************/ + +typedef struct mz_stream_mem_s { + mz_stream stream; + int32_t mode; + uint8_t *buffer; /* Memory buffer pointer */ + int32_t size; /* Size of the memory buffer */ + int32_t limit; /* Furthest we've written */ + int32_t position; /* Current position in the memory */ + int32_t grow_size; /* Size to grow when full */ +} mz_stream_mem; + +/***************************************************************************/ + +static int32_t mz_stream_mem_set_size(void *stream, int32_t size) { + mz_stream_mem *mem = (mz_stream_mem *)stream; + int32_t new_size = size; + uint8_t *new_buf = NULL; + + new_buf = (uint8_t *)malloc((uint32_t)new_size); + if (!new_buf) + return MZ_BUF_ERROR; + + if (mem->buffer) { + memcpy(new_buf, mem->buffer, mem->size); + free(mem->buffer); + } + + mem->buffer = new_buf; + mem->size = new_size; + return MZ_OK; +} + +int32_t mz_stream_mem_open(void *stream, const char *path, int32_t mode) { + mz_stream_mem *mem = (mz_stream_mem *)stream; + int32_t err = MZ_OK; + + MZ_UNUSED(path); + + mem->mode = mode; + mem->limit = 0; + mem->position = 0; + + if (mem->mode & MZ_OPEN_MODE_CREATE) + err = mz_stream_mem_set_size(stream, mem->grow_size); + else + mem->limit = mem->size; + + return err; +} + +int32_t mz_stream_mem_is_open(void *stream) { + mz_stream_mem *mem = (mz_stream_mem *)stream; + if (!mem->buffer) + return MZ_OPEN_ERROR; + return MZ_OK; +} + +int32_t mz_stream_mem_read(void *stream, void *buf, int32_t size) { + mz_stream_mem *mem = (mz_stream_mem *)stream; + + if (size > mem->size - mem->position) + size = mem->size - mem->position; + if (mem->position + size > mem->limit) + size = mem->limit - mem->position; + + if (size <= 0) + return 0; + + memcpy(buf, mem->buffer + mem->position, size); + mem->position += size; + + return size; +} + +int32_t mz_stream_mem_write(void *stream, const void *buf, int32_t size) { + mz_stream_mem *mem = (mz_stream_mem *)stream; + int32_t new_size = 0; + int32_t err = MZ_OK; + + if (!size) + return size; + + if (size > mem->size - mem->position) { + if (mem->mode & MZ_OPEN_MODE_CREATE) { + new_size = mem->size; + if (size < mem->grow_size) + new_size += mem->grow_size; + else + new_size += size; + + err = mz_stream_mem_set_size(stream, new_size); + if (err != MZ_OK) + return err; + } else { + size = mem->size - mem->position; + } + } + + memcpy(mem->buffer + mem->position, buf, size); + + mem->position += size; + if (mem->position > mem->limit) + mem->limit = mem->position; + + return size; +} + +int64_t mz_stream_mem_tell(void *stream) { + mz_stream_mem *mem = (mz_stream_mem *)stream; + return mem->position; +} + +int32_t mz_stream_mem_seek(void *stream, int64_t offset, int32_t origin) { + mz_stream_mem *mem = (mz_stream_mem *)stream; + int64_t new_pos = 0; + int32_t err = MZ_OK; + + switch (origin) { + case MZ_SEEK_CUR: + new_pos = mem->position + offset; + break; + case MZ_SEEK_END: + new_pos = mem->limit + offset; + break; + case MZ_SEEK_SET: + new_pos = offset; + break; + default: + return MZ_SEEK_ERROR; + } + + // While `malloc` accepts a size_t, mz_stream_mem_s.size only supports an int32_t + if (new_pos < 0 || new_pos > INT32_MAX) { + return MZ_SEEK_ERROR; + } + + if (new_pos > mem->size) { + if ((mem->mode & MZ_OPEN_MODE_CREATE) == 0) + return MZ_SEEK_ERROR; + + err = mz_stream_mem_set_size(stream, (int32_t)new_pos); + if (err != MZ_OK) + return err; + } + + mem->position = (int32_t)new_pos; + return MZ_OK; +} + +int32_t mz_stream_mem_close(void *stream) { + MZ_UNUSED(stream); + + /* We never return errors */ + return MZ_OK; +} + +int32_t mz_stream_mem_error(void *stream) { + MZ_UNUSED(stream); + + /* We never return errors */ + return MZ_OK; +} + +void mz_stream_mem_set_buffer(void *stream, void *buf, int32_t size) { + mz_stream_mem *mem = (mz_stream_mem *)stream; + mem->buffer = (uint8_t *)buf; + mem->size = size; + mem->limit = size; +} + +int32_t mz_stream_mem_get_buffer(void *stream, const void **buf) { + return mz_stream_mem_get_buffer_at(stream, 0, buf); +} + +int32_t mz_stream_mem_get_buffer_at(void *stream, int64_t position, const void **buf) { + mz_stream_mem *mem = (mz_stream_mem *)stream; + if (!buf || position < 0 || !mem->buffer || mem->size < position) + return MZ_SEEK_ERROR; + *buf = mem->buffer + position; + return MZ_OK; +} + +int32_t mz_stream_mem_get_buffer_at_current(void *stream, const void **buf) { + mz_stream_mem *mem = (mz_stream_mem *)stream; + return mz_stream_mem_get_buffer_at(stream, mem->position, buf); +} + +void mz_stream_mem_get_buffer_length(void *stream, int32_t *length) { + mz_stream_mem *mem = (mz_stream_mem *)stream; + *length = mem->limit; +} + +void mz_stream_mem_set_buffer_limit(void *stream, int32_t limit) { + mz_stream_mem *mem = (mz_stream_mem *)stream; + mem->limit = limit; +} + +void mz_stream_mem_set_grow_size(void *stream, int32_t grow_size) { + mz_stream_mem *mem = (mz_stream_mem *)stream; + mem->grow_size = grow_size; +} + +void *mz_stream_mem_create(void) { + mz_stream_mem *mem = (mz_stream_mem *)calloc(1, sizeof(mz_stream_mem)); + if (mem) { + mem->stream.vtbl = &mz_stream_mem_vtbl; + mem->grow_size = 4096; + } + return mem; +} + +void mz_stream_mem_delete(void **stream) { + mz_stream_mem *mem = NULL; + if (!stream) + return; + mem = (mz_stream_mem *)*stream; + if (mem) { + if ((mem->mode & MZ_OPEN_MODE_CREATE) && (mem->buffer)) + free(mem->buffer); + free(mem); + } + *stream = NULL; +} + +void *mz_stream_mem_get_interface(void) { + return (void *)&mz_stream_mem_vtbl; +} diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_strm_mem.h b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_mem.h new file mode 100644 index 00000000..1055634c --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_mem.h @@ -0,0 +1,48 @@ +/* mz_strm_mem.h -- Stream for memory access + part of the minizip-ng project + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#ifndef MZ_STREAM_MEM_H +#define MZ_STREAM_MEM_H + +#ifdef __cplusplus +extern "C" { +#endif + +/***************************************************************************/ + +int32_t mz_stream_mem_open(void *stream, const char *filename, int32_t mode); +int32_t mz_stream_mem_is_open(void *stream); +int32_t mz_stream_mem_read(void *stream, void *buf, int32_t size); +int32_t mz_stream_mem_write(void *stream, const void *buf, int32_t size); +int64_t mz_stream_mem_tell(void *stream); +int32_t mz_stream_mem_seek(void *stream, int64_t offset, int32_t origin); +int32_t mz_stream_mem_close(void *stream); +int32_t mz_stream_mem_error(void *stream); + +void mz_stream_mem_set_buffer(void *stream, void *buf, int32_t size); +int32_t mz_stream_mem_get_buffer(void *stream, const void **buf); +int32_t mz_stream_mem_get_buffer_at(void *stream, int64_t position, const void **buf); +int32_t mz_stream_mem_get_buffer_at_current(void *stream, const void **buf); +void mz_stream_mem_get_buffer_length(void *stream, int32_t *length); +void mz_stream_mem_set_buffer_limit(void *stream, int32_t limit); +void mz_stream_mem_set_grow_size(void *stream, int32_t grow_size); + +void *mz_stream_mem_create(void); +void mz_stream_mem_delete(void **stream); + +void *mz_stream_mem_get_interface(void); + +/***************************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_strm_os.h b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_os.h new file mode 100644 index 00000000..d6846d8c --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_os.h @@ -0,0 +1,40 @@ +/* mz_sstrm_os.h -- Stream for filesystem access + part of the minizip-ng project + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#ifndef MZ_STREAM_OS_H +#define MZ_STREAM_OS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/***************************************************************************/ + +int32_t mz_stream_os_open(void *stream, const char *path, int32_t mode); +int32_t mz_stream_os_is_open(void *stream); +int32_t mz_stream_os_read(void *stream, void *buf, int32_t size); +int32_t mz_stream_os_write(void *stream, const void *buf, int32_t size); +int64_t mz_stream_os_tell(void *stream); +int32_t mz_stream_os_seek(void *stream, int64_t offset, int32_t origin); +int32_t mz_stream_os_close(void *stream); +int32_t mz_stream_os_error(void *stream); + +void *mz_stream_os_create(void); +void mz_stream_os_delete(void **stream); + +void *mz_stream_os_get_interface(void); + +/***************************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_strm_os_posix.c b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_os_posix.c new file mode 100644 index 00000000..1bb5dc40 --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_os_posix.c @@ -0,0 +1,212 @@ +/* mz_strm_posix.c -- Stream for filesystem access for posix/linux + part of the minizip-ng project + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + Modifications for Zip64 support + Copyright (C) 2009-2010 Mathias Svensson + http://result42.com + Copyright (C) 1998-2010 Gilles Vollant + https://www.winimage.com/zLibDll/minizip.html + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#include "mz.h" +#include "mz_config.h" +#include "mz_strm.h" +#include "mz_strm_os.h" + +#include /* fopen, fread, ... */ +#include +#include /* S_IRUSR, S_IWUSR, ... */ +#include /* open, close, ... */ +#include /* O_NOFOLLOW, ... */ + +/***************************************************************************/ + +#define fopen64 fopen +#ifndef MZ_FILE32_API +# if HAVE_FSEEKO +# define ftello64 ftello +# define fseeko64 fseeko +# elif defined(_MSC_VER) && (_MSC_VER >= 1400) +# define ftello64 _ftelli64 +# define fseeko64 _fseeki64 +# endif +#endif +#ifndef ftello64 +# define ftello64 ftell +#endif +#ifndef fseeko64 +# define fseeko64 fseek +#endif + +/***************************************************************************/ + +static mz_stream_vtbl mz_stream_os_vtbl = {mz_stream_os_open, + mz_stream_os_is_open, + mz_stream_os_read, + mz_stream_os_write, + mz_stream_os_tell, + mz_stream_os_seek, + mz_stream_os_close, + mz_stream_os_error, + mz_stream_os_create, + mz_stream_os_delete, + NULL, + NULL}; + +/***************************************************************************/ + +typedef struct mz_stream_posix_s { + mz_stream stream; + int32_t error; + FILE *handle; +} mz_stream_posix; + +/***************************************************************************/ + +int32_t mz_stream_os_open(void *stream, const char *path, int32_t mode) { + mz_stream_posix *posix = (mz_stream_posix *)stream; + const char *mode_fopen = NULL; + int mode_open = 0; + int fd; + + if (!path) + return MZ_PARAM_ERROR; + + if ((mode & MZ_OPEN_MODE_READWRITE) == MZ_OPEN_MODE_READ) { + mode_fopen = "r"; + mode_open = O_RDONLY; + } else if (mode & MZ_OPEN_MODE_APPEND) { + mode_fopen = "r+"; + mode_open = O_RDWR; + } else if (mode & MZ_OPEN_MODE_CREATE) { + mode_fopen = "w"; + mode_open = O_WRONLY | O_CREAT | O_TRUNC; + } else + return MZ_OPEN_ERROR; + + if (mode & MZ_OPEN_MODE_NOFOLLOW) + mode_open |= O_NOFOLLOW; + + fd = open(path, mode_open, S_IRUSR | S_IWUSR | S_IRGRP); + if (fd != -1) { + posix->handle = fdopen(fd, mode_fopen); + if (!posix->handle) + close(fd); + } + if (!posix->handle) { + posix->error = errno; + return MZ_OPEN_ERROR; + } + + if (mode & MZ_OPEN_MODE_APPEND) + return mz_stream_os_seek(stream, 0, MZ_SEEK_END); + + return MZ_OK; +} + +int32_t mz_stream_os_is_open(void *stream) { + mz_stream_posix *posix = (mz_stream_posix *)stream; + if (!posix->handle) + return MZ_OPEN_ERROR; + return MZ_OK; +} + +int32_t mz_stream_os_read(void *stream, void *buf, int32_t size) { + mz_stream_posix *posix = (mz_stream_posix *)stream; + int32_t read = (int32_t)fread(buf, 1, (size_t)size, posix->handle); + if (read < size && ferror(posix->handle)) { + posix->error = errno; + return MZ_READ_ERROR; + } + return read; +} + +int32_t mz_stream_os_write(void *stream, const void *buf, int32_t size) { + mz_stream_posix *posix = (mz_stream_posix *)stream; + int32_t written = (int32_t)fwrite(buf, 1, (size_t)size, posix->handle); + if (written < size && ferror(posix->handle)) { + posix->error = errno; + return MZ_WRITE_ERROR; + } + return written; +} + +int64_t mz_stream_os_tell(void *stream) { + mz_stream_posix *posix = (mz_stream_posix *)stream; + int64_t position = ftello64(posix->handle); + if (position == -1) { + posix->error = errno; + return MZ_TELL_ERROR; + } + return position; +} + +int32_t mz_stream_os_seek(void *stream, int64_t offset, int32_t origin) { + mz_stream_posix *posix = (mz_stream_posix *)stream; + int32_t fseek_origin = 0; + + switch (origin) { + case MZ_SEEK_CUR: + fseek_origin = SEEK_CUR; + break; + case MZ_SEEK_END: + fseek_origin = SEEK_END; + break; + case MZ_SEEK_SET: + fseek_origin = SEEK_SET; + break; + default: + return MZ_SEEK_ERROR; + } + + if (fseeko64(posix->handle, offset, fseek_origin) != 0) { + posix->error = errno; + return MZ_SEEK_ERROR; + } + + return MZ_OK; +} + +int32_t mz_stream_os_close(void *stream) { + mz_stream_posix *posix = (mz_stream_posix *)stream; + int32_t closed = 0; + if (posix->handle) { + closed = fclose(posix->handle); + posix->handle = NULL; + } + if (closed != 0) { + posix->error = errno; + return MZ_CLOSE_ERROR; + } + return MZ_OK; +} + +int32_t mz_stream_os_error(void *stream) { + mz_stream_posix *posix = (mz_stream_posix *)stream; + return posix->error; +} + +void *mz_stream_os_create(void) { + mz_stream_posix *posix = (mz_stream_posix *)calloc(1, sizeof(mz_stream_posix)); + if (posix) + posix->stream.vtbl = &mz_stream_os_vtbl; + return posix; +} + +void mz_stream_os_delete(void **stream) { + mz_stream_posix *posix = NULL; + if (!stream) + return; + posix = (mz_stream_posix *)*stream; + free(posix); + *stream = NULL; +} + +void *mz_stream_os_get_interface(void) { + return (void *)&mz_stream_os_vtbl; +} diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_strm_pkcrypt.c b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_pkcrypt.c new file mode 100644 index 00000000..8098719a --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_pkcrypt.c @@ -0,0 +1,324 @@ +/* mz_strm_pkcrypt.c -- Code for traditional PKWARE encryption + part of the minizip-ng project + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + Copyright (C) 1998-2005 Gilles Vollant + Modifications for Info-ZIP crypting + https://www.winimage.com/zLibDll/minizip.html + Copyright (C) 2003 Terry Thorsen + + This code is a modified version of crypting code in Info-ZIP distribution + + Copyright (C) 1990-2000 Info-ZIP. All rights reserved. + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. + + This encryption code is a direct transcription of the algorithm from + Roger Schlafly, described by Phil Katz in the file appnote.txt. This + file (appnote.txt) is distributed with the PKZIP program (even in the + version without encryption capabilities). +*/ + +#include "mz.h" +#include "mz_crypt.h" +#include "mz_strm.h" +#include "mz_strm_pkcrypt.h" + +/***************************************************************************/ + +static mz_stream_vtbl mz_stream_pkcrypt_vtbl = { + mz_stream_pkcrypt_open, mz_stream_pkcrypt_is_open, mz_stream_pkcrypt_read, + mz_stream_pkcrypt_write, mz_stream_pkcrypt_tell, mz_stream_pkcrypt_seek, + mz_stream_pkcrypt_close, mz_stream_pkcrypt_error, mz_stream_pkcrypt_create, + mz_stream_pkcrypt_delete, mz_stream_pkcrypt_get_prop_int64, mz_stream_pkcrypt_set_prop_int64}; + +/***************************************************************************/ + +typedef struct mz_stream_pkcrypt_s { + mz_stream stream; + int32_t error; + int16_t initialized; + uint8_t buffer[UINT16_MAX]; + int64_t total_in; + int64_t max_total_in; + int64_t total_out; + uint32_t keys[3]; /* keys defining the pseudo-random sequence */ + uint8_t verify1; + uint8_t verify2; + uint16_t verify_version; + const char *password; +} mz_stream_pkcrypt; + +/***************************************************************************/ + +#define mz_stream_pkcrypt_decode(strm, c) \ + (mz_stream_pkcrypt_update_keys(strm, c ^= mz_stream_pkcrypt_decrypt_byte(strm))) + +#define mz_stream_pkcrypt_encode(strm, c, t) \ + (t = mz_stream_pkcrypt_decrypt_byte(strm), mz_stream_pkcrypt_update_keys(strm, (uint8_t)c), (uint8_t)(t ^ (c))) + +/***************************************************************************/ + +static uint8_t mz_stream_pkcrypt_decrypt_byte(void *stream) { + mz_stream_pkcrypt *pkcrypt = (mz_stream_pkcrypt *)stream; + + unsigned temp; /* POTENTIAL BUG: temp*(temp^1) may overflow in an */ + /* unpredictable manner on 16-bit systems; not a problem */ + /* with any known compiler so far, though. */ + + temp = pkcrypt->keys[2] | 2; + return (uint8_t)(((temp * (temp ^ 1)) >> 8) & 0xff); +} + +static uint8_t mz_stream_pkcrypt_update_keys(void *stream, uint8_t c) { + mz_stream_pkcrypt *pkcrypt = (mz_stream_pkcrypt *)stream; + uint8_t buf = c; + + pkcrypt->keys[0] = (uint32_t)~mz_crypt_crc32_update(~pkcrypt->keys[0], &buf, 1); + + pkcrypt->keys[1] += pkcrypt->keys[0] & 0xff; + pkcrypt->keys[1] *= 134775813L; + pkcrypt->keys[1] += 1; + + buf = (uint8_t)(pkcrypt->keys[1] >> 24); + pkcrypt->keys[2] = (uint32_t)~mz_crypt_crc32_update(~pkcrypt->keys[2], &buf, 1); + + return (uint8_t)c; +} + +static void mz_stream_pkcrypt_init_keys(void *stream, const char *password) { + mz_stream_pkcrypt *pkcrypt = (mz_stream_pkcrypt *)stream; + + pkcrypt->keys[0] = 305419896L; + pkcrypt->keys[1] = 591751049L; + pkcrypt->keys[2] = 878082192L; + + while (*password != 0) { + mz_stream_pkcrypt_update_keys(stream, (uint8_t)*password); + password += 1; + } +} + +/***************************************************************************/ + +int32_t mz_stream_pkcrypt_open(void *stream, const char *path, int32_t mode) { + mz_stream_pkcrypt *pkcrypt = (mz_stream_pkcrypt *)stream; + uint16_t t = 0; + int16_t i = 0; + uint8_t verify1 = 0; + uint8_t verify2 = 0; + uint8_t header[MZ_PKCRYPT_HEADER_SIZE]; + const char *password = path; + + pkcrypt->total_in = 0; + pkcrypt->total_out = 0; + pkcrypt->initialized = 0; + + if (mz_stream_is_open(pkcrypt->stream.base) != MZ_OK) + return MZ_OPEN_ERROR; + + if (!password) + password = pkcrypt->password; + if (!password) + return MZ_PARAM_ERROR; + + mz_stream_pkcrypt_init_keys(stream, password); + + if (mode & MZ_OPEN_MODE_WRITE) { + /* First generate RAND_HEAD_LEN - 2 random bytes. */ + mz_crypt_rand(header, MZ_PKCRYPT_HEADER_SIZE - 2); + + /* Encrypt random header (last two bytes is high word of crc) */ + for (i = 0; i < MZ_PKCRYPT_HEADER_SIZE - 2; i++) + header[i] = mz_stream_pkcrypt_encode(stream, header[i], t); + + header[i++] = mz_stream_pkcrypt_encode(stream, pkcrypt->verify1, t); + header[i++] = mz_stream_pkcrypt_encode(stream, pkcrypt->verify2, t); + + if (mz_stream_write(pkcrypt->stream.base, header, sizeof(header)) != sizeof(header)) + return MZ_WRITE_ERROR; + + pkcrypt->total_out += MZ_PKCRYPT_HEADER_SIZE; + } else if (mode & MZ_OPEN_MODE_READ) { + if (mz_stream_read(pkcrypt->stream.base, header, sizeof(header)) != sizeof(header)) + return MZ_READ_ERROR; + + for (i = 0; i < MZ_PKCRYPT_HEADER_SIZE - 2; i++) + header[i] = mz_stream_pkcrypt_decode(stream, header[i]); + + verify1 = mz_stream_pkcrypt_decode(stream, header[i++]); + verify2 = mz_stream_pkcrypt_decode(stream, header[i++]); + + /* PKZIP 2.0 and higher use 1 byte check, older versions used 2 byte check. + See app note section 6.1.6. */ + if (verify2 != pkcrypt->verify2) + return MZ_PASSWORD_ERROR; + if (pkcrypt->verify_version < 2) { + if (verify1 != pkcrypt->verify1) + return MZ_PASSWORD_ERROR; + } + + pkcrypt->total_in += MZ_PKCRYPT_HEADER_SIZE; + } + + pkcrypt->initialized = 1; + return MZ_OK; +} + +int32_t mz_stream_pkcrypt_is_open(void *stream) { + mz_stream_pkcrypt *pkcrypt = (mz_stream_pkcrypt *)stream; + if (!pkcrypt->initialized) + return MZ_OPEN_ERROR; + return MZ_OK; +} + +int32_t mz_stream_pkcrypt_read(void *stream, void *buf, int32_t size) { + mz_stream_pkcrypt *pkcrypt = (mz_stream_pkcrypt *)stream; + uint8_t *buf_ptr = (uint8_t *)buf; + int32_t bytes_to_read = size; + int32_t read = 0; + int32_t i = 0; + + if ((int64_t)bytes_to_read > (pkcrypt->max_total_in - pkcrypt->total_in)) + bytes_to_read = (int32_t)(pkcrypt->max_total_in - pkcrypt->total_in); + + read = mz_stream_read(pkcrypt->stream.base, buf, bytes_to_read); + + for (i = 0; i < read; i++) + buf_ptr[i] = mz_stream_pkcrypt_decode(stream, buf_ptr[i]); + + if (read > 0) + pkcrypt->total_in += read; + + return read; +} + +int32_t mz_stream_pkcrypt_write(void *stream, const void *buf, int32_t size) { + mz_stream_pkcrypt *pkcrypt = (mz_stream_pkcrypt *)stream; + const uint8_t *buf_ptr = (const uint8_t *)buf; + int32_t bytes_to_write = sizeof(pkcrypt->buffer); + int32_t total_written = 0; + int32_t written = 0; + int32_t i = 0; + uint16_t t = 0; + + if (size < 0) + return MZ_PARAM_ERROR; + + do { + if (bytes_to_write > (size - total_written)) + bytes_to_write = (size - total_written); + + for (i = 0; i < bytes_to_write; i += 1) { + pkcrypt->buffer[i] = mz_stream_pkcrypt_encode(stream, *buf_ptr, t); + buf_ptr += 1; + } + + written = mz_stream_write(pkcrypt->stream.base, pkcrypt->buffer, bytes_to_write); + if (written < 0) + return written; + + total_written += written; + } while (total_written < size && written > 0); + + pkcrypt->total_out += total_written; + return total_written; +} + +int64_t mz_stream_pkcrypt_tell(void *stream) { + mz_stream_pkcrypt *pkcrypt = (mz_stream_pkcrypt *)stream; + return mz_stream_tell(pkcrypt->stream.base); +} + +int32_t mz_stream_pkcrypt_seek(void *stream, int64_t offset, int32_t origin) { + mz_stream_pkcrypt *pkcrypt = (mz_stream_pkcrypt *)stream; + return mz_stream_seek(pkcrypt->stream.base, offset, origin); +} + +int32_t mz_stream_pkcrypt_close(void *stream) { + mz_stream_pkcrypt *pkcrypt = (mz_stream_pkcrypt *)stream; + pkcrypt->initialized = 0; + return MZ_OK; +} + +int32_t mz_stream_pkcrypt_error(void *stream) { + mz_stream_pkcrypt *pkcrypt = (mz_stream_pkcrypt *)stream; + return pkcrypt->error; +} + +void mz_stream_pkcrypt_set_password(void *stream, const char *password) { + mz_stream_pkcrypt *pkcrypt = (mz_stream_pkcrypt *)stream; + pkcrypt->password = password; +} + +void mz_stream_pkcrypt_set_verify(void *stream, uint8_t verify1, uint8_t verify2, uint16_t version) { + mz_stream_pkcrypt *pkcrypt = (mz_stream_pkcrypt *)stream; + pkcrypt->verify1 = verify1; + pkcrypt->verify2 = verify2; + pkcrypt->verify_version = version; +} + +void mz_stream_pkcrypt_get_verify(void *stream, uint8_t *verify1, uint8_t *verify2, uint16_t *version) { + mz_stream_pkcrypt *pkcrypt = (mz_stream_pkcrypt *)stream; + *verify1 = pkcrypt->verify1; + *verify2 = pkcrypt->verify2; + *version = pkcrypt->verify_version; +} + +int32_t mz_stream_pkcrypt_get_prop_int64(void *stream, int32_t prop, int64_t *value) { + mz_stream_pkcrypt *pkcrypt = (mz_stream_pkcrypt *)stream; + switch (prop) { + case MZ_STREAM_PROP_TOTAL_IN: + *value = pkcrypt->total_in; + break; + case MZ_STREAM_PROP_TOTAL_OUT: + *value = pkcrypt->total_out; + break; + case MZ_STREAM_PROP_TOTAL_IN_MAX: + *value = pkcrypt->max_total_in; + break; + case MZ_STREAM_PROP_HEADER_SIZE: + *value = MZ_PKCRYPT_HEADER_SIZE; + break; + case MZ_STREAM_PROP_FOOTER_SIZE: + *value = 0; + break; + default: + return MZ_EXIST_ERROR; + } + return MZ_OK; +} + +int32_t mz_stream_pkcrypt_set_prop_int64(void *stream, int32_t prop, int64_t value) { + mz_stream_pkcrypt *pkcrypt = (mz_stream_pkcrypt *)stream; + switch (prop) { + case MZ_STREAM_PROP_TOTAL_IN_MAX: + pkcrypt->max_total_in = value; + break; + default: + return MZ_EXIST_ERROR; + } + return MZ_OK; +} + +void *mz_stream_pkcrypt_create(void) { + mz_stream_pkcrypt *pkcrypt = (mz_stream_pkcrypt *)calloc(1, sizeof(mz_stream_pkcrypt)); + if (pkcrypt) + pkcrypt->stream.vtbl = &mz_stream_pkcrypt_vtbl; + return pkcrypt; +} + +void mz_stream_pkcrypt_delete(void **stream) { + mz_stream_pkcrypt *pkcrypt = NULL; + if (!stream) + return; + pkcrypt = (mz_stream_pkcrypt *)*stream; + free(pkcrypt); + *stream = NULL; +} + +void *mz_stream_pkcrypt_get_interface(void) { + return (void *)&mz_stream_pkcrypt_vtbl; +} diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_strm_pkcrypt.h b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_pkcrypt.h new file mode 100644 index 00000000..5c4f8d0b --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_pkcrypt.h @@ -0,0 +1,46 @@ +/* mz_strm_pkcrypt.h -- Code for traditional PKWARE encryption + part of the minizip-ng project + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#ifndef MZ_STREAM_PKCRYPT_H +#define MZ_STREAM_PKCRYPT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/***************************************************************************/ + +int32_t mz_stream_pkcrypt_open(void *stream, const char *filename, int32_t mode); +int32_t mz_stream_pkcrypt_is_open(void *stream); +int32_t mz_stream_pkcrypt_read(void *stream, void *buf, int32_t size); +int32_t mz_stream_pkcrypt_write(void *stream, const void *buf, int32_t size); +int64_t mz_stream_pkcrypt_tell(void *stream); +int32_t mz_stream_pkcrypt_seek(void *stream, int64_t offset, int32_t origin); +int32_t mz_stream_pkcrypt_close(void *stream); +int32_t mz_stream_pkcrypt_error(void *stream); + +void mz_stream_pkcrypt_set_password(void *stream, const char *password); +void mz_stream_pkcrypt_set_verify(void *stream, uint8_t verify1, uint8_t verify2, uint16_t version); +void mz_stream_pkcrypt_get_verify(void *stream, uint8_t *verify1, uint8_t *verify2, uint16_t *version); +int32_t mz_stream_pkcrypt_get_prop_int64(void *stream, int32_t prop, int64_t *value); +int32_t mz_stream_pkcrypt_set_prop_int64(void *stream, int32_t prop, int64_t value); + +void *mz_stream_pkcrypt_create(void); +void mz_stream_pkcrypt_delete(void **stream); + +void *mz_stream_pkcrypt_get_interface(void); + +/***************************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_strm_wzaes.c b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_wzaes.c new file mode 100644 index 00000000..a0c9facb --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_wzaes.c @@ -0,0 +1,355 @@ +/* mz_strm_wzaes.c -- Stream for WinZip AES encryption + part of the minizip-ng project + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + Copyright (C) 1998-2010 Brian Gladman, Worcester, UK + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#include "mz.h" +#include "mz_crypt.h" +#include "mz_strm.h" +#include "mz_strm_wzaes.h" + +/***************************************************************************/ + +#define MZ_AES_KEY_LENGTH(STRENGTH) (8 * (STRENGTH & 3) + 8) +#define MZ_AES_KEYING_ITERATIONS (1000) +#define MZ_AES_SALT_LENGTH(STRENGTH) (4 * (STRENGTH & 3) + 4) +#define MZ_AES_SALT_LENGTH_MAX (16) +#define MZ_AES_PW_LENGTH_MAX (128) +#define MZ_AES_PW_VERIFY_SIZE (2) +#define MZ_AES_AUTHCODE_SIZE (10) + +/***************************************************************************/ + +static mz_stream_vtbl mz_stream_wzaes_vtbl = { + mz_stream_wzaes_open, mz_stream_wzaes_is_open, mz_stream_wzaes_read, mz_stream_wzaes_write, + mz_stream_wzaes_tell, mz_stream_wzaes_seek, mz_stream_wzaes_close, mz_stream_wzaes_error, + mz_stream_wzaes_create, mz_stream_wzaes_delete, mz_stream_wzaes_get_prop_int64, mz_stream_wzaes_set_prop_int64}; + +/***************************************************************************/ + +typedef struct mz_stream_wzaes_s { + mz_stream stream; + int32_t mode; + int32_t error; + int16_t initialized; + uint8_t buffer[UINT16_MAX]; + int64_t total_in; + int64_t max_total_in; + int64_t total_out; + uint8_t strength; + const char *password; + void *aes; + uint32_t crypt_pos; + uint8_t crypt_block[MZ_AES_BLOCK_SIZE]; + void *hmac; + uint8_t nonce[MZ_AES_BLOCK_SIZE]; +} mz_stream_wzaes; + +/***************************************************************************/ + +int32_t mz_stream_wzaes_open(void *stream, const char *path, int32_t mode) { + mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; + uint16_t salt_length = 0; + uint16_t password_length = 0; + uint16_t key_length = 0; + uint8_t kbuf[2 * MZ_AES_KEY_LENGTH_MAX + MZ_AES_PW_VERIFY_SIZE]; + uint8_t verify[MZ_AES_PW_VERIFY_SIZE]; + uint8_t verify_expected[MZ_AES_PW_VERIFY_SIZE]; + uint8_t salt_value[MZ_AES_SALT_LENGTH_MAX]; + const char *password = path; + + wzaes->total_in = 0; + wzaes->total_out = 0; + wzaes->initialized = 0; + + if (mz_stream_is_open(wzaes->stream.base) != MZ_OK) + return MZ_OPEN_ERROR; + + if (!password) + password = wzaes->password; + if (!password) + return MZ_PARAM_ERROR; + password_length = (uint16_t)strlen(password); + if (password_length > MZ_AES_PW_LENGTH_MAX) + return MZ_PARAM_ERROR; + + if (wzaes->strength < 1 || wzaes->strength > 3) + return MZ_PARAM_ERROR; + + key_length = MZ_AES_KEY_LENGTH(wzaes->strength); + salt_length = MZ_AES_SALT_LENGTH(wzaes->strength); + + if (mode & MZ_OPEN_MODE_WRITE) { + mz_crypt_rand(salt_value, salt_length); + } else if (mode & MZ_OPEN_MODE_READ) { + if (mz_stream_read(wzaes->stream.base, salt_value, salt_length) != salt_length) + return MZ_READ_ERROR; + } + + /* Derive the encryption and authentication keys and the password verifier */ + mz_crypt_pbkdf2((uint8_t *)password, password_length, salt_value, salt_length, MZ_AES_KEYING_ITERATIONS, kbuf, + 2 * key_length + MZ_AES_PW_VERIFY_SIZE); + + /* Initialize the buffer pos */ + wzaes->crypt_pos = MZ_AES_BLOCK_SIZE; + + /* Use fixed zeroed IV/nonce for CTR mode */ + memset(wzaes->nonce, 0, sizeof(wzaes->nonce)); + + /* Initialize for encryption using key 1 */ + mz_crypt_aes_reset(wzaes->aes); + mz_crypt_aes_set_encrypt_key(wzaes->aes, kbuf, key_length, NULL, 0); + + /* Initialize for authentication using key 2 */ + mz_crypt_hmac_reset(wzaes->hmac); + mz_crypt_hmac_set_algorithm(wzaes->hmac, MZ_HASH_SHA1); + mz_crypt_hmac_init(wzaes->hmac, kbuf + key_length, key_length); + + memcpy(verify, kbuf + (2 * key_length), MZ_AES_PW_VERIFY_SIZE); + + if (mode & MZ_OPEN_MODE_WRITE) { + if (mz_stream_write(wzaes->stream.base, salt_value, salt_length) != salt_length) + return MZ_WRITE_ERROR; + + wzaes->total_out += salt_length; + + if (mz_stream_write(wzaes->stream.base, verify, MZ_AES_PW_VERIFY_SIZE) != MZ_AES_PW_VERIFY_SIZE) + return MZ_WRITE_ERROR; + + wzaes->total_out += MZ_AES_PW_VERIFY_SIZE; + } else if (mode & MZ_OPEN_MODE_READ) { + wzaes->total_in += salt_length; + + if (mz_stream_read(wzaes->stream.base, verify_expected, MZ_AES_PW_VERIFY_SIZE) != MZ_AES_PW_VERIFY_SIZE) + return MZ_READ_ERROR; + + wzaes->total_in += MZ_AES_PW_VERIFY_SIZE; + + if (memcmp(verify_expected, verify, MZ_AES_PW_VERIFY_SIZE) != 0) + return MZ_PASSWORD_ERROR; + } + + wzaes->mode = mode; + wzaes->initialized = 1; + + return MZ_OK; +} + +int32_t mz_stream_wzaes_is_open(void *stream) { + mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; + if (!wzaes->initialized) + return MZ_OPEN_ERROR; + return MZ_OK; +} + +static int32_t mz_stream_wzaes_ctr_encrypt(void *stream, uint8_t *buf, int32_t size) { + mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; + uint32_t pos = wzaes->crypt_pos; + uint32_t i = 0; + int32_t err = MZ_OK; + + while (i < (uint32_t)size) { + if (pos == MZ_AES_BLOCK_SIZE) { + uint32_t j = 0; + + /* Increment encryption nonce */ + while (j < 8 && !++wzaes->nonce[j]) + j += 1; + + /* Encrypt the nonce using ECB mode to form next xor buffer */ + memcpy(wzaes->crypt_block, wzaes->nonce, MZ_AES_BLOCK_SIZE); + mz_crypt_aes_encrypt(wzaes->aes, NULL, 0, wzaes->crypt_block, sizeof(wzaes->crypt_block)); + pos = 0; + } + + buf[i++] ^= wzaes->crypt_block[pos++]; + } + + wzaes->crypt_pos = pos; + return err; +} + +int32_t mz_stream_wzaes_read(void *stream, void *buf, int32_t size) { + mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; + int64_t max_total_in = 0; + int32_t bytes_to_read = size; + int32_t read = 0; + + max_total_in = wzaes->max_total_in - MZ_AES_FOOTER_SIZE; + if ((int64_t)bytes_to_read > (max_total_in - wzaes->total_in)) + bytes_to_read = (int32_t)(max_total_in - wzaes->total_in); + + read = mz_stream_read(wzaes->stream.base, buf, bytes_to_read); + + if (read > 0) { + mz_crypt_hmac_update(wzaes->hmac, (uint8_t *)buf, read); + mz_stream_wzaes_ctr_encrypt(stream, (uint8_t *)buf, read); + + wzaes->total_in += read; + } + + return read; +} + +int32_t mz_stream_wzaes_write(void *stream, const void *buf, int32_t size) { + mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; + const uint8_t *buf_ptr = (const uint8_t *)buf; + int32_t bytes_to_write = sizeof(wzaes->buffer); + int32_t total_written = 0; + int32_t written = 0; + + if (size < 0) + return MZ_PARAM_ERROR; + + do { + if (bytes_to_write > (size - total_written)) + bytes_to_write = (size - total_written); + + memcpy(wzaes->buffer, buf_ptr, bytes_to_write); + buf_ptr += bytes_to_write; + + mz_stream_wzaes_ctr_encrypt(stream, (uint8_t *)wzaes->buffer, bytes_to_write); + mz_crypt_hmac_update(wzaes->hmac, wzaes->buffer, bytes_to_write); + + written = mz_stream_write(wzaes->stream.base, wzaes->buffer, bytes_to_write); + if (written < 0) + return written; + + total_written += written; + } while (total_written < size && written > 0); + + wzaes->total_out += total_written; + return total_written; +} + +int64_t mz_stream_wzaes_tell(void *stream) { + mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; + return mz_stream_tell(wzaes->stream.base); +} + +int32_t mz_stream_wzaes_seek(void *stream, int64_t offset, int32_t origin) { + mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; + return mz_stream_seek(wzaes->stream.base, offset, origin); +} + +int32_t mz_stream_wzaes_close(void *stream) { + mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; + uint8_t expected_hash[MZ_AES_AUTHCODE_SIZE]; + uint8_t computed_hash[MZ_HASH_SHA1_SIZE]; + + mz_crypt_hmac_end(wzaes->hmac, computed_hash, sizeof(computed_hash)); + + if (wzaes->mode & MZ_OPEN_MODE_WRITE) { + if (mz_stream_write(wzaes->stream.base, computed_hash, MZ_AES_AUTHCODE_SIZE) != MZ_AES_AUTHCODE_SIZE) + return MZ_WRITE_ERROR; + + wzaes->total_out += MZ_AES_AUTHCODE_SIZE; + } else if (wzaes->mode & MZ_OPEN_MODE_READ) { + if (mz_stream_read(wzaes->stream.base, expected_hash, MZ_AES_AUTHCODE_SIZE) != MZ_AES_AUTHCODE_SIZE) + return MZ_READ_ERROR; + + wzaes->total_in += MZ_AES_AUTHCODE_SIZE; + + /* If entire entry was not read this will fail */ + if (memcmp(computed_hash, expected_hash, MZ_AES_AUTHCODE_SIZE) != 0) + return MZ_CRC_ERROR; + } + + wzaes->initialized = 0; + return MZ_OK; +} + +int32_t mz_stream_wzaes_error(void *stream) { + mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; + return wzaes->error; +} + +void mz_stream_wzaes_set_password(void *stream, const char *password) { + mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; + wzaes->password = password; +} + +void mz_stream_wzaes_set_strength(void *stream, uint8_t strength) { + mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; + wzaes->strength = strength; +} + +int32_t mz_stream_wzaes_get_prop_int64(void *stream, int32_t prop, int64_t *value) { + mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; + switch (prop) { + case MZ_STREAM_PROP_TOTAL_IN: + *value = wzaes->total_in; + break; + case MZ_STREAM_PROP_TOTAL_OUT: + *value = wzaes->total_out; + break; + case MZ_STREAM_PROP_TOTAL_IN_MAX: + *value = wzaes->max_total_in; + break; + case MZ_STREAM_PROP_HEADER_SIZE: + *value = MZ_AES_SALT_LENGTH((int64_t)wzaes->strength) + MZ_AES_PW_VERIFY_SIZE; + break; + case MZ_STREAM_PROP_FOOTER_SIZE: + *value = MZ_AES_AUTHCODE_SIZE; + break; + default: + return MZ_EXIST_ERROR; + } + return MZ_OK; +} + +int32_t mz_stream_wzaes_set_prop_int64(void *stream, int32_t prop, int64_t value) { + mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; + switch (prop) { + case MZ_STREAM_PROP_TOTAL_IN_MAX: + wzaes->max_total_in = value; + break; + default: + return MZ_EXIST_ERROR; + } + return MZ_OK; +} + +void *mz_stream_wzaes_create(void) { + mz_stream_wzaes *wzaes = (mz_stream_wzaes *)calloc(1, sizeof(mz_stream_wzaes)); + if (wzaes) { + wzaes->stream.vtbl = &mz_stream_wzaes_vtbl; + wzaes->strength = MZ_AES_STRENGTH_256; + + wzaes->hmac = mz_crypt_hmac_create(); + if (!wzaes->hmac) { + free(wzaes); + return NULL; + } + wzaes->aes = mz_crypt_aes_create(); + if (!wzaes->aes) { + mz_crypt_hmac_delete(&wzaes->hmac); + free(wzaes); + return NULL; + } + } + return wzaes; +} + +void mz_stream_wzaes_delete(void **stream) { + mz_stream_wzaes *wzaes = NULL; + if (!stream) + return; + wzaes = (mz_stream_wzaes *)*stream; + if (wzaes) { + mz_crypt_aes_delete(&wzaes->aes); + mz_crypt_hmac_delete(&wzaes->hmac); + free(wzaes); + } + *stream = NULL; +} + +void *mz_stream_wzaes_get_interface(void) { + return (void *)&mz_stream_wzaes_vtbl; +} diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_strm_wzaes.h b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_wzaes.h new file mode 100644 index 00000000..ac206553 --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_wzaes.h @@ -0,0 +1,46 @@ +/* mz_strm_wzaes.h -- Stream for WinZIP AES encryption + part of the minizip-ng project + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#ifndef MZ_STREAM_WZAES_SHA1_H +#define MZ_STREAM_WZAES_SHA1_H + +#ifdef __cplusplus +extern "C" { +#endif + +/***************************************************************************/ + +int32_t mz_stream_wzaes_open(void *stream, const char *filename, int32_t mode); +int32_t mz_stream_wzaes_is_open(void *stream); +int32_t mz_stream_wzaes_read(void *stream, void *buf, int32_t size); +int32_t mz_stream_wzaes_write(void *stream, const void *buf, int32_t size); +int64_t mz_stream_wzaes_tell(void *stream); +int32_t mz_stream_wzaes_seek(void *stream, int64_t offset, int32_t origin); +int32_t mz_stream_wzaes_close(void *stream); +int32_t mz_stream_wzaes_error(void *stream); + +void mz_stream_wzaes_set_password(void *stream, const char *password); +void mz_stream_wzaes_set_strength(void *stream, uint8_t strength); + +int32_t mz_stream_wzaes_get_prop_int64(void *stream, int32_t prop, int64_t *value); +int32_t mz_stream_wzaes_set_prop_int64(void *stream, int32_t prop, int64_t value); + +void *mz_stream_wzaes_create(void); +void mz_stream_wzaes_delete(void **stream); + +void *mz_stream_wzaes_get_interface(void); + +/***************************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_strm_zlib.c b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_zlib.c new file mode 100644 index 00000000..bcd5f30a --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_zlib.c @@ -0,0 +1,378 @@ +/* mz_strm_zlib.c -- Stream for zlib inflate/deflate + part of the minizip-ng project + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#include "mz.h" +#include "mz_strm.h" +#include "mz_strm_zlib.h" + +#if !defined(ZLIB_COMPAT) +# include "zlib-ng.h" +#else +# include "zlib.h" +#endif + +/***************************************************************************/ + +#if !defined(ZLIB_COMPAT) +# define ZLIB_PREFIX(x) zng_##x +typedef zng_stream zlib_stream; +#else +# define ZLIB_PREFIX(x) x +typedef z_stream zlib_stream; +#endif + +#if !defined(DEF_MEM_LEVEL) +# if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +# else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +# endif +#endif + +/***************************************************************************/ + +static mz_stream_vtbl mz_stream_zlib_vtbl = { + mz_stream_zlib_open, mz_stream_zlib_is_open, mz_stream_zlib_read, mz_stream_zlib_write, + mz_stream_zlib_tell, mz_stream_zlib_seek, mz_stream_zlib_close, mz_stream_zlib_error, + mz_stream_zlib_create, mz_stream_zlib_delete, mz_stream_zlib_get_prop_int64, mz_stream_zlib_set_prop_int64}; + +/***************************************************************************/ + +typedef struct mz_stream_zlib_s { + mz_stream stream; + zlib_stream zstream; + uint8_t buffer[INT16_MAX]; + int32_t buffer_len; + int64_t total_in; + int64_t total_out; + int64_t max_total_in; + int8_t initialized; + int16_t level; + int32_t window_bits; + int32_t mode; + int32_t error; +} mz_stream_zlib; + +/***************************************************************************/ + +int32_t mz_stream_zlib_open(void *stream, const char *path, int32_t mode) { + mz_stream_zlib *zlib = (mz_stream_zlib *)stream; + + MZ_UNUSED(path); + + zlib->zstream.data_type = Z_BINARY; + zlib->zstream.zalloc = Z_NULL; + zlib->zstream.zfree = Z_NULL; + zlib->zstream.opaque = Z_NULL; + zlib->zstream.total_in = 0; + zlib->zstream.total_out = 0; + + zlib->total_in = 0; + zlib->total_out = 0; + + if (mode & MZ_OPEN_MODE_WRITE) { +#ifdef MZ_ZIP_NO_COMPRESSION + return MZ_SUPPORT_ERROR; +#else + zlib->zstream.next_out = zlib->buffer; + zlib->zstream.avail_out = sizeof(zlib->buffer); + + zlib->error = ZLIB_PREFIX(deflateInit2)(&zlib->zstream, (int8_t)zlib->level, Z_DEFLATED, zlib->window_bits, + DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); +#endif + } else if (mode & MZ_OPEN_MODE_READ) { +#ifdef MZ_ZIP_NO_DECOMPRESSION + return MZ_SUPPORT_ERROR; +#else + zlib->zstream.next_in = zlib->buffer; + zlib->zstream.avail_in = 0; + + zlib->error = ZLIB_PREFIX(inflateInit2)(&zlib->zstream, zlib->window_bits); +#endif + } + + if (zlib->error != Z_OK) + return MZ_OPEN_ERROR; + + zlib->initialized = 1; + zlib->mode = mode; + return MZ_OK; +} + +int32_t mz_stream_zlib_is_open(void *stream) { + mz_stream_zlib *zlib = (mz_stream_zlib *)stream; + if (zlib->initialized != 1) + return MZ_OPEN_ERROR; + return MZ_OK; +} + +int32_t mz_stream_zlib_read(void *stream, void *buf, int32_t size) { +#ifdef MZ_ZIP_NO_DECOMPRESSION + MZ_UNUSED(stream); + MZ_UNUSED(buf); + MZ_UNUSED(size); + return MZ_SUPPORT_ERROR; +#else + mz_stream_zlib *zlib = (mz_stream_zlib *)stream; + uint64_t total_in_before = 0; + uint64_t total_in_after = 0; + uint64_t total_out_before = 0; + uint64_t total_out_after = 0; + uint32_t total_in = 0; + uint32_t total_out = 0; + uint32_t in_bytes = 0; + uint32_t out_bytes = 0; + int32_t bytes_to_read = sizeof(zlib->buffer); + int32_t read = 0; + int32_t err = Z_OK; + + zlib->zstream.next_out = (Bytef *)buf; + zlib->zstream.avail_out = (uInt)size; + + do { + if (zlib->zstream.avail_in == 0) { + if (zlib->max_total_in > 0) { + if ((int64_t)bytes_to_read > (zlib->max_total_in - zlib->total_in)) + bytes_to_read = (int32_t)(zlib->max_total_in - zlib->total_in); + } + + read = mz_stream_read(zlib->stream.base, zlib->buffer, bytes_to_read); + + if (read < 0) + return read; + + zlib->zstream.next_in = zlib->buffer; + zlib->zstream.avail_in = read; + } + + total_in_before = zlib->zstream.avail_in; + total_out_before = zlib->zstream.total_out; + + err = ZLIB_PREFIX(inflate)(&zlib->zstream, Z_SYNC_FLUSH); + if ((err >= Z_OK) && (zlib->zstream.msg)) { + zlib->error = Z_DATA_ERROR; + break; + } + + total_in_after = zlib->zstream.avail_in; + total_out_after = zlib->zstream.total_out; + + in_bytes = (uint32_t)(total_in_before - total_in_after); + out_bytes = (uint32_t)(total_out_after - total_out_before); + + total_in += in_bytes; + total_out += out_bytes; + + zlib->total_in += in_bytes; + zlib->total_out += out_bytes; + + if (err == Z_STREAM_END) + break; + if (err != Z_OK) { + zlib->error = err; + break; + } + } while (zlib->zstream.avail_out > 0); + + MZ_UNUSED(total_in); + + if (zlib->error != 0) { + /* Zlib errors are compatible with MZ */ + return zlib->error; + } + + return total_out; +#endif +} + +#ifndef MZ_ZIP_NO_COMPRESSION +static int32_t mz_stream_zlib_flush(void *stream) { + mz_stream_zlib *zlib = (mz_stream_zlib *)stream; + if (mz_stream_write(zlib->stream.base, zlib->buffer, zlib->buffer_len) != zlib->buffer_len) + return MZ_WRITE_ERROR; + return MZ_OK; +} + +static int32_t mz_stream_zlib_deflate(void *stream, int flush) { + mz_stream_zlib *zlib = (mz_stream_zlib *)stream; + uint64_t total_out_before = 0; + uint64_t total_out_after = 0; + int32_t out_bytes = 0; + int32_t err = Z_OK; + + do { + if (zlib->zstream.avail_out == 0) { + err = mz_stream_zlib_flush(zlib); + if (err != MZ_OK) + return err; + + zlib->zstream.avail_out = sizeof(zlib->buffer); + zlib->zstream.next_out = zlib->buffer; + + zlib->buffer_len = 0; + } + + total_out_before = zlib->zstream.total_out; + err = ZLIB_PREFIX(deflate)(&zlib->zstream, flush); + total_out_after = zlib->zstream.total_out; + + out_bytes = (uint32_t)(total_out_after - total_out_before); + + zlib->buffer_len += out_bytes; + zlib->total_out += out_bytes; + + if (err == Z_STREAM_END) + break; + if (err != Z_OK) { + zlib->error = err; + return MZ_DATA_ERROR; + } + } while ((zlib->zstream.avail_in > 0) || (flush == Z_FINISH && err == Z_OK)); + + return MZ_OK; +} +#endif + +int32_t mz_stream_zlib_write(void *stream, const void *buf, int32_t size) { +#ifdef MZ_ZIP_NO_COMPRESSION + MZ_UNUSED(stream); + MZ_UNUSED(buf); + MZ_UNUSED(size); + return MZ_SUPPORT_ERROR; +#else + mz_stream_zlib *zlib = (mz_stream_zlib *)stream; + int32_t err = MZ_OK; + + zlib->zstream.next_in = (Bytef *)(intptr_t)buf; + zlib->zstream.avail_in = (uInt)size; + + err = mz_stream_zlib_deflate(stream, Z_NO_FLUSH); + if (err != MZ_OK) { + return err; + } + + zlib->total_in += size; + return size; +#endif +} + +int64_t mz_stream_zlib_tell(void *stream) { + MZ_UNUSED(stream); + + return MZ_TELL_ERROR; +} + +int32_t mz_stream_zlib_seek(void *stream, int64_t offset, int32_t origin) { + MZ_UNUSED(stream); + MZ_UNUSED(offset); + MZ_UNUSED(origin); + + return MZ_SEEK_ERROR; +} + +int32_t mz_stream_zlib_close(void *stream) { + mz_stream_zlib *zlib = (mz_stream_zlib *)stream; + + if (zlib->mode & MZ_OPEN_MODE_WRITE) { +#ifdef MZ_ZIP_NO_COMPRESSION + return MZ_SUPPORT_ERROR; +#else + mz_stream_zlib_deflate(stream, Z_FINISH); + mz_stream_zlib_flush(stream); + + ZLIB_PREFIX(deflateEnd)(&zlib->zstream); +#endif + } else if (zlib->mode & MZ_OPEN_MODE_READ) { +#ifdef MZ_ZIP_NO_DECOMPRESSION + return MZ_SUPPORT_ERROR; +#else + ZLIB_PREFIX(inflateEnd)(&zlib->zstream); +#endif + } + + zlib->initialized = 0; + + if (zlib->error != Z_OK) + return MZ_CLOSE_ERROR; + return MZ_OK; +} + +int32_t mz_stream_zlib_error(void *stream) { + mz_stream_zlib *zlib = (mz_stream_zlib *)stream; + return zlib->error; +} + +int32_t mz_stream_zlib_get_prop_int64(void *stream, int32_t prop, int64_t *value) { + mz_stream_zlib *zlib = (mz_stream_zlib *)stream; + switch (prop) { + case MZ_STREAM_PROP_TOTAL_IN: + *value = zlib->total_in; + break; + case MZ_STREAM_PROP_TOTAL_IN_MAX: + *value = zlib->max_total_in; + break; + case MZ_STREAM_PROP_TOTAL_OUT: + *value = zlib->total_out; + break; + case MZ_STREAM_PROP_HEADER_SIZE: + *value = 0; + break; + case MZ_STREAM_PROP_COMPRESS_WINDOW: + *value = zlib->window_bits; + break; + default: + return MZ_EXIST_ERROR; + } + return MZ_OK; +} + +int32_t mz_stream_zlib_set_prop_int64(void *stream, int32_t prop, int64_t value) { + mz_stream_zlib *zlib = (mz_stream_zlib *)stream; + switch (prop) { + case MZ_STREAM_PROP_COMPRESS_LEVEL: + if (value == MZ_COMPRESS_LEVEL_DEFAULT) + zlib->level = Z_DEFAULT_COMPRESSION; + else + zlib->level = (int16_t)value; + break; + case MZ_STREAM_PROP_TOTAL_IN_MAX: + zlib->max_total_in = value; + break; + case MZ_STREAM_PROP_COMPRESS_WINDOW: + zlib->window_bits = (int32_t)value; + break; + default: + return MZ_EXIST_ERROR; + } + return MZ_OK; +} + +void *mz_stream_zlib_create(void) { + mz_stream_zlib *zlib = (mz_stream_zlib *)calloc(1, sizeof(mz_stream_zlib)); + if (zlib) { + zlib->stream.vtbl = &mz_stream_zlib_vtbl; + zlib->level = Z_DEFAULT_COMPRESSION; + zlib->window_bits = -MAX_WBITS; + } + return zlib; +} + +void mz_stream_zlib_delete(void **stream) { + mz_stream_zlib *zlib = NULL; + if (!stream) + return; + zlib = (mz_stream_zlib *)*stream; + free(zlib); + *stream = NULL; +} + +void *mz_stream_zlib_get_interface(void) { + return (void *)&mz_stream_zlib_vtbl; +} diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_strm_zlib.h b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_zlib.h new file mode 100644 index 00000000..89f10f21 --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_strm_zlib.h @@ -0,0 +1,43 @@ +/* mz_strm_zlib.h -- Stream for zlib inflate/deflate + part of the minizip-ng project + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#ifndef MZ_STREAM_ZLIB_H +#define MZ_STREAM_ZLIB_H + +#ifdef __cplusplus +extern "C" { +#endif + +/***************************************************************************/ + +int32_t mz_stream_zlib_open(void *stream, const char *filename, int32_t mode); +int32_t mz_stream_zlib_is_open(void *stream); +int32_t mz_stream_zlib_read(void *stream, void *buf, int32_t size); +int32_t mz_stream_zlib_write(void *stream, const void *buf, int32_t size); +int64_t mz_stream_zlib_tell(void *stream); +int32_t mz_stream_zlib_seek(void *stream, int64_t offset, int32_t origin); +int32_t mz_stream_zlib_close(void *stream); +int32_t mz_stream_zlib_error(void *stream); + +int32_t mz_stream_zlib_get_prop_int64(void *stream, int32_t prop, int64_t *value); +int32_t mz_stream_zlib_set_prop_int64(void *stream, int32_t prop, int64_t value); + +void *mz_stream_zlib_create(void); +void mz_stream_zlib_delete(void **stream); + +void *mz_stream_zlib_get_interface(void); + +/***************************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_zip.c b/packages/zip/platforms/linux/src/minizip-ng/mz_zip.c new file mode 100644 index 00000000..5b35fe19 --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_zip.c @@ -0,0 +1,2840 @@ +/* zip.c -- Zip manipulation + part of the minizip-ng project + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + Copyright (C) 2009-2010 Mathias Svensson + Modifications for Zip64 support + http://result42.com + Copyright (C) 2007-2008 Even Rouault + Modifications of Unzip for Zip64 + Copyright (C) 1998-2010 Gilles Vollant + https://www.winimage.com/zLibDll/minizip.html + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#include "mz.h" +#include "mz_crypt.h" +#include "mz_os.h" +#include "mz_strm.h" +#ifdef HAVE_BZIP2 +# include "mz_strm_bzip.h" +#endif +#ifdef HAVE_LIBCOMP +# include "mz_strm_libcomp.h" +#endif +#ifdef HAVE_LZMA +# include "mz_strm_lzma.h" +#endif +#include "mz_strm_mem.h" +#ifdef HAVE_PKCRYPT +# include "mz_strm_pkcrypt.h" +#endif +#ifdef HAVE_PPMD +# include "mz_strm_ppmd.h" +#endif +#ifdef HAVE_WZAES +# include "mz_strm_wzaes.h" +#endif +#ifdef HAVE_ZLIB +# include "mz_strm_zlib.h" +#endif +#ifdef HAVE_ZSTD +# include "mz_strm_zstd.h" +#endif +#include "mz_zip.h" + +#include /* tolower */ +#include /* snprintf */ + +#if defined(_MSC_VER) || defined(__MINGW32__) +# define localtime_r(t1, t2) (localtime_s(t2, t1) == 0 ? t1 : NULL) +#endif +#if defined(_MSC_VER) && (_MSC_VER < 1900) +# define snprintf _snprintf +#endif + +/***************************************************************************/ + +#define MZ_ZIP_MAGIC_LOCALHEADER (0x04034b50) +#define MZ_ZIP_MAGIC_LOCALHEADERU8 {0x50, 0x4b, 0x03, 0x04} +#define MZ_ZIP_MAGIC_CENTRALHEADER (0x02014b50) +#define MZ_ZIP_MAGIC_CENTRALHEADERU8 {0x50, 0x4b, 0x01, 0x02} +#define MZ_ZIP_MAGIC_ENDHEADER (0x06054b50) +#define MZ_ZIP_MAGIC_ENDHEADERU8 {0x50, 0x4b, 0x05, 0x06} +#define MZ_ZIP_MAGIC_ENDHEADER64 (0x06064b50) +#define MZ_ZIP_MAGIC_ENDLOCHEADER64 (0x07064b50) +#define MZ_ZIP_MAGIC_DATADESCRIPTOR (0x08074b50) +#define MZ_ZIP_MAGIC_DATADESCRIPTORU8 {0x50, 0x4b, 0x07, 0x08} + +#define MZ_ZIP_SIZE_LD_ITEM (30) +#define MZ_ZIP_SIZE_CD_ITEM (46) +#define MZ_ZIP_SIZE_CD_LOCATOR64 (20) +#define MZ_ZIP_SIZE_MAX_DATA_DESCRIPTOR (24) + +#define MZ_ZIP_OFFSET_CRC_SIZES (14) +#define MZ_ZIP_UNCOMPR_SIZE64_CUSHION (2 * 1024 * 1024) + +#ifndef MZ_ZIP_EOCD_MAX_BACK +# define MZ_ZIP_EOCD_MAX_BACK (1 << 20) +#endif + +/***************************************************************************/ + +typedef struct mz_zip_s { + mz_zip_file file_info; + mz_zip_file local_file_info; + + void *stream; /* main stream */ + void *cd_stream; /* pointer to the stream with the cd */ + void *cd_mem_stream; /* memory stream for central directory */ + void *compress_stream; /* compression stream */ + void *crypt_stream; /* encryption stream */ + void *file_info_stream; /* memory stream for storing file info */ + void *local_file_info_stream; /* memory stream for storing local file info */ + + int32_t open_mode; + uint8_t recover; + uint8_t data_descriptor; + + uint32_t disk_number_with_cd; /* number of the disk with the central dir */ + int64_t disk_offset_shift; /* correction for zips that have wrong offset start of cd */ + + int64_t cd_start_pos; /* pos of the first file in the central dir stream */ + int64_t cd_current_pos; /* pos of the current file in the central dir */ + int64_t cd_offset; /* offset of start of central directory */ + int64_t cd_size; /* size of the central directory */ + uint32_t cd_signature; /* signature of central directory */ + + uint8_t entry_scanned; /* entry header information read ok */ + uint8_t entry_opened; /* entry is open for read/write */ + uint8_t entry_raw; /* entry opened with raw mode */ + uint32_t entry_crc32; /* entry crc32 */ + + uint64_t number_entry; + + uint16_t version_madeby; + char *comment; +} mz_zip; + +/***************************************************************************/ + +#if 0 +# define mz_zip_print printf +#else +# define mz_zip_print(fmt, ...) +#endif + +/***************************************************************************/ + +/* Locate the end of central directory */ +static int32_t mz_zip_search_eocd(void *stream, int64_t *central_pos) { + int64_t file_size = 0; + int64_t max_back = MZ_ZIP_EOCD_MAX_BACK; + uint8_t find[4] = MZ_ZIP_MAGIC_ENDHEADERU8; + int32_t err = MZ_OK; + + err = mz_stream_seek(stream, 0, MZ_SEEK_END); + if (err != MZ_OK) + return err; + + file_size = mz_stream_tell(stream); + + if (max_back <= 0 || max_back > file_size) + max_back = file_size; + + return mz_stream_find_reverse(stream, (const void *)find, sizeof(find), max_back, central_pos); +} + +/* Locate the end of central directory 64 of a zip file */ +static int32_t mz_zip_search_zip64_eocd(void *stream, const int64_t end_central_offset, int64_t *central_pos) { + int64_t offset = 0; + uint32_t value32 = 0; + int32_t err = MZ_OK; + + *central_pos = 0; + + /* Zip64 end of central directory locator */ + err = mz_stream_seek(stream, end_central_offset - MZ_ZIP_SIZE_CD_LOCATOR64, MZ_SEEK_SET); + /* Read locator signature */ + if (err == MZ_OK) { + err = mz_stream_read_uint32(stream, &value32); + if (value32 != MZ_ZIP_MAGIC_ENDLOCHEADER64) + err = MZ_FORMAT_ERROR; + } + /* Number of the disk with the start of the zip64 end of central directory */ + if (err == MZ_OK) + err = mz_stream_read_uint32(stream, &value32); + /* Relative offset of the zip64 end of central directory record8 */ + if (err == MZ_OK) + err = mz_stream_read_uint64(stream, (uint64_t *)&offset); + /* Total number of disks */ + if (err == MZ_OK) + err = mz_stream_read_uint32(stream, &value32); + /* Goto end of central directory record */ + if (err == MZ_OK) + err = mz_stream_seek(stream, (int64_t)offset, MZ_SEEK_SET); + /* The signature */ + if (err == MZ_OK) { + err = mz_stream_read_uint32(stream, &value32); + if (value32 != MZ_ZIP_MAGIC_ENDHEADER64) + err = MZ_FORMAT_ERROR; + } + + if (err == MZ_OK) + *central_pos = offset; + + return err; +} + +#ifdef HAVE_PKCRYPT +/* Get PKWARE traditional encryption verifier */ +static uint16_t mz_zip_get_pk_verify(uint32_t dos_date, uint64_t crc, uint16_t flag) { + /* Info-ZIP modification to ZipCrypto format: if bit 3 of the general + * purpose bit flag is set, it uses high byte of 16-bit File Time. */ + if (flag & MZ_ZIP_FLAG_DATA_DESCRIPTOR) + return ((dos_date >> 16) & 0xff) << 8 | ((dos_date >> 8) & 0xff); + return ((crc >> 16) & 0xff) << 8 | ((crc >> 24) & 0xff); +} +#endif + +/* Get info about the current file in the zip file */ +static int32_t mz_zip_entry_read_header(void *stream, uint8_t local, mz_zip_file *file_info, void *file_extra_stream) { + uint64_t ntfs_time = 0; + uint32_t reserved = 0; + uint32_t magic = 0; + uint32_t dos_date = 0; + uint32_t field_pos = 0; + uint16_t field_type = 0; + uint16_t field_length = 0; + uint32_t field_length_read = 0; + uint16_t ntfs_attrib_id = 0; + uint16_t ntfs_attrib_size = 0; + uint16_t linkname_size; + uint16_t value16 = 0; + uint32_t value32 = 0; + int64_t extrafield_pos = 0; + int64_t comment_pos = 0; + int64_t linkname_pos = 0; + int64_t saved_pos = 0; + int32_t err = MZ_OK; + char *linkname = NULL; + + memset(file_info, 0, sizeof(mz_zip_file)); + + /* Check the magic */ + err = mz_stream_read_uint32(stream, &magic); + if (err == MZ_END_OF_STREAM) + err = MZ_END_OF_LIST; + else if (magic == MZ_ZIP_MAGIC_ENDHEADER || magic == MZ_ZIP_MAGIC_ENDHEADER64) + err = MZ_END_OF_LIST; + else if ((local) && (magic != MZ_ZIP_MAGIC_LOCALHEADER)) + err = MZ_FORMAT_ERROR; + else if ((!local) && (magic != MZ_ZIP_MAGIC_CENTRALHEADER)) + err = MZ_FORMAT_ERROR; + + /* Read header fields */ + if (err == MZ_OK) { + if (!local) + err = mz_stream_read_uint16(stream, &file_info->version_madeby); + if (err == MZ_OK) + err = mz_stream_read_uint16(stream, &file_info->version_needed); + if (err == MZ_OK) + err = mz_stream_read_uint16(stream, &file_info->flag); + if (err == MZ_OK) + err = mz_stream_read_uint16(stream, &file_info->compression_method); + if (err == MZ_OK) { + err = mz_stream_read_uint32(stream, &dos_date); + file_info->modified_date = mz_zip_dosdate_to_time_t(dos_date); + } + if (err == MZ_OK) + err = mz_stream_read_uint32(stream, &file_info->crc); +#ifdef HAVE_PKCRYPT + if (err == MZ_OK && file_info->flag & MZ_ZIP_FLAG_ENCRYPTED) { + /* Use dos_date from header instead of derived from time in zip extensions */ + file_info->pk_verify = mz_zip_get_pk_verify(dos_date, file_info->crc, file_info->flag); + } +#endif + if (err == MZ_OK) { + err = mz_stream_read_uint32(stream, &value32); + file_info->compressed_size = value32; + } + if (err == MZ_OK) { + err = mz_stream_read_uint32(stream, &value32); + file_info->uncompressed_size = value32; + } + if (err == MZ_OK) + err = mz_stream_read_uint16(stream, &file_info->filename_size); + if (err == MZ_OK) + err = mz_stream_read_uint16(stream, &file_info->extrafield_size); + if (!local) { + if (err == MZ_OK) + err = mz_stream_read_uint16(stream, &file_info->comment_size); + if (err == MZ_OK) { + err = mz_stream_read_uint16(stream, &value16); + file_info->disk_number = value16; + } + if (err == MZ_OK) + err = mz_stream_read_uint16(stream, &file_info->internal_fa); + if (err == MZ_OK) + err = mz_stream_read_uint32(stream, &file_info->external_fa); + if (err == MZ_OK) { + err = mz_stream_read_uint32(stream, &value32); + file_info->disk_offset = value32; + } + } + } + + if (err == MZ_OK) + err = mz_stream_seek(file_extra_stream, 0, MZ_SEEK_SET); + + /* Copy variable length data to memory stream for later retrieval */ + if ((err == MZ_OK) && (file_info->filename_size > 0)) + err = mz_stream_copy(file_extra_stream, stream, file_info->filename_size); + mz_stream_write_uint8(file_extra_stream, 0); + extrafield_pos = mz_stream_tell(file_extra_stream); + + if ((err == MZ_OK) && (file_info->extrafield_size > 0)) + err = mz_stream_copy(file_extra_stream, stream, file_info->extrafield_size); + mz_stream_write_uint8(file_extra_stream, 0); + + comment_pos = mz_stream_tell(file_extra_stream); + if ((err == MZ_OK) && (file_info->comment_size > 0)) + err = mz_stream_copy(file_extra_stream, stream, file_info->comment_size); + mz_stream_write_uint8(file_extra_stream, 0); + + linkname_pos = mz_stream_tell(file_extra_stream); + /* Overwrite if we encounter UNIX1 extra block */ + mz_stream_write_uint8(file_extra_stream, 0); + + if ((err == MZ_OK) && (file_info->extrafield_size > 0)) { + /* Seek to and parse the extra field */ + err = mz_stream_seek(file_extra_stream, extrafield_pos, MZ_SEEK_SET); + + while ((err == MZ_OK) && (field_pos + 4 <= file_info->extrafield_size)) { + err = mz_zip_extrafield_read(file_extra_stream, &field_type, &field_length); + if (err != MZ_OK) + break; + field_pos += 4; + + /* Don't allow field length to exceed size of remaining extrafield */ + if (field_length > (file_info->extrafield_size - field_pos)) + field_length = (uint16_t)(file_info->extrafield_size - field_pos); + + /* Read ZIP64 extra field */ + if ((field_type == MZ_ZIP_EXTENSION_ZIP64) && (field_length >= 8)) { + if ((err == MZ_OK) && (file_info->uncompressed_size == UINT32_MAX)) { + err = mz_stream_read_int64(file_extra_stream, &file_info->uncompressed_size); + if (file_info->uncompressed_size < 0) + err = MZ_FORMAT_ERROR; + } + if ((err == MZ_OK) && (file_info->compressed_size == UINT32_MAX)) { + err = mz_stream_read_int64(file_extra_stream, &file_info->compressed_size); + if (file_info->compressed_size < 0) + err = MZ_FORMAT_ERROR; + } + if ((err == MZ_OK) && (file_info->disk_offset == UINT32_MAX)) { + err = mz_stream_read_int64(file_extra_stream, &file_info->disk_offset); + if (file_info->disk_offset < 0) + err = MZ_FORMAT_ERROR; + } + if ((err == MZ_OK) && (file_info->disk_number == UINT16_MAX)) + err = mz_stream_read_uint32(file_extra_stream, &file_info->disk_number); + } + /* Read NTFS extra field */ + else if ((field_type == MZ_ZIP_EXTENSION_NTFS) && (field_length > 4)) { + if (err == MZ_OK) + err = mz_stream_read_uint32(file_extra_stream, &reserved); + field_length_read = 4; + + while ((err == MZ_OK) && (field_length_read + 4 <= field_length)) { + err = mz_stream_read_uint16(file_extra_stream, &ntfs_attrib_id); + if (err == MZ_OK) + err = mz_stream_read_uint16(file_extra_stream, &ntfs_attrib_size); + field_length_read += 4; + + if ((err == MZ_OK) && (ntfs_attrib_id == 0x01) && (ntfs_attrib_size == 24)) { + err = mz_stream_read_uint64(file_extra_stream, &ntfs_time); + mz_zip_ntfs_to_unix_time(ntfs_time, &file_info->modified_date); + + if (err == MZ_OK) { + err = mz_stream_read_uint64(file_extra_stream, &ntfs_time); + mz_zip_ntfs_to_unix_time(ntfs_time, &file_info->accessed_date); + } + if (err == MZ_OK) { + err = mz_stream_read_uint64(file_extra_stream, &ntfs_time); + mz_zip_ntfs_to_unix_time(ntfs_time, &file_info->creation_date); + } + } else if ((err == MZ_OK) && (field_length_read + ntfs_attrib_size <= field_length)) { + err = mz_stream_seek(file_extra_stream, ntfs_attrib_size, MZ_SEEK_CUR); + } + + field_length_read += ntfs_attrib_size; + } + } + /* Read UNIX1 extra field */ + else if ((field_type == MZ_ZIP_EXTENSION_UNIX1) && (field_length >= 12)) { + if (err == MZ_OK) { + err = mz_stream_read_uint32(file_extra_stream, &value32); + if (err == MZ_OK && file_info->accessed_date == 0) + file_info->accessed_date = value32; + } + if (err == MZ_OK) { + err = mz_stream_read_uint32(file_extra_stream, &value32); + if (err == MZ_OK && file_info->modified_date == 0) + file_info->modified_date = value32; + } + if (err == MZ_OK) + err = mz_stream_read_uint16(file_extra_stream, &value16); /* User id */ + if (err == MZ_OK) + err = mz_stream_read_uint16(file_extra_stream, &value16); /* Group id */ + + /* Copy linkname to end of file extra stream so we can return null + terminated string */ + linkname_size = field_length - 12; + if ((err == MZ_OK) && (linkname_size > 0)) { + linkname = (char *)malloc(linkname_size); + if (linkname) { + if (mz_stream_read(file_extra_stream, linkname, linkname_size) != linkname_size) + err = MZ_READ_ERROR; + if (err == MZ_OK) { + saved_pos = mz_stream_tell(file_extra_stream); + + mz_stream_seek(file_extra_stream, linkname_pos, MZ_SEEK_SET); + mz_stream_write(file_extra_stream, linkname, linkname_size); + mz_stream_write_uint8(file_extra_stream, 0); + + mz_stream_seek(file_extra_stream, saved_pos, MZ_SEEK_SET); + } + free(linkname); + } + } + } +#ifdef HAVE_WZAES + /* Read AES extra field */ + else if ((field_type == MZ_ZIP_EXTENSION_AES) && (field_length == 7)) { + uint8_t value8 = 0; + /* Verify version info */ + err = mz_stream_read_uint16(file_extra_stream, &value16); + /* Support AE-1 and AE-2 */ + if (value16 != 1 && value16 != 2) + err = MZ_FORMAT_ERROR; + file_info->aes_version = value16; + if (err == MZ_OK) + err = mz_stream_read_uint8(file_extra_stream, &value8); + if ((char)value8 != 'A') + err = MZ_FORMAT_ERROR; + if (err == MZ_OK) + err = mz_stream_read_uint8(file_extra_stream, &value8); + if ((char)value8 != 'E') + err = MZ_FORMAT_ERROR; + /* Get AES encryption strength and actual compression method */ + if (err == MZ_OK) { + err = mz_stream_read_uint8(file_extra_stream, &value8); + file_info->aes_strength = value8; + } + if (err == MZ_OK) { + err = mz_stream_read_uint16(file_extra_stream, &value16); + file_info->compression_method = value16; + } + } +#endif + else if (field_length > 0) { + err = mz_stream_seek(file_extra_stream, field_length, MZ_SEEK_CUR); + } + + field_pos += field_length; + } + } + + /* Get pointers to variable length data */ + mz_stream_mem_get_buffer(file_extra_stream, (const void **)&file_info->filename); + mz_stream_mem_get_buffer_at(file_extra_stream, extrafield_pos, (const void **)&file_info->extrafield); + mz_stream_mem_get_buffer_at(file_extra_stream, comment_pos, (const void **)&file_info->comment); + mz_stream_mem_get_buffer_at(file_extra_stream, linkname_pos, (const void **)&file_info->linkname); + + /* Set to empty string just in-case */ + if (!file_info->filename) + file_info->filename = ""; + if (!file_info->extrafield) + file_info->extrafield_size = 0; + if (!file_info->comment) + file_info->comment = ""; + if (!file_info->linkname) + file_info->linkname = ""; + + if (err == MZ_OK) { + mz_zip_print("Zip - Entry - Read header - %s (local %" PRId8 ")\n", file_info->filename, local); + mz_zip_print("Zip - Entry - Read header compress (ucs %" PRId64 " cs %" PRId64 " crc 0x%08" PRIx32 ")\n", + file_info->uncompressed_size, file_info->compressed_size, file_info->crc); + if (!local) { + mz_zip_print("Zip - Entry - Read header disk (disk %" PRIu32 " offset %" PRId64 ")\n", + file_info->disk_number, file_info->disk_offset); + } + mz_zip_print("Zip - Entry - Read header variable (fnl %" PRId32 " efs %" PRId32 " cms %" PRId32 ")\n", + file_info->filename_size, file_info->extrafield_size, file_info->comment_size); + } + + return err; +} + +static int32_t mz_zip_entry_read_descriptor(void *stream, uint8_t zip64, uint32_t *crc32, int64_t *compressed_size, + int64_t *uncompressed_size) { + uint32_t value32 = 0; + int64_t value64 = 0; + int32_t err = MZ_OK; + + err = mz_stream_read_uint32(stream, &value32); + if (value32 != MZ_ZIP_MAGIC_DATADESCRIPTOR) + err = MZ_FORMAT_ERROR; + if (err == MZ_OK) + err = mz_stream_read_uint32(stream, &value32); + if (err == MZ_OK && crc32) + *crc32 = value32; + if (err == MZ_OK) { + /* If zip 64 extension is enabled then read as 8 byte */ + if (!zip64) { + err = mz_stream_read_uint32(stream, &value32); + value64 = value32; + } else { + err = mz_stream_read_int64(stream, &value64); + if (value64 < 0) + err = MZ_FORMAT_ERROR; + } + if (err == MZ_OK && compressed_size) + *compressed_size = value64; + } + if (err == MZ_OK) { + if (!zip64) { + err = mz_stream_read_uint32(stream, &value32); + value64 = value32; + } else { + err = mz_stream_read_int64(stream, &value64); + if (value64 < 0) + err = MZ_FORMAT_ERROR; + } + if (err == MZ_OK && uncompressed_size) + *uncompressed_size = value64; + } + + return err; +} + +static int32_t mz_zip_entry_write_crc_sizes(void *stream, uint8_t zip64, uint8_t mask, mz_zip_file *file_info) { + int32_t err = MZ_OK; + + if (mask) + err = mz_stream_write_uint32(stream, 0); + else + err = mz_stream_write_uint32(stream, file_info->crc); /* crc */ + + /* For backwards-compatibility with older zip applications we set all sizes to UINT32_MAX + * when zip64 is needed, instead of only setting sizes larger than UINT32_MAX. */ + + if (err == MZ_OK) { + if (zip64) /* compr size */ + err = mz_stream_write_uint32(stream, UINT32_MAX); + else + err = mz_stream_write_uint32(stream, (uint32_t)file_info->compressed_size); + } + if (err == MZ_OK) { + if (mask) /* uncompr size */ + err = mz_stream_write_uint32(stream, 0); + else if (zip64) + err = mz_stream_write_uint32(stream, UINT32_MAX); + else + err = mz_stream_write_uint32(stream, (uint32_t)file_info->uncompressed_size); + } + return err; +} + +static int32_t mz_zip_entry_needs_zip64(mz_zip_file *file_info, uint8_t local, uint8_t *zip64) { + uint32_t max_uncompressed_size = UINT32_MAX; + uint8_t needs_zip64 = 0; + + if (!zip64) + return MZ_PARAM_ERROR; + + *zip64 = 0; + + if (local) { + /* At local header we might not know yet whether compressed size will overflow unsigned + 32-bit integer which might happen for high entropy data so we give it some cushion */ + + max_uncompressed_size -= MZ_ZIP_UNCOMPR_SIZE64_CUSHION; + } + + needs_zip64 = (file_info->uncompressed_size >= max_uncompressed_size) || (file_info->compressed_size >= UINT32_MAX); + + if (!local) { + /* Disk offset and number only used in central directory header */ + needs_zip64 |= (file_info->disk_offset >= UINT32_MAX) || (file_info->disk_number >= UINT16_MAX); + } + + if (file_info->zip64 == MZ_ZIP64_AUTO) { + /* If uncompressed size is unknown, assume zip64 for 64-bit data descriptors */ + if (local && file_info->uncompressed_size == 0) { + /* Don't use zip64 for local header directory entries */ + if (mz_zip_attrib_is_dir(file_info->external_fa, file_info->version_madeby) != MZ_OK) { + *zip64 = 1; + } + } + *zip64 |= needs_zip64; + } else if (file_info->zip64 == MZ_ZIP64_FORCE) { + *zip64 = 1; + } else if (file_info->zip64 == MZ_ZIP64_DISABLE) { + /* Zip64 extension is required to zip file */ + if (needs_zip64) + return MZ_PARAM_ERROR; + } + + return MZ_OK; +} + +static int32_t mz_zip_entry_write_header(void *stream, uint8_t local, mz_zip_file *file_info) { + uint64_t ntfs_time = 0; + uint32_t reserved = 0; + uint32_t dos_date = 0; + uint16_t extrafield_size = 0; + uint16_t field_type = 0; + uint16_t field_length = 0; + uint16_t field_length_zip64 = 0; + uint16_t field_length_ntfs = 0; + uint16_t field_length_aes = 0; + uint16_t field_length_unix1 = 0; + uint16_t filename_size = 0; + uint16_t filename_length = 0; + uint16_t linkname_size = 0; + uint16_t version_needed = 0; + int32_t comment_size = 0; + int32_t err = MZ_OK; + int32_t err_mem = MZ_OK; + uint8_t zip64 = 0; + uint8_t skip_aes = 0; + uint8_t mask = 0; + uint8_t write_end_slash = 0; + const char *filename = NULL; + char masked_name[64]; + void *file_extra_stream = NULL; + + if (!file_info) + return MZ_PARAM_ERROR; + + if ((local) && (file_info->flag & MZ_ZIP_FLAG_MASK_LOCAL_INFO)) + mask = 1; + + /* Determine if zip64 extra field is necessary */ + err = mz_zip_entry_needs_zip64(file_info, local, &zip64); + if (err != MZ_OK) + return err; + + /* Start calculating extra field sizes */ + if (zip64) { + /* Both compressed and uncompressed sizes must be included (at least in local header) */ + field_length_zip64 = 8 + 8; + if ((!local) && (file_info->disk_offset >= UINT32_MAX)) + field_length_zip64 += 8; + + extrafield_size += 4; + extrafield_size += field_length_zip64; + } + + /* Calculate extra field size and check for duplicates */ + if (file_info->extrafield_size > 0) { + file_extra_stream = mz_stream_mem_create(); + if (!file_extra_stream) + return MZ_MEM_ERROR; + mz_stream_mem_set_buffer(file_extra_stream, (void *)file_info->extrafield, file_info->extrafield_size); + + do { + err_mem = mz_stream_read_uint16(file_extra_stream, &field_type); + if (err_mem == MZ_OK) + err_mem = mz_stream_read_uint16(file_extra_stream, &field_length); + if (err_mem != MZ_OK) + break; + + /* Prefer incoming aes extensions over ours */ + if (field_type == MZ_ZIP_EXTENSION_AES) + skip_aes = 1; + + /* Prefer our zip64, ntfs, unix1 extension over incoming */ + if (field_type != MZ_ZIP_EXTENSION_ZIP64 && field_type != MZ_ZIP_EXTENSION_NTFS && + field_type != MZ_ZIP_EXTENSION_UNIX1) + extrafield_size += 4 + field_length; + + if (err_mem == MZ_OK) + err_mem = mz_stream_seek(file_extra_stream, field_length, MZ_SEEK_CUR); + } while (err_mem == MZ_OK); + } + +#ifdef HAVE_WZAES + if (!skip_aes) { + if ((file_info->flag & MZ_ZIP_FLAG_ENCRYPTED) && (file_info->aes_version)) { + field_length_aes = 1 + 1 + 1 + 2 + 2; + extrafield_size += 4 + field_length_aes; + } + } +#else + MZ_UNUSED(field_length_aes); + MZ_UNUSED(skip_aes); +#endif + /* NTFS timestamps */ + if ((file_info->modified_date != 0) && (file_info->accessed_date != 0) && (file_info->creation_date != 0) && + (!mask)) { + field_length_ntfs = 8 + 8 + 8 + 4 + 2 + 2; + extrafield_size += 4 + field_length_ntfs; + } + + /* Unix1 symbolic links */ + if (file_info->linkname && *file_info->linkname != 0) { + linkname_size = (uint16_t)strlen(file_info->linkname); + field_length_unix1 = 12 + linkname_size; + extrafield_size += 4 + field_length_unix1; + } + + if (local) + err = mz_stream_write_uint32(stream, MZ_ZIP_MAGIC_LOCALHEADER); + else { + err = mz_stream_write_uint32(stream, MZ_ZIP_MAGIC_CENTRALHEADER); + if (err == MZ_OK) + err = mz_stream_write_uint16(stream, file_info->version_madeby); + } + + /* Calculate version needed to extract */ + if (err == MZ_OK) { + version_needed = file_info->version_needed; + if (version_needed == 0) { + version_needed = 20; + if (zip64) + version_needed = 45; +#ifdef HAVE_BZIP2 + if (file_info->compression_method == MZ_COMPRESS_METHOD_BZIP2) + version_needed = 46; +#endif +#ifdef HAVE_WZAES + if ((file_info->flag & MZ_ZIP_FLAG_ENCRYPTED) && (file_info->aes_version)) + version_needed = 51; +#endif +#if defined(HAVE_LZMA) || defined(HAVE_LIBCOMP) || defined(HAVE_PPMD) + if ((file_info->compression_method == MZ_COMPRESS_METHOD_LZMA) || + (file_info->compression_method == MZ_COMPRESS_METHOD_PPMD) || + (file_info->compression_method == MZ_COMPRESS_METHOD_XZ)) + version_needed = 63; +#endif + } + err = mz_stream_write_uint16(stream, version_needed); + } + if (err == MZ_OK) + err = mz_stream_write_uint16(stream, file_info->flag); + if (err == MZ_OK) { +#ifdef HAVE_WZAES + if ((file_info->flag & MZ_ZIP_FLAG_ENCRYPTED) && (file_info->aes_version)) + err = mz_stream_write_uint16(stream, MZ_COMPRESS_METHOD_AES); + else +#endif + err = mz_stream_write_uint16(stream, file_info->compression_method); + } + if (err == MZ_OK) { + if (file_info->modified_date != 0 && !mask) + dos_date = mz_zip_time_t_to_dos_date(file_info->modified_date); + err = mz_stream_write_uint32(stream, dos_date); + } + + if (err == MZ_OK) + err = mz_zip_entry_write_crc_sizes(stream, zip64, mask, file_info); + + if (mask) { + snprintf(masked_name, sizeof(masked_name), "%" PRIx32 "_%" PRIx64, file_info->disk_number, + file_info->disk_offset); + filename = masked_name; + } else { + filename = file_info->filename; + } + + filename_length = (uint16_t)strlen(filename); + filename_size += filename_length; + + if ((mz_zip_attrib_is_dir(file_info->external_fa, file_info->version_madeby) == MZ_OK) && + ((filename[filename_length - 1] != '/') && (filename[filename_length - 1] != '\\'))) { + filename_size += 1; + write_end_slash = 1; + } + + if (err == MZ_OK) + err = mz_stream_write_uint16(stream, filename_size); + if (err == MZ_OK) + err = mz_stream_write_uint16(stream, extrafield_size); + + if (!local) { + if (file_info->comment) { + comment_size = (int32_t)strlen(file_info->comment); + if (comment_size > UINT16_MAX) + comment_size = UINT16_MAX; + } + if (err == MZ_OK) + err = mz_stream_write_uint16(stream, (uint16_t)comment_size); + if (err == MZ_OK) + err = mz_stream_write_uint16(stream, (uint16_t)file_info->disk_number); + if (err == MZ_OK) + err = mz_stream_write_uint16(stream, file_info->internal_fa); + if (err == MZ_OK) + err = mz_stream_write_uint32(stream, file_info->external_fa); + if (err == MZ_OK) { + if (file_info->disk_offset >= UINT32_MAX) + err = mz_stream_write_uint32(stream, UINT32_MAX); + else + err = mz_stream_write_uint32(stream, (uint32_t)file_info->disk_offset); + } + } + + if (err == MZ_OK) { + const char *next = filename; + int32_t left = filename_length; +#if defined(_WIN32) + const char *backslash = NULL; + + /* Ensure all slashes are written as forward slashes according to 4.4.17.1 */ + while ((err == MZ_OK) && (backslash = strchr(next, '\\'))) { + int32_t part_length = (int32_t)(backslash - next); + + if (mz_stream_write(stream, next, part_length) != part_length || mz_stream_write(stream, "/", 1) != 1) + err = MZ_WRITE_ERROR; + + left -= part_length + 1; + next = backslash + 1; + } +#endif + if (err == MZ_OK && left > 0) { + if (mz_stream_write(stream, next, left) != left) + err = MZ_WRITE_ERROR; + } + + /* Ensure that directories have a slash appended to them for compatibility */ + if (err == MZ_OK && write_end_slash) + err = mz_stream_write_uint8(stream, '/'); + } + + /* Write ZIP64 extra field first so we can update sizes later if data descriptor not used */ + if ((err == MZ_OK) && (zip64)) { + err = mz_zip_extrafield_write(stream, MZ_ZIP_EXTENSION_ZIP64, field_length_zip64); + if (err == MZ_OK) { + if (mask) + err = mz_stream_write_int64(stream, 0); + else + err = mz_stream_write_int64(stream, file_info->uncompressed_size); + } + if (err == MZ_OK) + err = mz_stream_write_int64(stream, file_info->compressed_size); + if ((err == MZ_OK) && (!local) && (file_info->disk_offset >= UINT32_MAX)) + err = mz_stream_write_int64(stream, file_info->disk_offset); + if ((err == MZ_OK) && (!local) && (file_info->disk_number >= UINT16_MAX)) + err = mz_stream_write_uint32(stream, file_info->disk_number); + } + /* Write NTFS extra field */ + if ((err == MZ_OK) && (field_length_ntfs > 0)) { + err = mz_zip_extrafield_write(stream, MZ_ZIP_EXTENSION_NTFS, field_length_ntfs); + if (err == MZ_OK) + err = mz_stream_write_uint32(stream, reserved); + if (err == MZ_OK) + err = mz_stream_write_uint16(stream, 0x01); + if (err == MZ_OK) + err = mz_stream_write_uint16(stream, field_length_ntfs - 8); + if (err == MZ_OK) { + mz_zip_unix_to_ntfs_time(file_info->modified_date, &ntfs_time); + err = mz_stream_write_uint64(stream, ntfs_time); + } + if (err == MZ_OK) { + mz_zip_unix_to_ntfs_time(file_info->accessed_date, &ntfs_time); + err = mz_stream_write_uint64(stream, ntfs_time); + } + if (err == MZ_OK) { + mz_zip_unix_to_ntfs_time(file_info->creation_date, &ntfs_time); + err = mz_stream_write_uint64(stream, ntfs_time); + } + } + /* Write UNIX extra block extra field */ + if ((err == MZ_OK) && (field_length_unix1 > 0)) { + err = mz_zip_extrafield_write(stream, MZ_ZIP_EXTENSION_UNIX1, field_length_unix1); + if (err == MZ_OK) + err = mz_stream_write_uint32(stream, (uint32_t)file_info->accessed_date); + if (err == MZ_OK) + err = mz_stream_write_uint32(stream, (uint32_t)file_info->modified_date); + if (err == MZ_OK) /* User id */ + err = mz_stream_write_uint16(stream, 0); + if (err == MZ_OK) /* Group id */ + err = mz_stream_write_uint16(stream, 0); + if (err == MZ_OK && linkname_size > 0) { + if (mz_stream_write(stream, file_info->linkname, linkname_size) != linkname_size) + err = MZ_WRITE_ERROR; + } + } +#ifdef HAVE_WZAES + /* Write AES extra field */ + if ((err == MZ_OK) && (!skip_aes) && (file_info->flag & MZ_ZIP_FLAG_ENCRYPTED) && (file_info->aes_version)) { + err = mz_zip_extrafield_write(stream, MZ_ZIP_EXTENSION_AES, field_length_aes); + if (err == MZ_OK) + err = mz_stream_write_uint16(stream, file_info->aes_version); + if (err == MZ_OK) + err = mz_stream_write_uint8(stream, 'A'); + if (err == MZ_OK) + err = mz_stream_write_uint8(stream, 'E'); + if (err == MZ_OK) + err = mz_stream_write_uint8(stream, file_info->aes_strength); + if (err == MZ_OK) + err = mz_stream_write_uint16(stream, file_info->compression_method); + } +#endif + + if (file_info->extrafield_size > 0) { + err_mem = mz_stream_mem_seek(file_extra_stream, 0, MZ_SEEK_SET); + while (err == MZ_OK && err_mem == MZ_OK) { + err_mem = mz_stream_read_uint16(file_extra_stream, &field_type); + if (err_mem == MZ_OK) + err_mem = mz_stream_read_uint16(file_extra_stream, &field_length); + if (err_mem != MZ_OK) + break; + + /* Prefer our zip 64, ntfs, unix1 extensions over incoming */ + if (field_type == MZ_ZIP_EXTENSION_ZIP64 || field_type == MZ_ZIP_EXTENSION_NTFS || + field_type == MZ_ZIP_EXTENSION_UNIX1) { + err_mem = mz_stream_seek(file_extra_stream, field_length, MZ_SEEK_CUR); + continue; + } + + err = mz_stream_write_uint16(stream, field_type); + if (err == MZ_OK) + err = mz_stream_write_uint16(stream, field_length); + if (err == MZ_OK) + err = mz_stream_copy(stream, file_extra_stream, field_length); + } + + mz_stream_mem_delete(&file_extra_stream); + } + + if (err == MZ_OK && !local && file_info->comment) { + if (mz_stream_write(stream, file_info->comment, file_info->comment_size) != file_info->comment_size) + err = MZ_WRITE_ERROR; + } + + return err; +} + +static int32_t mz_zip_entry_write_descriptor(void *stream, uint8_t zip64, uint32_t crc32, int64_t compressed_size, + int64_t uncompressed_size) { + int32_t err = MZ_OK; + + err = mz_stream_write_uint32(stream, MZ_ZIP_MAGIC_DATADESCRIPTOR); + if (err == MZ_OK) + err = mz_stream_write_uint32(stream, crc32); + + /* Store data descriptor as 8 bytes if zip 64 extension enabled */ + if (err == MZ_OK) { + /* Zip 64 extension is enabled when uncompressed size is > UINT32_MAX */ + if (!zip64) + err = mz_stream_write_uint32(stream, (uint32_t)compressed_size); + else + err = mz_stream_write_int64(stream, compressed_size); + } + if (err == MZ_OK) { + if (!zip64) + err = mz_stream_write_uint32(stream, (uint32_t)uncompressed_size); + else + err = mz_stream_write_int64(stream, uncompressed_size); + } + + return err; +} + +static int32_t mz_zip_read_cd(void *handle) { + mz_zip *zip = (mz_zip *)handle; + uint64_t number_entry_cd64 = 0; + uint64_t number_entry_cd = 0; + int64_t eocd_pos = 0; + int64_t eocd_pos64 = 0; + int64_t value64i = 0; + uint16_t value16 = 0; + uint32_t value32 = 0; + uint64_t value64 = 0; + uint16_t comment_size = 0; + int32_t comment_read = 0; + int32_t err = MZ_OK; + + if (!zip) + return MZ_PARAM_ERROR; + + /* Read and cache central directory records */ + err = mz_zip_search_eocd(zip->stream, &eocd_pos); + if (err == MZ_OK) { + /* The signature, already checked */ + err = mz_stream_read_uint32(zip->stream, &value32); + /* Number of this disk */ + if (err == MZ_OK) + err = mz_stream_read_uint16(zip->stream, &value16); + /* Number of the disk with the start of the central directory */ + if (err == MZ_OK) + err = mz_stream_read_uint16(zip->stream, &value16); + zip->disk_number_with_cd = value16; + /* Total number of entries in the central dir on this disk */ + if (err == MZ_OK) + err = mz_stream_read_uint16(zip->stream, &value16); + zip->number_entry = value16; + /* Total number of entries in the central dir */ + if (err == MZ_OK) + err = mz_stream_read_uint16(zip->stream, &value16); + number_entry_cd = value16; + /* When recover is enabled, we can ignore incorrect number of entries */ + if (number_entry_cd != zip->number_entry && !zip->recover) + err = MZ_FORMAT_ERROR; + /* Size of the central directory */ + if (err == MZ_OK) + err = mz_stream_read_uint32(zip->stream, &value32); + if (err == MZ_OK) + zip->cd_size = value32; + /* Offset of start of central directory with respect to the starting disk number */ + if (err == MZ_OK) + err = mz_stream_read_uint32(zip->stream, &value32); + if (err == MZ_OK) + zip->cd_offset = value32; + /* Zip file global comment length */ + if (err == MZ_OK) + err = mz_stream_read_uint16(zip->stream, &comment_size); + if ((err == MZ_OK) && (comment_size > 0)) { + zip->comment = (char *)malloc(comment_size + 1); + if (zip->comment) { + comment_read = mz_stream_read(zip->stream, zip->comment, comment_size); + /* Don't fail if incorrect comment length read, not critical */ + if (comment_read < 0) + comment_read = 0; + zip->comment[comment_read] = 0; + } + } + + if ((err == MZ_OK) && ((number_entry_cd == UINT16_MAX) || (zip->cd_offset == UINT32_MAX))) { + /* Format should be Zip64, as the central directory or file size is too large */ + if (mz_zip_search_zip64_eocd(zip->stream, eocd_pos, &eocd_pos64) == MZ_OK) { + eocd_pos = eocd_pos64; + + err = mz_stream_seek(zip->stream, eocd_pos, MZ_SEEK_SET); + /* The signature, already checked */ + if (err == MZ_OK) + err = mz_stream_read_uint32(zip->stream, &value32); + /* Size of zip64 end of central directory record */ + if (err == MZ_OK) + err = mz_stream_read_uint64(zip->stream, &value64); + /* Version made by */ + if (err == MZ_OK) + err = mz_stream_read_uint16(zip->stream, &zip->version_madeby); + /* Version needed to extract */ + if (err == MZ_OK) + err = mz_stream_read_uint16(zip->stream, &value16); + /* Number of this disk */ + if (err == MZ_OK) + err = mz_stream_read_uint32(zip->stream, &value32); + /* Number of the disk with the start of the central directory */ + if (err == MZ_OK) + err = mz_stream_read_uint32(zip->stream, &zip->disk_number_with_cd); + /* Total number of entries in the central directory on this disk */ + if (err == MZ_OK) + err = mz_stream_read_uint64(zip->stream, &zip->number_entry); + /* Total number of entries in the central directory */ + if (err == MZ_OK) + err = mz_stream_read_uint64(zip->stream, &number_entry_cd64); + if (zip->number_entry != number_entry_cd64) + err = MZ_FORMAT_ERROR; + /* Size of the central directory */ + if (err == MZ_OK) { + err = mz_stream_read_int64(zip->stream, &zip->cd_size); + if (zip->cd_size < 0) + err = MZ_FORMAT_ERROR; + } + /* Offset of start of central directory with respect to the starting disk number */ + if (err == MZ_OK) { + err = mz_stream_read_int64(zip->stream, &zip->cd_offset); + if (zip->cd_offset < 0) + err = MZ_FORMAT_ERROR; + } + } else if ((zip->number_entry == UINT16_MAX) || (number_entry_cd != zip->number_entry) || + (zip->cd_size == UINT16_MAX) || (zip->cd_offset == UINT32_MAX)) { + err = MZ_FORMAT_ERROR; + } + } + } + + if (err == MZ_OK) { + mz_zip_print("Zip - Read cd (disk %" PRId32 " entries %" PRId64 " offset %" PRId64 " size %" PRId64 ")\n", + zip->disk_number_with_cd, zip->number_entry, zip->cd_offset, zip->cd_size); + + /* Verify central directory signature exists at offset */ + err = mz_stream_seek(zip->stream, zip->cd_offset, MZ_SEEK_SET); + if (err == MZ_OK) + err = mz_stream_read_uint32(zip->stream, &zip->cd_signature); + if ((err == MZ_OK) && (zip->cd_signature != MZ_ZIP_MAGIC_CENTRALHEADER)) { + /* If cd exists in large file and no zip-64 support, error for recover */ + if (eocd_pos > UINT32_MAX && eocd_pos64 == 0) + err = MZ_FORMAT_ERROR; + /* If cd not found attempt to seek backward to find it */ + if (err == MZ_OK) + err = mz_stream_seek(zip->stream, eocd_pos - zip->cd_size, MZ_SEEK_SET); + if (err == MZ_OK) + err = mz_stream_read_uint32(zip->stream, &zip->cd_signature); + if ((err == MZ_OK) && (zip->cd_signature == MZ_ZIP_MAGIC_CENTRALHEADER)) { + /* If found compensate for incorrect locations */ + value64i = zip->cd_offset; + zip->cd_offset = eocd_pos - zip->cd_size; + /* Assume disk has prepended data */ + zip->disk_offset_shift = zip->cd_offset - value64i; + } + } + } + + if (err == MZ_OK) { + if (eocd_pos < zip->cd_offset) { + /* End of central dir should always come after central dir */ + err = MZ_FORMAT_ERROR; + } else if ((uint64_t)eocd_pos < (uint64_t)zip->cd_offset + zip->cd_size) { + /* Truncate size of cd if incorrect size or offset provided */ + zip->cd_size = eocd_pos - zip->cd_offset; + } + } + + return err; +} + +static int32_t mz_zip_write_cd(void *handle) { + mz_zip *zip = (mz_zip *)handle; + int64_t zip64_eocd_pos_inzip = 0; + int64_t disk_number = 0; + int64_t disk_size = 0; + int32_t comment_size = 0; + int32_t err = MZ_OK; + + if (!zip) + return MZ_PARAM_ERROR; + + if (mz_stream_get_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, &disk_number) == MZ_OK) + zip->disk_number_with_cd = (uint32_t)disk_number; + if (mz_stream_get_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_SIZE, &disk_size) == MZ_OK && disk_size > 0) + zip->disk_number_with_cd += 1; + mz_stream_set_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, -1); + if ((zip->disk_number_with_cd > 0) && (zip->open_mode & MZ_OPEN_MODE_APPEND)) { + /* Overwrite existing central directory if using split disks */ + mz_stream_seek(zip->stream, 0, MZ_SEEK_SET); + } + + zip->cd_offset = mz_stream_tell(zip->stream); + mz_stream_seek(zip->cd_mem_stream, 0, MZ_SEEK_END); + zip->cd_size = (uint32_t)mz_stream_tell(zip->cd_mem_stream); + mz_stream_seek(zip->cd_mem_stream, 0, MZ_SEEK_SET); + + err = mz_stream_copy(zip->stream, zip->cd_mem_stream, (int32_t)zip->cd_size); + + mz_zip_print("Zip - Write cd (disk %" PRId32 " entries %" PRId64 " offset %" PRId64 " size %" PRId64 ")\n", + zip->disk_number_with_cd, zip->number_entry, zip->cd_offset, zip->cd_size); + + if (zip->cd_size == 0 && zip->number_entry > 0) { + /* Zip does not contain central directory, open with recovery option */ + return MZ_FORMAT_ERROR; + } + + /* Write the ZIP64 central directory header */ + if (zip->cd_offset >= UINT32_MAX || zip->number_entry >= UINT16_MAX) { + zip64_eocd_pos_inzip = mz_stream_tell(zip->stream); + + err = mz_stream_write_uint32(zip->stream, MZ_ZIP_MAGIC_ENDHEADER64); + + /* Size of this 'zip64 end of central directory' */ + if (err == MZ_OK) + err = mz_stream_write_uint64(zip->stream, (uint64_t)44); + /* Version made by */ + if (err == MZ_OK) + err = mz_stream_write_uint16(zip->stream, zip->version_madeby); + /* Version needed */ + if (err == MZ_OK) + err = mz_stream_write_uint16(zip->stream, (uint16_t)45); + /* Number of this disk */ + if (err == MZ_OK) + err = mz_stream_write_uint32(zip->stream, zip->disk_number_with_cd); + /* Number of the disk with the start of the central directory */ + if (err == MZ_OK) + err = mz_stream_write_uint32(zip->stream, zip->disk_number_with_cd); + /* Total number of entries in the central dir on this disk */ + if (err == MZ_OK) + err = mz_stream_write_uint64(zip->stream, zip->number_entry); + /* Total number of entries in the central dir */ + if (err == MZ_OK) + err = mz_stream_write_uint64(zip->stream, zip->number_entry); + /* Size of the central directory */ + if (err == MZ_OK) + err = mz_stream_write_int64(zip->stream, zip->cd_size); + /* Offset of start of central directory with respect to the starting disk number */ + if (err == MZ_OK) + err = mz_stream_write_int64(zip->stream, zip->cd_offset); + if (err == MZ_OK) + err = mz_stream_write_uint32(zip->stream, MZ_ZIP_MAGIC_ENDLOCHEADER64); + + /* Number of the disk with the start of the central directory */ + if (err == MZ_OK) + err = mz_stream_write_uint32(zip->stream, zip->disk_number_with_cd); + /* Relative offset to the end of zip64 central directory */ + if (err == MZ_OK) + err = mz_stream_write_int64(zip->stream, zip64_eocd_pos_inzip); + /* Number of the disk with the start of the central directory */ + if (err == MZ_OK) + err = mz_stream_write_uint32(zip->stream, zip->disk_number_with_cd + 1); + } + + /* Write the central directory header */ + + /* Signature */ + if (err == MZ_OK) + err = mz_stream_write_uint32(zip->stream, MZ_ZIP_MAGIC_ENDHEADER); + /* Number of this disk */ + if (err == MZ_OK) + err = mz_stream_write_uint16(zip->stream, (uint16_t)zip->disk_number_with_cd); + /* Number of the disk with the start of the central directory */ + if (err == MZ_OK) + err = mz_stream_write_uint16(zip->stream, (uint16_t)zip->disk_number_with_cd); + /* Total number of entries in the central dir on this disk */ + if (err == MZ_OK) { + if (zip->number_entry >= UINT16_MAX) + err = mz_stream_write_uint16(zip->stream, UINT16_MAX); + else + err = mz_stream_write_uint16(zip->stream, (uint16_t)zip->number_entry); + } + /* Total number of entries in the central dir */ + if (err == MZ_OK) { + if (zip->number_entry >= UINT16_MAX) + err = mz_stream_write_uint16(zip->stream, UINT16_MAX); + else + err = mz_stream_write_uint16(zip->stream, (uint16_t)zip->number_entry); + } + /* Size of the central directory */ + if (err == MZ_OK) + err = mz_stream_write_uint32(zip->stream, (uint32_t)zip->cd_size); + /* Offset of start of central directory with respect to the starting disk number */ + if (err == MZ_OK) { + if (zip->cd_offset >= UINT32_MAX) + err = mz_stream_write_uint32(zip->stream, UINT32_MAX); + else + err = mz_stream_write_uint32(zip->stream, (uint32_t)zip->cd_offset); + } + + /* Write global comment */ + if (zip->comment) { + comment_size = (int32_t)strlen(zip->comment); + if (comment_size > UINT16_MAX) + comment_size = UINT16_MAX; + } + if (err == MZ_OK) + err = mz_stream_write_uint16(zip->stream, (uint16_t)comment_size); + if (err == MZ_OK) { + if (mz_stream_write(zip->stream, zip->comment, comment_size) != comment_size) + err = MZ_READ_ERROR; + } + return err; +} + +static int32_t mz_zip_recover_cd(void *handle) { + mz_zip *zip = (mz_zip *)handle; + mz_zip_file local_file_info; + void *local_file_info_stream = NULL; + void *cd_mem_stream = NULL; + uint64_t number_entry = 0; + int64_t descriptor_pos = 0; + int64_t next_header_pos = 0; + int64_t disk_offset = 0; + int64_t disk_number = 0; + int64_t compressed_pos = 0; + int64_t compressed_end_pos = 0; + int64_t compressed_size = 0; + int64_t uncompressed_size = 0; + uint8_t descriptor_magic[4] = MZ_ZIP_MAGIC_DATADESCRIPTORU8; + uint8_t local_header_magic[4] = MZ_ZIP_MAGIC_LOCALHEADERU8; + uint8_t central_header_magic[4] = MZ_ZIP_MAGIC_CENTRALHEADERU8; + uint32_t crc32 = 0; + int32_t disk_number_with_cd = 0; + int32_t err = MZ_OK; + uint8_t zip64 = 0; + uint8_t eof = 0; + + mz_zip_print("Zip - Recover - Start\n"); + + err = mz_zip_get_cd_mem_stream(zip, &cd_mem_stream); + if (err != MZ_OK) + return err; + + /* Determine if we are on a split disk or not */ + mz_stream_set_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, 0); + if (mz_stream_tell(zip->stream) < 0) { + mz_stream_set_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, -1); + mz_stream_seek(zip->stream, 0, MZ_SEEK_SET); + } else + disk_number_with_cd = 1; + + local_file_info_stream = mz_stream_mem_create(); + if (!local_file_info_stream) + return MZ_MEM_ERROR; + + if (mz_stream_is_open(cd_mem_stream) != MZ_OK) + err = mz_stream_mem_open(cd_mem_stream, NULL, MZ_OPEN_MODE_CREATE); + + mz_stream_mem_open(local_file_info_stream, NULL, MZ_OPEN_MODE_CREATE); + + if (err == MZ_OK) { + err = mz_stream_find(zip->stream, (const void *)local_header_magic, sizeof(local_header_magic), INT64_MAX, + &next_header_pos); + } + + while (err == MZ_OK && !eof) { + /* Get current offset and disk number for central dir record */ + disk_offset = mz_stream_tell(zip->stream); + mz_stream_get_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, &disk_number); + + /* Read local headers */ + memset(&local_file_info, 0, sizeof(local_file_info)); + err = mz_zip_entry_read_header(zip->stream, 1, &local_file_info, local_file_info_stream); + if (err != MZ_OK) + break; + + local_file_info.disk_offset = disk_offset; + if (disk_number < 0) + disk_number = 0; + local_file_info.disk_number = (uint32_t)disk_number; + + compressed_pos = mz_stream_tell(zip->stream); + + if ((err == MZ_OK) && (local_file_info.compressed_size > 0)) { + mz_stream_seek(zip->stream, local_file_info.compressed_size, MZ_SEEK_CUR); + } + + for (;;) { + /* Search for the next local header */ + err = mz_stream_find(zip->stream, (const void *)local_header_magic, sizeof(local_header_magic), INT64_MAX, + &next_header_pos); + + if (err == MZ_EXIST_ERROR) { + mz_stream_seek(zip->stream, compressed_pos, MZ_SEEK_SET); + + /* Search for central dir if no local header found */ + err = mz_stream_find(zip->stream, (const void *)central_header_magic, sizeof(central_header_magic), + INT64_MAX, &next_header_pos); + + if (err == MZ_EXIST_ERROR) { + /* Get end of stream if no central header found */ + mz_stream_seek(zip->stream, 0, MZ_SEEK_END); + next_header_pos = mz_stream_tell(zip->stream); + } + + eof = 1; + } + + if (local_file_info.flag & MZ_ZIP_FLAG_DATA_DESCRIPTOR || local_file_info.compressed_size == 0) { + /* Search backwards for the descriptor, seeking too far back will be incorrect if compressed size is + * small */ + err = mz_stream_find_reverse(zip->stream, (const void *)descriptor_magic, sizeof(descriptor_magic), + MZ_ZIP_SIZE_MAX_DATA_DESCRIPTOR, &descriptor_pos); + if (err == MZ_OK) { + if (mz_zip_extrafield_contains(local_file_info.extrafield, local_file_info.extrafield_size, + MZ_ZIP_EXTENSION_ZIP64, NULL) == MZ_OK) + zip64 = 1; + + err = + mz_zip_entry_read_descriptor(zip->stream, zip64, &crc32, &compressed_size, &uncompressed_size); + + if (err == MZ_OK) { + if (local_file_info.crc == 0) + local_file_info.crc = crc32; + if (local_file_info.compressed_size == 0) + local_file_info.compressed_size = compressed_size; + if (local_file_info.uncompressed_size == 0) + local_file_info.uncompressed_size = uncompressed_size; + } + + compressed_end_pos = descriptor_pos; + } else if (eof) { + compressed_end_pos = next_header_pos; + } else if (local_file_info.flag & MZ_ZIP_FLAG_DATA_DESCRIPTOR) { + /* Wrong local file entry found, keep searching */ + next_header_pos += 1; + mz_stream_seek(zip->stream, next_header_pos, MZ_SEEK_SET); + continue; + } + } else { + compressed_end_pos = next_header_pos; + } + + break; + } + + compressed_size = compressed_end_pos - compressed_pos; + + if (compressed_size > UINT32_MAX) { + /* Update sizes if 4GB file is written with no ZIP64 support */ + if (local_file_info.uncompressed_size < UINT32_MAX) { + local_file_info.compressed_size = compressed_size; + local_file_info.uncompressed_size = 0; + } + } + + mz_zip_print("Zip - Recover - Entry %s (csize %" PRId64 " usize %" PRId64 " flags 0x%" PRIx16 ")\n", + local_file_info.filename, local_file_info.compressed_size, local_file_info.uncompressed_size, + local_file_info.flag); + + /* Rewrite central dir with local headers and offsets */ + err = mz_zip_entry_write_header(cd_mem_stream, 0, &local_file_info); + if (err == MZ_OK) + number_entry += 1; + + err = mz_stream_seek(zip->stream, next_header_pos, MZ_SEEK_SET); + } + + mz_stream_mem_delete(&local_file_info_stream); + + mz_zip_print("Zip - Recover - Complete (cddisk %" PRId32 " entries %" PRId64 ")\n", disk_number_with_cd, + number_entry); + + if (number_entry == 0) + return err; + + /* Set new upper seek boundary for central dir mem stream */ + disk_offset = mz_stream_tell(cd_mem_stream); + mz_stream_mem_set_buffer_limit(cd_mem_stream, (int32_t)disk_offset); + + /* Set new central directory info */ + mz_zip_set_cd_stream(zip, 0, cd_mem_stream); + mz_zip_set_number_entry(zip, number_entry); + mz_zip_set_disk_number_with_cd(zip, disk_number_with_cd); + + return MZ_OK; +} + +void *mz_zip_create(void) { + mz_zip *zip = (mz_zip *)calloc(1, sizeof(mz_zip)); + if (zip) + zip->data_descriptor = 1; + return zip; +} + +void mz_zip_delete(void **handle) { + mz_zip *zip = NULL; + if (!handle) + return; + zip = (mz_zip *)*handle; + free(zip); + *handle = NULL; +} + +int32_t mz_zip_open(void *handle, void *stream, int32_t mode) { + mz_zip *zip = (mz_zip *)handle; + int32_t err = MZ_OK; + + if (!zip) + return MZ_PARAM_ERROR; + + mz_zip_print("Zip - Open\n"); + + zip->stream = stream; + zip->cd_mem_stream = mz_stream_mem_create(); + if (!zip->cd_mem_stream) + return MZ_MEM_ERROR; + + if (mode & MZ_OPEN_MODE_WRITE) { + mz_stream_mem_open(zip->cd_mem_stream, NULL, MZ_OPEN_MODE_CREATE); + zip->cd_stream = zip->cd_mem_stream; + } else { + zip->cd_stream = stream; + } + + if ((mode & MZ_OPEN_MODE_READ) || (mode & MZ_OPEN_MODE_APPEND)) { + if ((mode & MZ_OPEN_MODE_CREATE) == 0) { + err = mz_zip_read_cd(zip); + if (err != MZ_OK) { + mz_zip_print("Zip - Error detected reading cd (%" PRId32 ")\n", err); + if (zip->recover && mz_zip_recover_cd(zip) == MZ_OK) + err = MZ_OK; + } + } + + if ((err == MZ_OK) && (mode & MZ_OPEN_MODE_APPEND)) { + if (zip->cd_size > 0) { + /* Store central directory in memory */ + err = mz_stream_seek(zip->stream, zip->cd_offset, MZ_SEEK_SET); + if (err == MZ_OK) + err = mz_stream_copy(zip->cd_mem_stream, zip->stream, (int32_t)zip->cd_size); + if (err == MZ_OK) + err = mz_stream_seek(zip->stream, zip->cd_offset, MZ_SEEK_SET); + } else { + if (zip->cd_signature == MZ_ZIP_MAGIC_ENDHEADER) { + /* If tiny zip then overwrite end header */ + err = mz_stream_seek(zip->stream, zip->cd_offset, MZ_SEEK_SET); + } else { + /* If no central directory, append new zip to end of file */ + err = mz_stream_seek(zip->stream, 0, MZ_SEEK_END); + } + } + + if (zip->disk_number_with_cd > 0) { + /* Move to last disk to begin appending */ + mz_stream_set_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, zip->disk_number_with_cd - 1); + } + } else { + zip->cd_start_pos = zip->cd_offset; + } + } + + if (err != MZ_OK) { + mz_zip_close(zip); + return err; + } + + /* Memory streams used to store variable length file info data */ + zip->file_info_stream = mz_stream_mem_create(); + if (!zip->file_info_stream) + return MZ_MEM_ERROR; + + mz_stream_mem_open(zip->file_info_stream, NULL, MZ_OPEN_MODE_CREATE); + + zip->local_file_info_stream = mz_stream_mem_create(); + if (!zip->local_file_info_stream) { + mz_stream_delete(&zip->file_info_stream); + return MZ_MEM_ERROR; + } + + mz_stream_mem_open(zip->local_file_info_stream, NULL, MZ_OPEN_MODE_CREATE); + + zip->open_mode = mode; + + return err; +} + +int32_t mz_zip_close(void *handle) { + mz_zip *zip = (mz_zip *)handle; + int32_t err = MZ_OK; + + if (!zip) + return MZ_PARAM_ERROR; + + mz_zip_print("Zip - Close\n"); + + if (mz_zip_entry_is_open(zip) == MZ_OK) + err = mz_zip_entry_close(zip); + + if ((err == MZ_OK) && (zip->open_mode & MZ_OPEN_MODE_WRITE)) + err = mz_zip_write_cd(zip); + + if (zip->cd_mem_stream) { + mz_stream_close(zip->cd_mem_stream); + mz_stream_delete(&zip->cd_mem_stream); + } + + if (zip->file_info_stream) { + mz_stream_mem_close(zip->file_info_stream); + mz_stream_mem_delete(&zip->file_info_stream); + } + if (zip->local_file_info_stream) { + mz_stream_mem_close(zip->local_file_info_stream); + mz_stream_mem_delete(&zip->local_file_info_stream); + } + + if (zip->comment) { + free(zip->comment); + zip->comment = NULL; + } + + zip->stream = NULL; + zip->cd_stream = NULL; + + return err; +} + +int32_t mz_zip_get_comment(void *handle, const char **comment) { + mz_zip *zip = (mz_zip *)handle; + if (!zip || !comment) + return MZ_PARAM_ERROR; + if (!zip->comment) + return MZ_EXIST_ERROR; + *comment = zip->comment; + return MZ_OK; +} + +int32_t mz_zip_set_comment(void *handle, const char *comment) { + mz_zip *zip = (mz_zip *)handle; + int32_t comment_size = 0; + if (!zip || !comment) + return MZ_PARAM_ERROR; + free(zip->comment); + comment_size = (int32_t)strlen(comment); + if (comment_size > UINT16_MAX) + return MZ_PARAM_ERROR; + zip->comment = (char *)calloc(comment_size + 1, sizeof(char)); + if (!zip->comment) + return MZ_MEM_ERROR; + strncpy(zip->comment, comment, comment_size); + return MZ_OK; +} + +int32_t mz_zip_get_version_madeby(void *handle, uint16_t *version_madeby) { + mz_zip *zip = (mz_zip *)handle; + if (!zip || !version_madeby) + return MZ_PARAM_ERROR; + *version_madeby = zip->version_madeby; + return MZ_OK; +} + +int32_t mz_zip_set_version_madeby(void *handle, uint16_t version_madeby) { + mz_zip *zip = (mz_zip *)handle; + if (!zip) + return MZ_PARAM_ERROR; + zip->version_madeby = version_madeby; + return MZ_OK; +} + +int32_t mz_zip_set_recover(void *handle, uint8_t recover) { + mz_zip *zip = (mz_zip *)handle; + if (!zip) + return MZ_PARAM_ERROR; + zip->recover = recover; + return MZ_OK; +} + +int32_t mz_zip_set_data_descriptor(void *handle, uint8_t data_descriptor) { + mz_zip *zip = (mz_zip *)handle; + if (!zip) + return MZ_PARAM_ERROR; + zip->data_descriptor = data_descriptor; + return MZ_OK; +} + +int32_t mz_zip_get_stream(void *handle, void **stream) { + mz_zip *zip = (mz_zip *)handle; + if (!zip || !stream) + return MZ_PARAM_ERROR; + *stream = zip->stream; + if (!*stream) + return MZ_EXIST_ERROR; + return MZ_OK; +} + +int32_t mz_zip_set_cd_stream(void *handle, int64_t cd_start_pos, void *cd_stream) { + mz_zip *zip = (mz_zip *)handle; + if (!zip || !cd_stream) + return MZ_PARAM_ERROR; + zip->cd_offset = 0; + zip->cd_stream = cd_stream; + zip->cd_start_pos = cd_start_pos; + return MZ_OK; +} + +int32_t mz_zip_get_cd_mem_stream(void *handle, void **cd_mem_stream) { + mz_zip *zip = (mz_zip *)handle; + if (!zip || !cd_mem_stream) + return MZ_PARAM_ERROR; + *cd_mem_stream = zip->cd_mem_stream; + if (!*cd_mem_stream) + return MZ_EXIST_ERROR; + return MZ_OK; +} + +int32_t mz_zip_set_number_entry(void *handle, uint64_t number_entry) { + mz_zip *zip = (mz_zip *)handle; + if (!zip) + return MZ_PARAM_ERROR; + zip->number_entry = number_entry; + return MZ_OK; +} + +int32_t mz_zip_get_number_entry(void *handle, uint64_t *number_entry) { + mz_zip *zip = (mz_zip *)handle; + if (!zip || !number_entry) + return MZ_PARAM_ERROR; + *number_entry = zip->number_entry; + return MZ_OK; +} + +int32_t mz_zip_set_disk_number_with_cd(void *handle, uint32_t disk_number_with_cd) { + mz_zip *zip = (mz_zip *)handle; + if (!zip) + return MZ_PARAM_ERROR; + zip->disk_number_with_cd = disk_number_with_cd; + return MZ_OK; +} + +int32_t mz_zip_get_disk_number_with_cd(void *handle, uint32_t *disk_number_with_cd) { + mz_zip *zip = (mz_zip *)handle; + if (!zip || !disk_number_with_cd) + return MZ_PARAM_ERROR; + *disk_number_with_cd = zip->disk_number_with_cd; + return MZ_OK; +} + +static int32_t mz_zip_entry_close_int(void *handle) { + mz_zip *zip = (mz_zip *)handle; + + if (zip->crypt_stream) + mz_stream_delete(&zip->crypt_stream); + zip->crypt_stream = NULL; + if (zip->compress_stream) + mz_stream_delete(&zip->compress_stream); + zip->compress_stream = NULL; + + zip->entry_opened = 0; + + return MZ_OK; +} + +static int32_t mz_zip_entry_open_int(void *handle, uint8_t raw, int16_t compress_level, const char *password) { + mz_zip *zip = (mz_zip *)handle; + int64_t max_total_in = 0; + int64_t header_size = 0; + int64_t footer_size = 0; + int32_t err = MZ_OK; + uint8_t use_crypt = 0; + + if (!zip) + return MZ_PARAM_ERROR; + + switch (zip->file_info.compression_method) { + case MZ_COMPRESS_METHOD_STORE: + case MZ_COMPRESS_METHOD_DEFLATE: +#ifdef HAVE_BZIP2 + case MZ_COMPRESS_METHOD_BZIP2: +#endif +#ifdef HAVE_LZMA + case MZ_COMPRESS_METHOD_LZMA: +#endif +#if defined(HAVE_LZMA) || defined(HAVE_LIBCOMP) + case MZ_COMPRESS_METHOD_XZ: +#endif +#ifdef HAVE_PPMD + case MZ_COMPRESS_METHOD_PPMD: +#endif +#ifdef HAVE_ZSTD + case MZ_COMPRESS_METHOD_ZSTD: +#endif + err = MZ_OK; + break; + default: + return MZ_SUPPORT_ERROR; + } + +#ifndef HAVE_WZAES + if (zip->file_info.aes_version) + return MZ_SUPPORT_ERROR; +#endif + + zip->entry_raw = raw; + + if ((zip->file_info.flag & MZ_ZIP_FLAG_ENCRYPTED) && (password)) { + if (zip->open_mode & MZ_OPEN_MODE_WRITE) { + /* Encrypt only when we are not trying to write raw and password is supplied. */ + if (!zip->entry_raw) + use_crypt = 1; + } else if (zip->open_mode & MZ_OPEN_MODE_READ) { + /* Decrypt only when password is supplied. Don't error when password */ + /* is not supplied as we may want to read the raw encrypted data. */ + use_crypt = 1; + } + } + + if ((err == MZ_OK) && (use_crypt)) { +#ifdef HAVE_WZAES + if (zip->file_info.aes_version) { + zip->crypt_stream = mz_stream_wzaes_create(); + if (!zip->crypt_stream) + return MZ_MEM_ERROR; + mz_stream_wzaes_set_password(zip->crypt_stream, password); + mz_stream_wzaes_set_strength(zip->crypt_stream, zip->file_info.aes_strength); + } else +#endif + { +#ifdef HAVE_PKCRYPT + uint8_t verify1 = (uint8_t)((zip->file_info.pk_verify >> 8) & 0xff); + uint8_t verify2 = (uint8_t)((zip->file_info.pk_verify) & 0xff); + + zip->crypt_stream = mz_stream_pkcrypt_create(); + if (!zip->crypt_stream) + return MZ_MEM_ERROR; + mz_stream_pkcrypt_set_password(zip->crypt_stream, password); + mz_stream_pkcrypt_set_verify(zip->crypt_stream, verify1, verify2, zip->file_info.version_needed); +#endif + } + } + + if (err == MZ_OK) { + if (!zip->crypt_stream) + zip->crypt_stream = mz_stream_raw_create(); + if (!zip->crypt_stream) + return MZ_MEM_ERROR; + + mz_stream_set_base(zip->crypt_stream, zip->stream); + + err = mz_stream_open(zip->crypt_stream, NULL, zip->open_mode); + } + + if (err == MZ_OK) { + if (zip->entry_raw || zip->file_info.compression_method == MZ_COMPRESS_METHOD_STORE) + zip->compress_stream = mz_stream_raw_create(); +#ifdef HAVE_ZLIB + else if (zip->file_info.compression_method == MZ_COMPRESS_METHOD_DEFLATE) + zip->compress_stream = mz_stream_zlib_create(); +#endif +#ifdef HAVE_BZIP2 + else if (zip->file_info.compression_method == MZ_COMPRESS_METHOD_BZIP2) + zip->compress_stream = mz_stream_bzip_create(); +#endif +#ifdef HAVE_LIBCOMP + else if (zip->file_info.compression_method == MZ_COMPRESS_METHOD_DEFLATE || + zip->file_info.compression_method == MZ_COMPRESS_METHOD_XZ) { + zip->compress_stream = mz_stream_libcomp_create(); + if (zip->compress_stream) { + mz_stream_set_prop_int64(zip->compress_stream, MZ_STREAM_PROP_COMPRESS_METHOD, + zip->file_info.compression_method); + } + } +#endif +#ifdef HAVE_LZMA + else if (zip->file_info.compression_method == MZ_COMPRESS_METHOD_LZMA || + zip->file_info.compression_method == MZ_COMPRESS_METHOD_XZ) { + zip->compress_stream = mz_stream_lzma_create(); + if (zip->compress_stream) { + mz_stream_set_prop_int64(zip->compress_stream, MZ_STREAM_PROP_COMPRESS_METHOD, + zip->file_info.compression_method); + } + } +#endif +#ifdef HAVE_PPMD + else if (zip->file_info.compression_method == MZ_COMPRESS_METHOD_PPMD) { + zip->compress_stream = mz_stream_ppmd_create(); + if (zip->compress_stream) { + mz_stream_set_prop_int64(zip->compress_stream, MZ_STREAM_PROP_TOTAL_IN_MAX, + zip->file_info.compressed_size); + } + } +#endif +#ifdef HAVE_ZSTD + else if (zip->file_info.compression_method == MZ_COMPRESS_METHOD_ZSTD) + zip->compress_stream = mz_stream_zstd_create(); +#endif + else + err = MZ_PARAM_ERROR; + } + + if (err == MZ_OK && !zip->compress_stream) + err = MZ_MEM_ERROR; + + if (err == MZ_OK) { + if (zip->open_mode & MZ_OPEN_MODE_WRITE) { + mz_stream_set_prop_int64(zip->compress_stream, MZ_STREAM_PROP_COMPRESS_LEVEL, compress_level); + } else { + int32_t set_end_of_stream = 0; + +#ifndef HAVE_LIBCOMP + if (zip->entry_raw || zip->file_info.compression_method == MZ_COMPRESS_METHOD_STORE || + zip->file_info.flag & MZ_ZIP_FLAG_ENCRYPTED) +#endif + { + max_total_in = zip->file_info.compressed_size; + mz_stream_set_prop_int64(zip->crypt_stream, MZ_STREAM_PROP_TOTAL_IN_MAX, max_total_in); + + if (mz_stream_get_prop_int64(zip->crypt_stream, MZ_STREAM_PROP_HEADER_SIZE, &header_size) == MZ_OK) + max_total_in -= header_size; + if (mz_stream_get_prop_int64(zip->crypt_stream, MZ_STREAM_PROP_FOOTER_SIZE, &footer_size) == MZ_OK) + max_total_in -= footer_size; + + mz_stream_set_prop_int64(zip->compress_stream, MZ_STREAM_PROP_TOTAL_IN_MAX, max_total_in); + } + + switch (zip->file_info.compression_method) { + case MZ_COMPRESS_METHOD_LZMA: + case MZ_COMPRESS_METHOD_XZ: + set_end_of_stream = (zip->file_info.flag & MZ_ZIP_FLAG_LZMA_EOS_MARKER); + break; + case MZ_COMPRESS_METHOD_ZSTD: + set_end_of_stream = 1; + break; + } + + if (set_end_of_stream) { + mz_stream_set_prop_int64(zip->compress_stream, MZ_STREAM_PROP_TOTAL_IN_MAX, + zip->file_info.compressed_size); + mz_stream_set_prop_int64(zip->compress_stream, MZ_STREAM_PROP_TOTAL_OUT_MAX, + zip->file_info.uncompressed_size); + } + } + + mz_stream_set_base(zip->compress_stream, zip->crypt_stream); + + err = mz_stream_open(zip->compress_stream, NULL, zip->open_mode); + } + + if (err == MZ_OK) { + zip->entry_opened = 1; + zip->entry_crc32 = 0; + } else { + mz_zip_entry_close_int(zip); + } + + return err; +} + +int32_t mz_zip_entry_is_open(void *handle) { + mz_zip *zip = (mz_zip *)handle; + if (!zip) + return MZ_PARAM_ERROR; + if (zip->entry_opened == 0) + return MZ_EXIST_ERROR; + return MZ_OK; +} + +int32_t mz_zip_entry_read_open(void *handle, uint8_t raw, const char *password) { + mz_zip *zip = (mz_zip *)handle; + int32_t err = MZ_OK; + int32_t err_shift = MZ_OK; + +#if defined(MZ_ZIP_NO_ENCRYPTION) + if (password) + return MZ_SUPPORT_ERROR; +#endif + if (!zip || !zip->entry_scanned) + return MZ_PARAM_ERROR; + if ((zip->open_mode & MZ_OPEN_MODE_READ) == 0) + return MZ_PARAM_ERROR; + + mz_zip_print("Zip - Entry - Read open (raw %" PRId32 ")\n", raw); + + err = mz_zip_entry_seek_local_header(zip); + if (err == MZ_OK) + err = mz_zip_entry_read_header(zip->stream, 1, &zip->local_file_info, zip->local_file_info_stream); + + if (err == MZ_FORMAT_ERROR && zip->disk_offset_shift > 0) { + /* Perhaps we didn't compensated correctly for incorrect cd offset */ + err_shift = mz_stream_seek(zip->stream, zip->file_info.disk_offset, MZ_SEEK_SET); + if (err_shift == MZ_OK) + err_shift = mz_zip_entry_read_header(zip->stream, 1, &zip->local_file_info, zip->local_file_info_stream); + if (err_shift == MZ_OK) { + zip->disk_offset_shift = 0; + err = err_shift; + } + } + +#ifdef MZ_ZIP_NO_DECOMPRESSION + if (!raw && zip->file_info.compression_method != MZ_COMPRESS_METHOD_STORE) + err = MZ_SUPPORT_ERROR; +#endif + if (err == MZ_OK) + err = mz_zip_entry_open_int(zip, raw, 0, password); + + return err; +} + +int32_t mz_zip_entry_write_open(void *handle, const mz_zip_file *file_info, int16_t compress_level, uint8_t raw, + const char *password) { + mz_zip *zip = (mz_zip *)handle; + int64_t filename_pos = -1; + int64_t extrafield_pos = 0; + int64_t comment_pos = 0; + int64_t linkname_pos = 0; + int64_t disk_number = 0; + uint8_t is_dir = 0; + int32_t err = MZ_OK; + +#if defined(MZ_ZIP_NO_ENCRYPTION) + if (password) + return MZ_SUPPORT_ERROR; +#endif + if (!zip || !file_info || !file_info->filename) + return MZ_PARAM_ERROR; + + if (mz_zip_entry_is_open(zip) == MZ_OK) { + err = mz_zip_entry_close(zip); + if (err != MZ_OK) + return err; + } + + memcpy(&zip->file_info, file_info, sizeof(mz_zip_file)); + + mz_zip_print("Zip - Entry - Write open - %s (level %" PRId16 " raw %" PRId8 ")\n", zip->file_info.filename, + compress_level, raw); + + mz_stream_seek(zip->file_info_stream, 0, MZ_SEEK_SET); + mz_stream_write(zip->file_info_stream, file_info, sizeof(mz_zip_file)); + + /* Copy filename, extrafield, and comment internally */ + filename_pos = mz_stream_tell(zip->file_info_stream); + if (file_info->filename) + mz_stream_write(zip->file_info_stream, file_info->filename, (int32_t)strlen(file_info->filename)); + mz_stream_write_uint8(zip->file_info_stream, 0); + + extrafield_pos = mz_stream_tell(zip->file_info_stream); + if (file_info->extrafield) + mz_stream_write(zip->file_info_stream, file_info->extrafield, file_info->extrafield_size); + mz_stream_write_uint8(zip->file_info_stream, 0); + + comment_pos = mz_stream_tell(zip->file_info_stream); + if (file_info->comment) + mz_stream_write(zip->file_info_stream, file_info->comment, file_info->comment_size); + mz_stream_write_uint8(zip->file_info_stream, 0); + + linkname_pos = mz_stream_tell(zip->file_info_stream); + if (file_info->linkname) + mz_stream_write(zip->file_info_stream, file_info->linkname, (int32_t)strlen(file_info->linkname)); + mz_stream_write_uint8(zip->file_info_stream, 0); + + mz_stream_mem_get_buffer_at(zip->file_info_stream, filename_pos, (const void **)&zip->file_info.filename); + mz_stream_mem_get_buffer_at(zip->file_info_stream, extrafield_pos, (const void **)&zip->file_info.extrafield); + mz_stream_mem_get_buffer_at(zip->file_info_stream, comment_pos, (const void **)&zip->file_info.comment); + mz_stream_mem_get_buffer_at(zip->file_info_stream, linkname_pos, (const void **)&zip->file_info.linkname); + + if (zip->file_info.compression_method == MZ_COMPRESS_METHOD_DEFLATE) { + if ((compress_level == 8) || (compress_level == 9)) + zip->file_info.flag |= MZ_ZIP_FLAG_DEFLATE_MAX; + if (compress_level == 2) + zip->file_info.flag |= MZ_ZIP_FLAG_DEFLATE_FAST; + if (compress_level == 1) + zip->file_info.flag |= MZ_ZIP_FLAG_DEFLATE_SUPER_FAST; + } +#if defined(HAVE_LZMA) || defined(HAVE_LIBCOMP) + else if (zip->file_info.compression_method == MZ_COMPRESS_METHOD_LZMA || + zip->file_info.compression_method == MZ_COMPRESS_METHOD_XZ) + zip->file_info.flag |= MZ_ZIP_FLAG_LZMA_EOS_MARKER; +#endif + + if (mz_zip_attrib_is_dir(zip->file_info.external_fa, zip->file_info.version_madeby) == MZ_OK) + is_dir = 1; + + if (!raw && !is_dir) { + if (zip->data_descriptor) + zip->file_info.flag |= MZ_ZIP_FLAG_DATA_DESCRIPTOR; + if (password) + zip->file_info.flag |= MZ_ZIP_FLAG_ENCRYPTED; + } + + mz_stream_get_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, &disk_number); + zip->file_info.disk_number = (uint32_t)disk_number; + zip->file_info.disk_offset = mz_stream_tell(zip->stream); + + if (zip->file_info.flag & MZ_ZIP_FLAG_ENCRYPTED) { +#ifdef HAVE_PKCRYPT + /* Pre-calculated CRC value is required for PKWARE traditional encryption */ + uint32_t dos_date = mz_zip_time_t_to_dos_date(zip->file_info.modified_date); + zip->file_info.pk_verify = mz_zip_get_pk_verify(dos_date, zip->file_info.crc, zip->file_info.flag); +#endif +#ifdef HAVE_WZAES + if (zip->file_info.aes_version && zip->file_info.aes_strength == 0) + zip->file_info.aes_strength = MZ_AES_STRENGTH_256; +#endif + } + + zip->file_info.crc = 0; + zip->file_info.compressed_size = 0; + + if ((compress_level == 0) || (is_dir)) + zip->file_info.compression_method = MZ_COMPRESS_METHOD_STORE; + +#ifdef MZ_ZIP_NO_COMPRESSION + if (zip->file_info.compression_method != MZ_COMPRESS_METHOD_STORE) + err = MZ_SUPPORT_ERROR; +#endif + if (err == MZ_OK) + err = mz_zip_entry_write_header(zip->stream, 1, &zip->file_info); + if (err == MZ_OK) + err = mz_zip_entry_open_int(zip, raw, compress_level, password); + + return err; +} + +int32_t mz_zip_entry_read(void *handle, void *buf, int32_t len) { + mz_zip *zip = (mz_zip *)handle; + int32_t read = 0; + + if (!zip || mz_zip_entry_is_open(zip) != MZ_OK) + return MZ_PARAM_ERROR; + if (UINT_MAX == UINT16_MAX && len > UINT16_MAX) /* zlib limitation */ + return MZ_PARAM_ERROR; + if (len == 0) + return MZ_PARAM_ERROR; + + if (zip->file_info.compressed_size == 0) + return 0; + + /* Read entire entry even if uncompressed_size = 0, otherwise */ + /* aes encryption validation will fail if compressed_size > 0 */ + read = mz_stream_read(zip->compress_stream, buf, len); + if (read > 0) + zip->entry_crc32 = mz_crypt_crc32_update(zip->entry_crc32, buf, read); + + mz_zip_print("Zip - Entry - Read - %" PRId32 " (max %" PRId32 ")\n", read, len); + + return read; +} + +int32_t mz_zip_entry_write(void *handle, const void *buf, int32_t len) { + mz_zip *zip = (mz_zip *)handle; + int32_t written = 0; + + if (!zip || mz_zip_entry_is_open(zip) != MZ_OK) + return MZ_PARAM_ERROR; + written = mz_stream_write(zip->compress_stream, buf, len); + if (written > 0) + zip->entry_crc32 = mz_crypt_crc32_update(zip->entry_crc32, buf, written); + + mz_zip_print("Zip - Entry - Write - %" PRId32 " (max %" PRId32 ")\n", written, len); + return written; +} + +int32_t mz_zip_entry_read_close(void *handle, uint32_t *crc32, int64_t *compressed_size, int64_t *uncompressed_size) { + mz_zip *zip = (mz_zip *)handle; + int64_t total_in = 0; + int32_t err = MZ_OK; + uint8_t zip64 = 0; + + if (!zip || mz_zip_entry_is_open(zip) != MZ_OK) + return MZ_PARAM_ERROR; + + mz_stream_close(zip->compress_stream); + +#ifdef HAVE_WZAES + /* nszip: close the AES stream on read */ + if (!zip->entry_raw && zip->file_info.aes_version && (zip->file_info.flag & MZ_ZIP_FLAG_ENCRYPTED) && zip->crypt_stream) + err = mz_stream_close(zip->crypt_stream); +#endif + + mz_zip_print("Zip - Entry - Read Close\n"); + + if (crc32) + *crc32 = zip->file_info.crc; + if (compressed_size) + *compressed_size = zip->file_info.compressed_size; + if (uncompressed_size) + *uncompressed_size = zip->file_info.uncompressed_size; + + mz_stream_get_prop_int64(zip->compress_stream, MZ_STREAM_PROP_TOTAL_IN, &total_in); + + if ((zip->file_info.flag & MZ_ZIP_FLAG_DATA_DESCRIPTOR) && + ((zip->file_info.flag & MZ_ZIP_FLAG_MASK_LOCAL_INFO) == 0) && (crc32 || compressed_size || uncompressed_size)) { + /* Check to see if data descriptor is zip64 bit format or not */ + if (mz_zip_extrafield_contains(zip->local_file_info.extrafield, zip->local_file_info.extrafield_size, + MZ_ZIP_EXTENSION_ZIP64, NULL) == MZ_OK) { + zip64 = 1; + } + + err = mz_zip_entry_seek_local_header(zip); + + /* Seek to end of compressed stream since we might have over-read during decompression */ + if (err == MZ_OK) { + err = mz_stream_seek(zip->stream, + MZ_ZIP_SIZE_LD_ITEM + (int64_t)zip->local_file_info.filename_size + + (int64_t)zip->local_file_info.extrafield_size + total_in, + MZ_SEEK_CUR); + } + + /* Read data descriptor */ + if (err == MZ_OK) + err = mz_zip_entry_read_descriptor(zip->stream, zip64, crc32, compressed_size, uncompressed_size); + } + + /* If entire entry was not read verification will fail */ + if ((err == MZ_OK) && (total_in == zip->file_info.compressed_size) && (!zip->entry_raw)) { +#ifdef HAVE_WZAES + /* AES zip version AE-1 will expect a valid crc as well */ + if (zip->file_info.aes_version <= 0x0001) +#endif + { + if (zip->entry_crc32 != zip->file_info.crc) { + mz_zip_print("Zip - Entry - Crc failed (actual 0x%08" PRIx32 " expected 0x%08" PRIx32 ")\n", + zip->entry_crc32, zip->file_info.crc); + + err = MZ_CRC_ERROR; + } + } + } + + mz_zip_entry_close_int(zip); + + return err; +} + +int32_t mz_zip_entry_write_close(void *handle, uint32_t crc32, int64_t compressed_size, int64_t uncompressed_size) { + mz_zip *zip = (mz_zip *)handle; + int64_t end_disk_number = 0; + int32_t err = MZ_OK; + uint8_t zip64 = 0; + + if (!zip || mz_zip_entry_is_open(zip) != MZ_OK) + return MZ_PARAM_ERROR; + + mz_stream_close(zip->compress_stream); + + if (!zip->entry_raw) + crc32 = zip->entry_crc32; + + mz_zip_print("Zip - Entry - Write Close (crc 0x%08" PRIx32 " cs %" PRId64 " ucs %" PRId64 ")\n", crc32, + compressed_size, uncompressed_size); + + /* If sizes are not set, then read them from the compression stream */ + if (compressed_size < 0) + mz_stream_get_prop_int64(zip->compress_stream, MZ_STREAM_PROP_TOTAL_OUT, &compressed_size); + if (uncompressed_size < 0) + mz_stream_get_prop_int64(zip->compress_stream, MZ_STREAM_PROP_TOTAL_IN, &uncompressed_size); + + if (zip->file_info.flag & MZ_ZIP_FLAG_ENCRYPTED) { + mz_stream_set_base(zip->crypt_stream, zip->stream); + err = mz_stream_close(zip->crypt_stream); + + mz_stream_get_prop_int64(zip->crypt_stream, MZ_STREAM_PROP_TOTAL_OUT, &compressed_size); + } + + mz_zip_entry_needs_zip64(&zip->file_info, 1, &zip64); + + if ((err == MZ_OK) && (zip->file_info.flag & MZ_ZIP_FLAG_DATA_DESCRIPTOR)) { + /* Determine if we need to write data descriptor in zip64 format, + if local extrafield was saved with zip64 extrafield */ + + if (zip->file_info.flag & MZ_ZIP_FLAG_MASK_LOCAL_INFO) + err = mz_zip_entry_write_descriptor(zip->stream, zip64, 0, compressed_size, 0); + else + err = mz_zip_entry_write_descriptor(zip->stream, zip64, crc32, compressed_size, uncompressed_size); + } + + /* Write file info to central directory */ + + mz_zip_print("Zip - Entry - Write cd (ucs %" PRId64 " cs %" PRId64 " crc 0x%08" PRIx32 ")\n", uncompressed_size, + compressed_size, crc32); + + zip->file_info.crc = crc32; + zip->file_info.compressed_size = compressed_size; + zip->file_info.uncompressed_size = uncompressed_size; + + if (err == MZ_OK) + err = mz_zip_entry_write_header(zip->cd_mem_stream, 0, &zip->file_info); + + /* Update local header with crc32 and sizes */ + if ((err == MZ_OK) && ((zip->file_info.flag & MZ_ZIP_FLAG_DATA_DESCRIPTOR) == 0) && + ((zip->file_info.flag & MZ_ZIP_FLAG_MASK_LOCAL_INFO) == 0)) { + /* Save the disk number and position we are to seek back after updating local header */ + int64_t end_pos = mz_stream_tell(zip->stream); + mz_stream_get_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, &end_disk_number); + + err = mz_zip_entry_seek_local_header(zip); + + if (err == MZ_OK) { + /* Seek to crc32 and sizes offset in local header */ + mz_stream_set_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, zip->file_info.disk_number); + err = mz_stream_seek(zip->stream, zip->file_info.disk_offset + MZ_ZIP_OFFSET_CRC_SIZES, MZ_SEEK_SET); + } + + if (err == MZ_OK) + err = mz_zip_entry_write_crc_sizes(zip->stream, zip64, 0, &zip->file_info); + + /* Seek to and update zip64 extension sizes */ + if ((err == MZ_OK) && (zip64)) { + int64_t filename_size = zip->file_info.filename_size; + + if (filename_size == 0) + filename_size = strlen(zip->file_info.filename); + + /* Since we write zip64 extension first we know its offset */ + err = mz_stream_seek(zip->stream, 2 + 2 + filename_size + 4, MZ_SEEK_CUR); + + if (err == MZ_OK) + err = mz_stream_write_uint64(zip->stream, zip->file_info.uncompressed_size); + if (err == MZ_OK) + err = mz_stream_write_uint64(zip->stream, zip->file_info.compressed_size); + } + + mz_stream_set_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, end_disk_number); + mz_stream_seek(zip->stream, end_pos, MZ_SEEK_SET); + } + + zip->number_entry += 1; + + mz_zip_entry_close_int(zip); + + return err; +} + +int32_t mz_zip_entry_seek_local_header(void *handle) { + mz_zip *zip = (mz_zip *)handle; + int64_t disk_size = 0; + uint32_t disk_number = 0; + + if (!zip) + return MZ_PARAM_ERROR; + disk_number = zip->file_info.disk_number; + if (disk_number == zip->disk_number_with_cd) { + mz_stream_get_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_SIZE, &disk_size); + if ((disk_size == 0) || ((zip->open_mode & MZ_OPEN_MODE_WRITE) == 0)) + disk_number = (uint32_t)-1; + } + + mz_stream_set_prop_int64(zip->stream, MZ_STREAM_PROP_DISK_NUMBER, disk_number); + + mz_zip_print("Zip - Entry - Seek local (disk %" PRId32 " offset %" PRId64 ")\n", disk_number, + zip->file_info.disk_offset); + + /* Guard against seek overflows */ + if ((zip->disk_offset_shift > 0) && (zip->file_info.disk_offset > (INT64_MAX - zip->disk_offset_shift))) + return MZ_FORMAT_ERROR; + + return mz_stream_seek(zip->stream, zip->file_info.disk_offset + zip->disk_offset_shift, MZ_SEEK_SET); +} + +int32_t mz_zip_entry_get_compress_stream(void *handle, void **compress_stream) { + mz_zip *zip = (mz_zip *)handle; + if (!zip || !compress_stream) + return MZ_PARAM_ERROR; + *compress_stream = zip->compress_stream; + if (!*compress_stream) + return MZ_EXIST_ERROR; + return MZ_OK; +} + +int32_t mz_zip_entry_close(void *handle) { + return mz_zip_entry_close_raw(handle, UINT64_MAX, 0); +} + +int32_t mz_zip_entry_close_raw(void *handle, int64_t uncompressed_size, uint32_t crc32) { + mz_zip *zip = (mz_zip *)handle; + int32_t err = MZ_OK; + + if (!zip || mz_zip_entry_is_open(zip) != MZ_OK) + return MZ_PARAM_ERROR; + + if (zip->open_mode & MZ_OPEN_MODE_WRITE) + err = mz_zip_entry_write_close(zip, crc32, UINT64_MAX, uncompressed_size); + else + err = mz_zip_entry_read_close(zip, NULL, NULL, NULL); + + return err; +} + +int32_t mz_zip_entry_is_dir(void *handle) { + mz_zip *zip = (mz_zip *)handle; + int32_t filename_length = 0; + + if (!zip || !zip->entry_scanned) + return MZ_PARAM_ERROR; + if (mz_zip_attrib_is_dir(zip->file_info.external_fa, zip->file_info.version_madeby) == MZ_OK) + return MZ_OK; + + filename_length = (int32_t)strlen(zip->file_info.filename); + if (filename_length > 0) { + if (mz_os_is_dir_separator(zip->file_info.filename[filename_length - 1])) + return MZ_OK; + } + return MZ_EXIST_ERROR; +} + +int32_t mz_zip_entry_is_symlink(void *handle) { + mz_zip *zip = (mz_zip *)handle; + + if (!zip || !zip->entry_scanned) + return MZ_PARAM_ERROR; + if (mz_zip_attrib_is_symlink(zip->file_info.external_fa, zip->file_info.version_madeby) != MZ_OK) + return MZ_EXIST_ERROR; + + return MZ_OK; +} + +int32_t mz_zip_entry_get_info(void *handle, mz_zip_file **file_info) { + mz_zip *zip = (mz_zip *)handle; + + if (!zip) + return MZ_PARAM_ERROR; + + if ((zip->open_mode & MZ_OPEN_MODE_WRITE) == 0) { + if (!zip->entry_scanned) + return MZ_PARAM_ERROR; + } + + *file_info = &zip->file_info; + return MZ_OK; +} + +int32_t mz_zip_entry_get_local_info(void *handle, mz_zip_file **local_file_info) { + mz_zip *zip = (mz_zip *)handle; + if (!zip || mz_zip_entry_is_open(zip) != MZ_OK) + return MZ_PARAM_ERROR; + *local_file_info = &zip->local_file_info; + return MZ_OK; +} + +int32_t mz_zip_entry_set_extrafield(void *handle, const uint8_t *extrafield, uint16_t extrafield_size) { + mz_zip *zip = (mz_zip *)handle; + + if (!zip || mz_zip_entry_is_open(zip) != MZ_OK) + return MZ_PARAM_ERROR; + + zip->file_info.extrafield = extrafield; + zip->file_info.extrafield_size = extrafield_size; + return MZ_OK; +} + +static int32_t mz_zip_goto_next_entry_int(void *handle) { + mz_zip *zip = (mz_zip *)handle; + int32_t err = MZ_OK; + + if (!zip) + return MZ_PARAM_ERROR; + + zip->entry_scanned = 0; + + mz_stream_set_prop_int64(zip->cd_stream, MZ_STREAM_PROP_DISK_NUMBER, -1); + + err = mz_stream_seek(zip->cd_stream, zip->cd_current_pos, MZ_SEEK_SET); + if (err == MZ_OK) + err = mz_zip_entry_read_header(zip->cd_stream, 0, &zip->file_info, zip->file_info_stream); + if (err == MZ_OK) + zip->entry_scanned = 1; + return err; +} + +int64_t mz_zip_get_entry(void *handle) { + mz_zip *zip = (mz_zip *)handle; + + if (!zip) + return MZ_PARAM_ERROR; + + return zip->cd_current_pos; +} + +int32_t mz_zip_goto_entry(void *handle, int64_t cd_pos) { + mz_zip *zip = (mz_zip *)handle; + + if (!zip) + return MZ_PARAM_ERROR; + + if (cd_pos < zip->cd_start_pos || cd_pos > zip->cd_start_pos + zip->cd_size) + return MZ_PARAM_ERROR; + + zip->cd_current_pos = cd_pos; + + return mz_zip_goto_next_entry_int(zip); +} + +int32_t mz_zip_goto_first_entry(void *handle) { + mz_zip *zip = (mz_zip *)handle; + + if (!zip) + return MZ_PARAM_ERROR; + + zip->cd_current_pos = zip->cd_start_pos; + + return mz_zip_goto_next_entry_int(zip); +} + +int32_t mz_zip_goto_next_entry(void *handle) { + mz_zip *zip = (mz_zip *)handle; + + if (!zip) + return MZ_PARAM_ERROR; + + zip->cd_current_pos += (int64_t)MZ_ZIP_SIZE_CD_ITEM + zip->file_info.filename_size + + zip->file_info.extrafield_size + zip->file_info.comment_size; + + return mz_zip_goto_next_entry_int(zip); +} + +int32_t mz_zip_locate_entry(void *handle, const char *filename, uint8_t ignore_case) { + mz_zip *zip = (mz_zip *)handle; + int32_t err = MZ_OK; + int32_t result = 0; + + if (!zip || !filename) + return MZ_PARAM_ERROR; + + /* If we are already on the current entry, no need to search */ + if (zip->entry_scanned && zip->file_info.filename) { + result = mz_zip_path_compare(zip->file_info.filename, filename, ignore_case); + if (result == 0) + return MZ_OK; + } + + /* Search all entries starting at the first */ + err = mz_zip_goto_first_entry(zip); + while (err == MZ_OK) { + result = mz_zip_path_compare(zip->file_info.filename, filename, ignore_case); + if (result == 0) + return MZ_OK; + + err = mz_zip_goto_next_entry(zip); + } + + return err; +} + +int32_t mz_zip_locate_first_entry(void *handle, void *userdata, mz_zip_locate_entry_cb cb) { + mz_zip *zip = (mz_zip *)handle; + int32_t err = MZ_OK; + int32_t result = 0; + + /* Search first entry looking for match */ + err = mz_zip_goto_first_entry(zip); + if (err != MZ_OK) + return err; + + result = cb(zip, userdata, &zip->file_info); + if (result == 0) + return MZ_OK; + + return mz_zip_locate_next_entry(zip, userdata, cb); +} + +int32_t mz_zip_locate_next_entry(void *handle, void *userdata, mz_zip_locate_entry_cb cb) { + mz_zip *zip = (mz_zip *)handle; + int32_t err = MZ_OK; + int32_t result = 0; + + /* Search next entries looking for match */ + err = mz_zip_goto_next_entry(zip); + while (err == MZ_OK) { + result = cb(zip, userdata, &zip->file_info); + if (result == 0) + return MZ_OK; + + err = mz_zip_goto_next_entry(zip); + } + + return err; +} + +/***************************************************************************/ + +int32_t mz_zip_attrib_is_dir(uint32_t attrib, int32_t version_madeby) { + uint32_t posix_attrib = 0; + uint8_t system = MZ_HOST_SYSTEM(version_madeby); + int32_t err = MZ_OK; + + err = mz_zip_attrib_convert(system, attrib, MZ_HOST_SYSTEM_UNIX, &posix_attrib); + if (err == MZ_OK) { + if ((posix_attrib & 0170000) == 0040000) /* S_ISDIR */ + return MZ_OK; + } + + return MZ_EXIST_ERROR; +} + +int32_t mz_zip_attrib_is_symlink(uint32_t attrib, int32_t version_madeby) { + uint32_t posix_attrib = 0; + uint8_t system = MZ_HOST_SYSTEM(version_madeby); + int32_t err = MZ_OK; + + err = mz_zip_attrib_convert(system, attrib, MZ_HOST_SYSTEM_UNIX, &posix_attrib); + if (err == MZ_OK) { + if ((posix_attrib & 0170000) == 0120000) /* S_ISLNK */ + return MZ_OK; + } + + return MZ_EXIST_ERROR; +} + +int32_t mz_zip_attrib_convert(uint8_t src_sys, uint32_t src_attrib, uint8_t target_sys, uint32_t *target_attrib) { + if (!target_attrib) + return MZ_PARAM_ERROR; + + *target_attrib = 0; + + if ((src_sys == MZ_HOST_SYSTEM_MSDOS) || (src_sys == MZ_HOST_SYSTEM_WINDOWS_NTFS)) { + if ((target_sys == MZ_HOST_SYSTEM_MSDOS) || (target_sys == MZ_HOST_SYSTEM_WINDOWS_NTFS)) { + *target_attrib = src_attrib; + return MZ_OK; + } + if ((target_sys == MZ_HOST_SYSTEM_UNIX) || (target_sys == MZ_HOST_SYSTEM_OSX_DARWIN) || + (target_sys == MZ_HOST_SYSTEM_RISCOS)) + return mz_zip_attrib_win32_to_posix(src_attrib, target_attrib); + } else if ((src_sys == MZ_HOST_SYSTEM_UNIX) || (src_sys == MZ_HOST_SYSTEM_OSX_DARWIN) || + (src_sys == MZ_HOST_SYSTEM_RISCOS)) { + /* If high bytes are set, it contains unix specific attributes */ + if ((src_attrib >> 16) != 0) + src_attrib >>= 16; + + if ((target_sys == MZ_HOST_SYSTEM_UNIX) || (target_sys == MZ_HOST_SYSTEM_OSX_DARWIN) || + (target_sys == MZ_HOST_SYSTEM_RISCOS)) { + *target_attrib = src_attrib; + return MZ_OK; + } + if ((target_sys == MZ_HOST_SYSTEM_MSDOS) || (target_sys == MZ_HOST_SYSTEM_WINDOWS_NTFS)) + return mz_zip_attrib_posix_to_win32(src_attrib, target_attrib); + } + + return MZ_SUPPORT_ERROR; +} + +int32_t mz_zip_attrib_posix_to_win32(uint32_t posix_attrib, uint32_t *win32_attrib) { + if (!win32_attrib) + return MZ_PARAM_ERROR; + + *win32_attrib = 0; + + /* S_IWUSR | S_IWGRP | S_IWOTH | S_IXUSR | S_IXGRP | S_IXOTH */ + if ((posix_attrib & 0000333) == 0 && (posix_attrib & 0000444) != 0) + *win32_attrib |= 0x01; /* FILE_ATTRIBUTE_READONLY */ + /* S_IFLNK */ + if ((posix_attrib & 0170000) == 0120000) + *win32_attrib |= 0x400; /* FILE_ATTRIBUTE_REPARSE_POINT */ + /* S_IFDIR */ + else if ((posix_attrib & 0170000) == 0040000) + *win32_attrib |= 0x10; /* FILE_ATTRIBUTE_DIRECTORY */ + /* S_IFREG */ + else + *win32_attrib |= 0x80; /* FILE_ATTRIBUTE_NORMAL */ + + return MZ_OK; +} + +int32_t mz_zip_attrib_win32_to_posix(uint32_t win32_attrib, uint32_t *posix_attrib) { + if (!posix_attrib) + return MZ_PARAM_ERROR; + + *posix_attrib = 0000444; /* S_IRUSR | S_IRGRP | S_IROTH */ + /* FILE_ATTRIBUTE_READONLY */ + if ((win32_attrib & 0x01) == 0) + *posix_attrib |= 0000222; /* S_IWUSR | S_IWGRP | S_IWOTH */ + /* FILE_ATTRIBUTE_REPARSE_POINT */ + if ((win32_attrib & 0x400) == 0x400) + *posix_attrib |= 0120000; /* S_IFLNK */ + /* FILE_ATTRIBUTE_DIRECTORY */ + else if ((win32_attrib & 0x10) == 0x10) + *posix_attrib |= 0040111; /* S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH */ + else + *posix_attrib |= 0100000; /* S_IFREG */ + + return MZ_OK; +} + +/***************************************************************************/ + +int32_t mz_zip_extrafield_find(void *stream, uint16_t type, int32_t max_seek, uint16_t *length) { + int32_t err = MZ_OK; + uint16_t field_type = 0; + uint16_t field_length = 0; + + if (max_seek < 4) + return MZ_EXIST_ERROR; + + do { + err = mz_stream_read_uint16(stream, &field_type); + if (err == MZ_OK) + err = mz_stream_read_uint16(stream, &field_length); + if (err != MZ_OK) + break; + + if (type == field_type) { + if (length) + *length = field_length; + return MZ_OK; + } + + max_seek -= field_length - 4; + if (max_seek < 0) + return MZ_EXIST_ERROR; + + err = mz_stream_seek(stream, field_length, MZ_SEEK_CUR); + } while (err == MZ_OK); + + return MZ_EXIST_ERROR; +} + +int32_t mz_zip_extrafield_contains(const uint8_t *extrafield, int32_t extrafield_size, uint16_t type, + uint16_t *length) { + void *file_extra_stream = NULL; + int32_t err = MZ_OK; + + if (!extrafield || !extrafield_size) + return MZ_PARAM_ERROR; + + file_extra_stream = mz_stream_mem_create(); + if (!file_extra_stream) + return MZ_MEM_ERROR; + + mz_stream_mem_set_buffer(file_extra_stream, (void *)extrafield, extrafield_size); + err = mz_zip_extrafield_find(file_extra_stream, type, extrafield_size, length); + mz_stream_mem_delete(&file_extra_stream); + return err; +} + +int32_t mz_zip_extrafield_read(void *stream, uint16_t *type, uint16_t *length) { + int32_t err = MZ_OK; + if (!type || !length) + return MZ_PARAM_ERROR; + err = mz_stream_read_uint16(stream, type); + if (err == MZ_OK) + err = mz_stream_read_uint16(stream, length); + return err; +} + +int32_t mz_zip_extrafield_write(void *stream, uint16_t type, uint16_t length) { + int32_t err = MZ_OK; + err = mz_stream_write_uint16(stream, type); + if (err == MZ_OK) + err = mz_stream_write_uint16(stream, length); + return err; +} + +/***************************************************************************/ + +/** + This is mostly validating a given time struct is convertible to and from a valid dos date. + This does not however perform a calendar validation (like February 31). + */ +static int32_t mz_zip_invalid_date(const struct tm *ptm) { +#define datevalue_in_range(min, max, value) ((min) <= (value) && (value) <= (max)) + return (!datevalue_in_range(0, 127 + 80, ptm->tm_year) || /* 1980-based year, allow 80 extra */ + !datevalue_in_range(0, 11, ptm->tm_mon) || /* allowing months 0-11 in a year */ + !datevalue_in_range(1, 31, ptm->tm_mday) || /* allowing days 1-31 in a month */ + !datevalue_in_range(0, 23, ptm->tm_hour) || /* allowing hours 0-23 in a day */ + !datevalue_in_range(0, 59, ptm->tm_min) || /* allowing minutes 0-59 in an hour */ + !datevalue_in_range(0, 59, ptm->tm_sec)); /* allowing seconds 0-59 in a minute */ +#undef datevalue_in_range +} + +/** + Returns a year-1900 indexed time struct. + This may produce slightly out-of-range months, hours, minutes, seconds. + This may also produce invalid calendar days (like the 31st of February). + */ +static void mz_zip_dosdate_to_raw_tm(uint64_t dos_date, struct tm *ptm) { + uint64_t date = (uint64_t)(dos_date >> 16); + + ptm->tm_year = (int16_t)(((date & 0x0FE00) / 0x0200) + 80); /* result in 80..207 */ + ptm->tm_mon = (int16_t)(((date & 0x1E0) / 0x20) - 1); /* result in -1..14 */ + ptm->tm_mday = (int16_t)(date & 0x1f); /* result in 0..31 */ + ptm->tm_hour = (int16_t)((dos_date & 0xF800) / 0x800); /* result in 0..31 */ + ptm->tm_min = (int16_t)((dos_date & 0x7E0) / 0x20); /* result in 0..63 */ + ptm->tm_sec = (int16_t)(2 * (dos_date & 0x1f)); /* result in 0..62 */ + ptm->tm_isdst = -1; +} + +int32_t mz_zip_dosdate_to_tm(uint64_t dos_date, struct tm *ptm) { + if (!ptm) + return MZ_PARAM_ERROR; + + mz_zip_dosdate_to_raw_tm(dos_date, ptm); + + if (mz_zip_invalid_date(ptm)) { + /* Invalid date stored, so don't return it */ + memset(ptm, 0, sizeof(struct tm)); + return MZ_FORMAT_ERROR; + } + return MZ_OK; +} + +time_t mz_zip_dosdate_to_time_t(uint64_t dos_date) { + struct tm ptm; + mz_zip_dosdate_to_raw_tm(dos_date, &ptm); + return mz_zip_tm_to_time_t(&ptm); +} + +time_t mz_zip_tm_to_time_t(struct tm *ptm) { + // `ptm->tm_year` must be 1900-indexed. + return mktime(ptm); +} + +int32_t mz_zip_time_t_to_tm(time_t unix_time, struct tm *ptm) { + struct tm ltm; + if (!ptm) + return MZ_PARAM_ERROR; + if (!localtime_r(&unix_time, <m)) { /* Returns a 1900-based year */ + /* Invalid date stored, so don't return it */ + memset(ptm, 0, sizeof(struct tm)); + return MZ_INTERNAL_ERROR; + } + memcpy(ptm, <m, sizeof(struct tm)); + return MZ_OK; +} + +uint32_t mz_zip_time_t_to_dos_date(time_t unix_time) { + struct tm ptm; + mz_zip_time_t_to_tm(unix_time, &ptm); + return mz_zip_tm_to_dosdate((const struct tm *)&ptm); +} + +uint32_t mz_zip_tm_to_dosdate(const struct tm *ptm) { + struct tm fixed_tm; + + /* Years supported: */ + + /* [00, 79] (assumed to be between 2000 and 2079) */ + /* [80, 207] (assumed to be between 1980 and 2107, typical output of old */ + /* software that does 'year-1900' to get a double digit year) */ + /* [1980, 2107] (due to format limitations, only years 1980-2107 can be stored.) */ + + memcpy(&fixed_tm, ptm, sizeof(struct tm)); + if (fixed_tm.tm_year >= 1980) /* range [1980, 2107] */ + fixed_tm.tm_year -= 1980; + else if (fixed_tm.tm_year >= 80) /* range [80, 207] */ + fixed_tm.tm_year -= 80; + else /* range [00, 79] */ + fixed_tm.tm_year += 20; + + if (mz_zip_invalid_date(&fixed_tm)) + return 0; + + return (((uint32_t)fixed_tm.tm_mday + (32 * ((uint32_t)fixed_tm.tm_mon + 1)) + (512 * (uint32_t)fixed_tm.tm_year)) + << 16) | + (((uint32_t)fixed_tm.tm_sec / 2) + (32 * (uint32_t)fixed_tm.tm_min) + (2048 * (uint32_t)fixed_tm.tm_hour)); +} + +int32_t mz_zip_ntfs_to_unix_time(uint64_t ntfs_time, time_t *unix_time) { + *unix_time = (time_t)((ntfs_time - 116444736000000000LL) / 10000000); + return MZ_OK; +} + +int32_t mz_zip_unix_to_ntfs_time(time_t unix_time, uint64_t *ntfs_time) { + *ntfs_time = ((uint64_t)unix_time * 10000000) + 116444736000000000LL; + return MZ_OK; +} + +/***************************************************************************/ + +int32_t mz_zip_path_compare(const char *path1, const char *path2, uint8_t ignore_case) { + do { + if ((*path1 == '\\' && *path2 == '/') || (*path2 == '\\' && *path1 == '/')) { + /* Ignore comparison of path slashes */ + } else if (ignore_case) { + if (tolower(*path1) != tolower(*path2)) + break; + } else if (*path1 != *path2) { + break; + } + + path1 += 1; + path2 += 1; + } while (*path1 != 0 && *path2 != 0); + + if (ignore_case) + return (int32_t)(tolower(*path1) - tolower(*path2)); + + return (int32_t)(*path1 - *path2); +} + +/***************************************************************************/ + +const char *mz_zip_get_compression_method_string(int32_t compression_method) { + const char *method = "?"; + switch (compression_method) { + case MZ_COMPRESS_METHOD_STORE: + method = "stored"; + break; + case MZ_COMPRESS_METHOD_DEFLATE: + method = "deflate"; + break; + case MZ_COMPRESS_METHOD_BZIP2: + method = "bzip2"; + break; + case MZ_COMPRESS_METHOD_LZMA: + method = "lzma"; + break; + case MZ_COMPRESS_METHOD_XZ: + method = "xz"; + break; + case MZ_COMPRESS_METHOD_ZSTD: + method = "zstd"; + break; + } + return method; +} + +/***************************************************************************/ diff --git a/packages/zip/platforms/linux/src/minizip-ng/mz_zip.h b/packages/zip/platforms/linux/src/minizip-ng/mz_zip.h new file mode 100644 index 00000000..dd1269c7 --- /dev/null +++ b/packages/zip/platforms/linux/src/minizip-ng/mz_zip.h @@ -0,0 +1,257 @@ +/* mz_zip.h -- Zip manipulation + part of the minizip-ng project + + Copyright (C) Nathan Moinvaziri + https://github.com/zlib-ng/minizip-ng + Copyright (C) 2009-2010 Mathias Svensson + Modifications for Zip64 support + http://result42.com + Copyright (C) 1998-2010 Gilles Vollant + https://www.winimage.com/zLibDll/minizip.html + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#ifndef MZ_ZIP_H +#define MZ_ZIP_H + +#ifdef __cplusplus +extern "C" { +#endif + +/***************************************************************************/ + +typedef struct mz_zip_file_s { + uint16_t version_madeby; /* version made by */ + uint16_t version_needed; /* version needed to extract */ + uint16_t flag; /* general purpose bit flag */ + uint16_t compression_method; /* compression method */ + time_t modified_date; /* last modified date in unix time */ + time_t accessed_date; /* last accessed date in unix time */ + time_t creation_date; /* creation date in unix time */ + uint32_t crc; /* crc-32 */ + int64_t compressed_size; /* compressed size */ + int64_t uncompressed_size; /* uncompressed size */ + uint16_t filename_size; /* filename length */ + uint16_t extrafield_size; /* extra field length */ + uint16_t comment_size; /* file comment length */ + uint32_t disk_number; /* disk number start */ + int64_t disk_offset; /* relative offset of local header */ + uint16_t internal_fa; /* internal file attributes */ + uint32_t external_fa; /* external file attributes */ + const char *filename; /* filename utf8 null-terminated string */ + const uint8_t *extrafield; /* extrafield data */ + const char *comment; /* comment utf8 null-terminated string */ + const char *linkname; /* sym-link filename utf8 null-terminated string */ + uint16_t zip64; /* zip64 extension mode */ + uint16_t aes_version; /* winzip aes extension if not 0 */ + uint8_t aes_strength; /* winzip aes encryption strength */ + uint16_t pk_verify; /* pkware encryption verifier */ +} mz_zip_file, mz_zip_entry; + +/***************************************************************************/ + +typedef int32_t (*mz_zip_locate_entry_cb)(void *handle, void *userdata, mz_zip_file *file_info); + +/***************************************************************************/ + +void *mz_zip_create(void); +/* Create zip instance for opening */ + +void mz_zip_delete(void **handle); +/* Delete zip object */ + +int32_t mz_zip_open(void *handle, void *stream, int32_t mode); +/* Create a zip file, no delete file in zip functionality */ + +int32_t mz_zip_close(void *handle); +/* Close the zip file */ + +int32_t mz_zip_get_comment(void *handle, const char **comment); +/* Get a pointer to the global comment */ + +int32_t mz_zip_set_comment(void *handle, const char *comment); +/* Sets the global comment used for writing zip file */ + +int32_t mz_zip_get_version_madeby(void *handle, uint16_t *version_madeby); +/* Get the version made by */ + +int32_t mz_zip_set_version_madeby(void *handle, uint16_t version_madeby); +/* Sets the version made by used for writing zip file */ + +int32_t mz_zip_set_recover(void *handle, uint8_t recover); +/* Sets the ability to recover the central dir by reading local file headers */ + +int32_t mz_zip_set_data_descriptor(void *handle, uint8_t data_descriptor); +/* Sets the use of data descriptor flag when writing zip entries */ + +int32_t mz_zip_get_stream(void *handle, void **stream); +/* Get a pointer to the stream used to open */ + +int32_t mz_zip_set_cd_stream(void *handle, int64_t cd_start_pos, void *cd_stream); +/* Sets the stream to use for reading the central dir */ + +int32_t mz_zip_get_cd_mem_stream(void *handle, void **cd_mem_stream); +/* Get a pointer to the stream used to store the central dir in memory */ + +int32_t mz_zip_set_number_entry(void *handle, uint64_t number_entry); +/* Sets the total number of entries */ + +int32_t mz_zip_get_number_entry(void *handle, uint64_t *number_entry); +/* Get the total number of entries */ + +int32_t mz_zip_set_disk_number_with_cd(void *handle, uint32_t disk_number_with_cd); +/* Sets the disk number containing the central directory record */ + +int32_t mz_zip_get_disk_number_with_cd(void *handle, uint32_t *disk_number_with_cd); +/* Get the disk number containing the central directory record */ + +/***************************************************************************/ + +int32_t mz_zip_entry_is_open(void *handle); +/* Check to see if entry is open for read/write */ + +int32_t mz_zip_entry_read_open(void *handle, uint8_t raw, const char *password); +/* Open for reading the current file in the zip file */ + +int32_t mz_zip_entry_read(void *handle, void *buf, int32_t len); +/* Read bytes from the current file in the zip file */ + +int32_t mz_zip_entry_read_close(void *handle, uint32_t *crc32, int64_t *compressed_size, int64_t *uncompressed_size); +/* Close the current file for reading and get data descriptor values */ + +int32_t mz_zip_entry_write_open(void *handle, const mz_zip_file *file_info, int16_t compress_level, uint8_t raw, + const char *password); +/* Open for writing the current file in the zip file */ + +int32_t mz_zip_entry_write(void *handle, const void *buf, int32_t len); +/* Write bytes from the current file in the zip file */ + +int32_t mz_zip_entry_write_close(void *handle, uint32_t crc32, int64_t compressed_size, int64_t uncompressed_size); +/* Close the current file for writing and set data descriptor values */ + +int32_t mz_zip_entry_seek_local_header(void *handle); +/* Seeks to the local header for the entry */ + +int32_t mz_zip_entry_get_compress_stream(void *handle, void **compress_stream); +/* Get a pointer to the compression stream used for the current entry */ + +int32_t mz_zip_entry_close_raw(void *handle, int64_t uncompressed_size, uint32_t crc32); +/* Close the current file in the zip file where raw is compressed data */ + +int32_t mz_zip_entry_close(void *handle); +/* Close the current file in the zip file */ + +/***************************************************************************/ + +int32_t mz_zip_entry_is_dir(void *handle); +/* Checks to see if the entry is a directory */ + +int32_t mz_zip_entry_is_symlink(void *handle); +/* Checks to see if the entry is a symbolic link */ + +int32_t mz_zip_entry_get_info(void *handle, mz_zip_file **file_info); +/* Get info about the current file, only valid while current entry is open */ + +int32_t mz_zip_entry_get_local_info(void *handle, mz_zip_file **local_file_info); +/* Get local info about the current file, only valid while current entry is being read */ + +int32_t mz_zip_entry_set_extrafield(void *handle, const uint8_t *extrafield, uint16_t extrafield_size); +/* Sets or updates the extra field for the entry to be used before writing cd */ + +int64_t mz_zip_get_entry(void *handle); +/* Return offset of the current entry in the zip file */ + +int32_t mz_zip_goto_entry(void *handle, int64_t cd_pos); +/* Go to specified entry in the zip file */ + +int32_t mz_zip_goto_first_entry(void *handle); +/* Go to the first entry in the zip file */ + +int32_t mz_zip_goto_next_entry(void *handle); +/* Go to the next entry in the zip file or MZ_END_OF_LIST if reaching the end */ + +int32_t mz_zip_locate_entry(void *handle, const char *filename, uint8_t ignore_case); +/* Locate the file with the specified name in the zip file or MZ_END_LIST if not found */ + +int32_t mz_zip_locate_first_entry(void *handle, void *userdata, mz_zip_locate_entry_cb cb); +/* Locate the first matching entry based on a match callback */ + +int32_t mz_zip_locate_next_entry(void *handle, void *userdata, mz_zip_locate_entry_cb cb); +/* Locate the next matching entry based on a match callback */ + +/***************************************************************************/ + +int32_t mz_zip_attrib_is_dir(uint32_t attrib, int32_t version_madeby); +/* Checks to see if the attribute is a directory based on platform */ + +int32_t mz_zip_attrib_is_symlink(uint32_t attrib, int32_t version_madeby); +/* Checks to see if the attribute is a symbolic link based on platform */ + +int32_t mz_zip_attrib_convert(uint8_t src_sys, uint32_t src_attrib, uint8_t target_sys, uint32_t *target_attrib); +/* Converts file attributes from one host system to another */ + +int32_t mz_zip_attrib_posix_to_win32(uint32_t posix_attrib, uint32_t *win32_attrib); +/* Converts posix file attributes to win32 file attributes */ + +int32_t mz_zip_attrib_win32_to_posix(uint32_t win32_attrib, uint32_t *posix_attrib); +/* Converts win32 file attributes to posix file attributes */ + +/***************************************************************************/ + +int32_t mz_zip_extrafield_find(void *stream, uint16_t type, int32_t max_seek, uint16_t *length); +/* Seeks to extra field by its type and returns its length */ + +int32_t mz_zip_extrafield_contains(const uint8_t *extrafield, int32_t extrafield_size, uint16_t type, uint16_t *length); +/* Gets whether an extrafield exists and its size */ + +int32_t mz_zip_extrafield_read(void *stream, uint16_t *type, uint16_t *length); +/* Reads an extrafield header from a stream */ + +int32_t mz_zip_extrafield_write(void *stream, uint16_t type, uint16_t length); +/* Writes an extrafield header to a stream */ + +/***************************************************************************/ + +int32_t mz_zip_dosdate_to_tm(uint64_t dos_date, struct tm *ptm); +/* Convert dos date/time format to struct tm */ + +time_t mz_zip_dosdate_to_time_t(uint64_t dos_date); +/* Convert dos date/time format to time_t */ + +time_t mz_zip_tm_to_time_t(struct tm *ptm); +/* Convert year-1900 indexed time struct to time_t */ + +int32_t mz_zip_time_t_to_tm(time_t unix_time, struct tm *ptm); +/* Convert time_t to time struct */ + +uint32_t mz_zip_time_t_to_dos_date(time_t unix_time); +/* Convert time_t to dos date/time format */ + +uint32_t mz_zip_tm_to_dosdate(const struct tm *ptm); +/* Convert struct tm to dos date/time format */ + +int32_t mz_zip_ntfs_to_unix_time(uint64_t ntfs_time, time_t *unix_time); +/* Convert ntfs time to unix time */ + +int32_t mz_zip_unix_to_ntfs_time(time_t unix_time, uint64_t *ntfs_time); +/* Convert unix time to ntfs time */ + +/***************************************************************************/ + +int32_t mz_zip_path_compare(const char *path1, const char *path2, uint8_t ignore_case); +/* Compare two paths without regard to slashes */ + +/***************************************************************************/ + +const char *mz_zip_get_compression_method_string(int32_t compression_method); +/* Gets a string representing the compression method */ + +/***************************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif /* _ZIP_H */ diff --git a/packages/zip/platforms/linux/src/nszip.c b/packages/zip/platforms/linux/src/nszip.c new file mode 100644 index 00000000..0ee64f3c --- /dev/null +++ b/packages/zip/platforms/linux/src/nszip.c @@ -0,0 +1,818 @@ +#define _FILE_OFFSET_BITS 64 +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include "nszip.h" +#include "nszip_internal.h" + +#include "minizip-ng/mz.h" +#include "minizip-ng/mz_os.h" +#include "minizip-ng/mz_strm.h" +#include "minizip-ng/mz_strm_os.h" +#include "minizip-ng/mz_zip.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CHUNK (256 * 1024) +#define EXTRA_UNICODE_PATH 0x7075 +#define MADE_BY_UNIX ((MZ_HOST_SYSTEM_UNIX << 8) | 63) + +uint64_t nszip_zip64_threshold = 0xffffffffull; + +static uint16_t get16(const uint8_t *p) { return (uint16_t)(p[0] | (p[1] << 8)); } +static uint32_t get32(const uint8_t *p) { return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); } + +typedef struct { + nszip_progress_fn progress; + void *user; + uint64_t total; + uint64_t done; + int last; + gboolean reporting; + char *error; + uint64_t length; +} Ctx; + +static gboolean fail(Ctx *c, const char *fmt, ...) G_GNUC_PRINTF(2, 3); + +static gboolean fail(Ctx *c, const char *fmt, ...) { + if (!c->error) { + va_list ap; + va_start(ap, fmt); + c->error = g_strdup_vprintf(fmt, ap); + va_end(ap); + } + return FALSE; +} + +static uint64_t sat_add(uint64_t a, uint64_t b) { return a > UINT64_MAX - b ? UINT64_MAX : a + b; } + +static mode_t current_umask(void) { + static gsize once = 0; + static mode_t mask = 022; + if (g_once_init_enter(&once)) { + gchar *status = NULL; + if (g_file_get_contents("/proc/self/status", &status, NULL, NULL)) { + const char *line = strstr(status, "\nUmask:"); + if (line) { + mask = (mode_t)(strtoul(line + 8, NULL, 8) & 0777); + } + g_free(status); + } + g_once_init_leave(&once, 1); + } + return mask; +} + +static void advance(Ctx *c, uint64_t units) { + if (!c->reporting) { + return; + } + c->done = sat_add(c->done, units); + int percent = c->total ? (int)MIN(100.0, (double)c->done * 100.0 / (double)c->total) : 100; + if (percent > c->last) { + c->last = percent; + if (c->progress) { + c->progress(percent, c->user); + } + } +} + +static FILE *open_regular(const char *path, struct stat *out_st) { + int fd = open(path, O_RDONLY | O_CLOEXEC | O_NOCTTY | O_NONBLOCK); + if (fd < 0) { + return NULL; + } + struct stat st; + if (fstat(fd, &st) != 0 || !S_ISREG(st.st_mode)) { + close(fd); + errno = EINVAL; + return NULL; + } + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK); + if (out_st) { + *out_st = st; + } + FILE *f = fdopen(fd, "rb"); + if (!f) { + close(fd); + } + return f; +} + +static const char *CP437_HIGH = "ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ "; + +static char *decode_name(const uint8_t *bytes, size_t len) { + if (g_utf8_validate((const char *)bytes, (gssize)len, NULL)) { + return g_strndup((const char *)bytes, len); + } + GString *s = g_string_sized_new(len * 2); + for (size_t i = 0; i < len; i++) { + if (bytes[i] < 0x80) { + g_string_append_c(s, (char)bytes[i]); + } else { + const char *p = g_utf8_offset_to_pointer(CP437_HIGH, bytes[i] - 0x80); + g_string_append_len(s, p, g_utf8_next_char(p) - p); + } + } + return g_string_free(s, FALSE); +} + +typedef struct { + char *name; + char *path; + gboolean dir; + uint64_t size; + time_t mtime; + uint32_t mode; + dev_t dev; + ino_t ino; +} Source; + +static void source_free(gpointer p) { + Source *s = p; + g_free(s->name); + g_free(s->path); + g_free(s); +} + +static gint compare_names(gconstpointer a, gconstpointer b) { return strcmp(*(char *const *)a, *(char *const *)b); } + +static gboolean inside(const char *root, const char *path) { + size_t n = strlen(root); + return strncmp(path, root, n) == 0 && (path[n] == '/' || (n > 0 && root[n - 1] == '/')); +} + +static gboolean collect(Ctx *c, GPtrArray *out, const char *root, const char *dir, const char *prefix) { + DIR *d = opendir(dir); + if (!d) { + return fail(c, "Cannot read directory %s: %s", dir, g_strerror(errno)); + } + GPtrArray *names = g_ptr_array_new_with_free_func(g_free); + struct dirent *e; + while ((e = readdir(d))) { + if (strcmp(e->d_name, ".") != 0 && strcmp(e->d_name, "..") != 0) { + g_ptr_array_add(names, g_strdup(e->d_name)); + } + } + closedir(d); + g_ptr_array_sort(names, compare_names); + + gboolean ok = TRUE; + for (guint i = 0; ok && i < names->len; i++) { + const char *name = g_ptr_array_index(names, i); + char *path = g_build_filename(dir, name, NULL); + struct stat st; + if (lstat(path, &st) != 0) { + ok = fail(c, "Cannot stat %s: %s", path, g_strerror(errno)); + g_free(path); + break; + } + if (S_ISLNK(st.st_mode)) { + char *resolved = realpath(path, NULL); + gboolean usable = resolved && inside(root, resolved) && stat(resolved, &st) == 0 && S_ISREG(st.st_mode); + free(resolved); + if (!usable) { + g_free(path); + continue; + } + } + if (S_ISDIR(st.st_mode)) { + Source *s = g_new0(Source, 1); + s->name = g_strconcat(prefix, name, "/", NULL); + s->dir = TRUE; + s->mtime = st.st_mtime; + s->mode = st.st_mode; + g_ptr_array_add(out, s); + ok = collect(c, out, root, path, s->name); + } else if (S_ISREG(st.st_mode)) { + Source *s = g_new0(Source, 1); + s->name = g_strconcat(prefix, name, NULL); + s->path = g_strdup(path); + s->size = (uint64_t)st.st_size; + s->mtime = st.st_mtime; + s->mode = st.st_mode; + s->dev = st.st_dev; + s->ino = st.st_ino; + g_ptr_array_add(out, s); + } + g_free(path); + } + g_ptr_array_unref(names); + return ok; +} + +static char **safe_segments(const char *name) { + char *normalized = g_strdup(name); + g_strdelimit(normalized, "\\", '/'); + if (normalized[0] == '/' || (g_ascii_isalpha(normalized[0]) && normalized[1] == ':')) { + g_free(normalized); + return NULL; + } + char **parts = g_strsplit(normalized, "/", -1); + g_free(normalized); + GPtrArray *kept = g_ptr_array_new(); + for (char **p = parts; *p; p++) { + if (!**p || strcmp(*p, ".") == 0) { + continue; + } + if (strcmp(*p, "..") == 0) { + g_ptr_array_unref(kept); + g_strfreev(parts); + return NULL; + } + g_ptr_array_add(kept, g_strdup(*p)); + } + g_strfreev(parts); + g_ptr_array_add(kept, NULL); + return (char **)g_ptr_array_free(kept, FALSE); +} + +static int ensure_directories(Ctx *c, const char *destination, char **segments, guint count) { + char *path = g_strdup(destination); + int result = 1; + for (guint i = 0; i < count; i++) { + char *next = g_build_filename(path, segments[i], NULL); + g_free(path); + path = next; + struct stat st; + if (lstat(path, &st) != 0) { + if (errno != ENOENT || (mkdir(path, 0755) != 0 && errno != EEXIST) || lstat(path, &st) != 0) { + fail(c, "Cannot create %s: %s", path, g_strerror(errno)); + result = 0; + break; + } + } + if (S_ISLNK(st.st_mode) || !S_ISDIR(st.st_mode)) { + result = -1; + break; + } + } + g_free(path); + return result; +} + +static const char *mz_message(int32_t err) { + switch (err) { + case MZ_PASSWORD_ERROR: + return "Wrong password"; + case MZ_CRC_ERROR: + return "Integrity check failed"; + case MZ_FORMAT_ERROR: + return "Not a valid zip archive"; + case MZ_SUPPORT_ERROR: + case MZ_PARAM_ERROR: + return "Unsupported zip entry"; + case MZ_MEM_ERROR: + return "Out of memory"; + case MZ_OPEN_ERROR: + return "Cannot open file"; + case MZ_WRITE_ERROR: + return "Write failed"; + default: + return "Corrupt or truncated data"; + } +} + +static gboolean add_entry(Ctx *c, void *zip, const Source *s, const char *password, uint8_t *buf) { + gboolean encrypt = !s->dir && password && *password; + mz_zip_file info; + memset(&info, 0, sizeof info); + info.version_madeby = MADE_BY_UNIX; + info.flag = MZ_ZIP_FLAG_UTF8 | (encrypt ? MZ_ZIP_FLAG_ENCRYPTED : 0); + info.compression_method = s->dir || !s->size ? MZ_COMPRESS_METHOD_STORE : MZ_COMPRESS_METHOD_DEFLATE; + info.modified_date = s->mtime; + info.filename = s->name; + info.uncompressed_size = (int64_t)s->size; + info.external_fa = (s->mode & (S_IFMT | 0777)) << 16 | (s->dir ? 0x10 : 0); + info.zip64 = s->size >= nszip_zip64_threshold - MIN(nszip_zip64_threshold, 65536) ? MZ_ZIP64_FORCE : MZ_ZIP64_AUTO; + if (encrypt) { + info.aes_version = MZ_AES_VERSION; + info.aes_strength = MZ_AES_STRENGTH_256; + } + + FILE *src = NULL; + if (!s->dir) { + struct stat opened; + src = open_regular(s->path, &opened); + if (src && (opened.st_dev != s->dev || opened.st_ino != s->ino)) { + fclose(src); + src = NULL; + errno = ESTALE; + } + if (!src) { + return fail(c, "Cannot open %s: %s", s->path, g_strerror(errno)); + } + } + + int32_t err = mz_zip_entry_write_open(zip, &info, MZ_COMPRESS_LEVEL_DEFAULT, 0, encrypt ? password : NULL); + if (err != MZ_OK) { + if (src) { + fclose(src); + } + return fail(c, "Cannot add %s: %s", s->name, mz_message(err)); + } + gboolean ok = TRUE; + while (src) { + size_t n = fread(buf, 1, CHUNK, src); + if (n == 0) { + if (ferror(src)) { + ok = fail(c, "Cannot read %s: %s", s->path, g_strerror(errno)); + } + break; + } + if (mz_zip_entry_write(zip, buf, (int32_t)n) != (int32_t)n) { + ok = fail(c, "Cannot add %s: %s", s->name, mz_message(MZ_WRITE_ERROR)); + break; + } + advance(c, n); + } + err = mz_zip_entry_close(zip); + if (ok && err != MZ_OK) { + ok = fail(c, "Cannot add %s: %s", s->name, mz_message(err)); + } + if (src) { + fclose(src); + } + return ok; +} + +char *nszip_zip_sync(const char *directory, const char *archive, int keep_parent, const char *password, nszip_progress_fn progress, void *user) { + Ctx c = {progress, user, 0, 0, 0, TRUE, NULL, 0}; + GPtrArray *sources = g_ptr_array_new_with_free_func(source_free); + uint8_t *buf = g_malloc(CHUNK); + void *stream = NULL, *zip = NULL; + gboolean ok = TRUE, created = FALSE, opened = FALSE; + + struct stat root; + if (!directory || !archive) { + ok = fail(&c, "Invalid arguments"); + goto done; + } + if (stat(directory, &root) != 0 || !S_ISDIR(root.st_mode)) { + ok = fail(&c, "Directory does not exist, invalid directory path: %s", directory); + goto done; + } + char *prefix = NULL; + char *canonical = g_canonicalize_filename(directory, NULL); + char *base = g_path_get_basename(canonical); + g_free(canonical); + if (keep_parent && strcmp(base, "/") != 0 && strcmp(base, ".") != 0) { + prefix = g_strconcat(base, "/", NULL); + Source *s = g_new0(Source, 1); + s->name = g_strdup(prefix); + s->dir = TRUE; + s->mtime = root.st_mtime; + s->mode = root.st_mode; + g_ptr_array_add(sources, s); + } + g_free(base); + char *root_path = realpath(directory, NULL); + if (!root_path) { + g_free(prefix); + ok = fail(&c, "Directory does not exist, invalid directory path: %s", directory); + goto done; + } + ok = collect(&c, sources, root_path, directory, prefix ? prefix : ""); + free(root_path); + g_free(prefix); + if (!ok) { + goto done; + } + for (guint i = 0; i < sources->len; i++) { + const Source *s = g_ptr_array_index(sources, i); + c.total = sat_add(c.total, sat_add(s->size, 1)); + } + + char *parent = g_path_get_dirname(archive); + g_mkdir_with_parents(parent, 0755); + g_free(parent); + stream = mz_stream_os_create(); + if (!stream || mz_stream_os_open(stream, archive, MZ_OPEN_MODE_CREATE | MZ_OPEN_MODE_WRITE) != MZ_OK) { + ok = fail(&c, "Cannot create %s: %s", archive, g_strerror(errno)); + goto done; + } + created = TRUE; + struct stat archive_st; + if (stat(archive, &archive_st) != 0) { + memset(&archive_st, 0, sizeof archive_st); + } + zip = mz_zip_create(); + int32_t err = zip ? mz_zip_open(zip, stream, MZ_OPEN_MODE_WRITE) : MZ_MEM_ERROR; + if (err != MZ_OK) { + ok = fail(&c, "Cannot create %s: %s", archive, mz_message(err)); + goto done; + } + opened = TRUE; + mz_zip_set_version_madeby(zip, MADE_BY_UNIX); + + for (guint i = 0; ok && i < sources->len; i++) { + const Source *s = g_ptr_array_index(sources, i); + if (!s->dir && s->dev == archive_st.st_dev && s->ino == archive_st.st_ino) { + advance(&c, sat_add(s->size, 1)); + continue; + } + if (strlen(s->name) > 0xffff) { + ok = fail(&c, "Path too long for a zip entry: %s", s->name); + break; + } + ok = add_entry(&c, zip, s, password, buf); + advance(&c, 1); + } + opened = FALSE; + err = mz_zip_close(zip); + if (ok && err != MZ_OK) { + ok = fail(&c, "Cannot write %s: %s", archive, mz_message(err)); + } + +done: + if (zip) { + if (opened) { + mz_zip_close(zip); + } + mz_zip_delete(&zip); + } + if (stream) { + if (mz_stream_os_close(stream) != MZ_OK && ok) { + ok = fail(&c, "Cannot write %s: %s", archive, g_strerror(errno)); + } + mz_stream_os_delete(&stream); + } + if (!ok && created) { + unlink(archive); + } + g_ptr_array_unref(sources); + g_free(buf); + return c.error; +} + +static char *entry_name(const mz_zip_file *info) { + const uint8_t *raw = (const uint8_t *)info->filename; + if (!raw) { + return g_strdup(""); + } + if (!(info->flag & MZ_ZIP_FLAG_UTF8) && info->extrafield) { + const uint8_t *x = info->extrafield, *end = x + info->extrafield_size; + while (x + 4 <= end) { + uint16_t id = get16(x), len = get16(x + 2); + const uint8_t *d = x + 4; + if (d + len > end) { + break; + } + if (id == EXTRA_UNICODE_PATH && len > 5 && d[0] == 1 && get32(d + 1) == (uint32_t)crc32(0L, raw, info->filename_size) && g_utf8_validate((const char *)d + 5, len - 5, NULL)) { + return g_strndup((const char *)d + 5, len - 5); + } + x = d + len; + } + } + return decode_name(raw, info->filename_size); +} + +static uint32_t posix_mode(const mz_zip_file *info) { + uint32_t attrib = 0; + if (mz_zip_attrib_convert(MZ_HOST_SYSTEM(info->version_madeby), info->external_fa, MZ_HOST_SYSTEM_UNIX, &attrib) != MZ_OK) { + return 0; + } + return attrib & 0777; +} + +static gboolean extract(Ctx *c, void *zip, const mz_zip_file *info, const char *name, const char *target, const char *password, uint8_t *buf) { + gboolean encrypted = (info->flag & MZ_ZIP_FLAG_ENCRYPTED) != 0; + if (encrypted && (!password || !*password)) { + return fail(c, "Entry %s is encrypted, a password is required", name); + } + uint64_t usize = (uint64_t)info->uncompressed_size; + uint32_t expected_crc = info->crc; + gboolean verify_crc = !(encrypted && info->aes_version == 2); + uint32_t mode = posix_mode(info); + time_t mtime = info->modified_date; + + int32_t err = mz_zip_entry_read_open(zip, 0, encrypted ? password : NULL); + if (err != MZ_OK) { + return fail(c, err == MZ_PASSWORD_ERROR ? "%s" : "%s: %s", err == MZ_PASSWORD_ERROR ? mz_message(err) : name, mz_message(err)); + } + + char *parent = g_path_get_dirname(target); + char *temp = g_build_filename(parent, ".nszip-XXXXXX", NULL); + g_free(parent); + int fd = g_mkstemp_full(temp, O_RDWR | O_CLOEXEC, 0600); + FILE *out = fd >= 0 ? fdopen(fd, "wb") : NULL; + gboolean ok = TRUE; + if (!out) { + int saved = errno; + if (fd >= 0) { + close(fd); + unlink(temp); + } + ok = fail(c, "Cannot create %s: %s", target, g_strerror(saved)); + } + + uLong crc = crc32(0L, Z_NULL, 0); + uint64_t written = 0; + while (ok) { + int32_t n = mz_zip_entry_read(zip, buf, CHUNK); + if (n < 0) { + ok = fail(c, "%s: %s", name, mz_message(n)); + break; + } + if (n == 0) { + break; + } + if ((uint64_t)n > usize - written) { + ok = fail(c, "Size mismatch for %s", name); + break; + } + crc = crc32(crc, buf, (uInt)n); + written += (uint64_t)n; + if (fwrite(buf, 1, (size_t)n, out) != (size_t)n) { + ok = fail(c, "Write failed: %s", g_strerror(errno)); + break; + } + advance(c, (uint64_t)n); + } + err = mz_zip_entry_close(zip); + if (ok && err != MZ_OK) { + ok = fail(c, "%s: %s", name, mz_message(err)); + } + if (ok && written != usize) { + ok = fail(c, "Size mismatch for %s", name); + } + if (ok && verify_crc && (uint32_t)crc != expected_crc) { + ok = fail(c, "%s: %s", name, mz_message(MZ_CRC_ERROR)); + } + if (!out) { + g_free(temp); + return FALSE; + } + if (ok && fflush(out) != 0) { + ok = fail(c, "Write failed: %s", g_strerror(errno)); + } + if (ok) { + fchmod(fd, (mode ? ((mode & 0755) | S_IRUSR | S_IWUSR) : 0644) & ~current_umask()); + struct timespec times[2] = {{mtime, 0}, {mtime, 0}}; + futimens(fd, times); + } + if (fclose(out) != 0 && ok) { + ok = fail(c, "Write failed: %s", g_strerror(errno)); + } + if (ok && rename(temp, target) != 0) { + ok = fail(c, "Cannot create %s: %s", target, g_strerror(errno)); + } + if (!ok) { + unlink(temp); + } + g_free(temp); + return ok; +} + +static gboolean handle_entry(Ctx *c, void *zip, const mz_zip_file *info, const char *destination, int overwrite, const char *password, uint8_t *buf) { + uint64_t usize = (uint64_t)info->uncompressed_size; + char *name = entry_name(info); + char **segments = safe_segments(name); + guint count = segments ? g_strv_length(segments) : 0; + gboolean ok = TRUE; + if (!count) { + g_debug("nszip: skipping unsafe entry %s", name); + advance(c, sat_add(usize, 1)); + goto out; + } + gboolean dir = mz_zip_entry_is_dir(zip) == MZ_OK; + int parents = ensure_directories(c, destination, segments, dir ? count : count - 1); + if (parents <= 0) { + ok = parents < 0; + advance(c, sat_add(usize, 1)); + goto out; + } + if (dir) { + advance(c, 1); + goto out; + } + char *relative = g_build_filenamev(segments); + char *target = g_build_filename(destination, relative, NULL); + g_free(relative); + struct stat st; + gboolean exists = lstat(target, &st) == 0; + if (mz_zip_entry_is_symlink(zip) == MZ_OK || (!overwrite && exists) || (exists && S_ISDIR(st.st_mode))) { + advance(c, sat_add(usize, 1)); + } else { + ok = extract(c, zip, info, name, target, password, buf); + advance(c, 1); + } + g_free(target); +out: + g_strfreev(segments); + g_free(name); + return ok; +} + +typedef struct { + int64_t offset; + int64_t size; +} Span; + +static gint compare_spans(gconstpointer a, gconstpointer b) { + const Span *x = a, *y = b; + return x->offset < y->offset ? -1 : x->offset > y->offset; +} + +char *nszip_unzip_sync(const char *archive, const char *destination, int overwrite, const char *password, nszip_progress_fn progress, void *user) { + Ctx c = {progress, user, 0, 0, 0, TRUE, NULL, 0}; + GArray *spans = g_array_new(FALSE, FALSE, sizeof(Span)); + uint8_t *buf = g_malloc(CHUNK); + void *stream = NULL, *zip = NULL; + gboolean ok = TRUE, opened = FALSE; + int32_t err; + + if (!archive || !destination) { + ok = fail(&c, "Invalid arguments"); + goto done; + } + FILE *probe = open_regular(archive, NULL); + if (!probe) { + ok = fail(&c, "File does not exist, invalid archive path: %s", archive); + goto done; + } + fclose(probe); + stream = mz_stream_os_create(); + if (!stream || mz_stream_os_open(stream, archive, MZ_OPEN_MODE_READ) != MZ_OK) { + ok = fail(&c, "File does not exist, invalid archive path: %s", archive); + goto done; + } + zip = mz_zip_create(); + err = zip ? mz_zip_open(zip, stream, MZ_OPEN_MODE_READ) : MZ_MEM_ERROR; + if (err != MZ_OK) { + ok = fail(&c, "Not a valid zip archive: %s", archive); + goto done; + } + opened = TRUE; + + uint64_t declared = 0; + for (err = mz_zip_goto_first_entry(zip); ok && err == MZ_OK; err = mz_zip_goto_next_entry(zip)) { + mz_zip_file *info = NULL; + if (mz_zip_entry_get_info(zip, &info) != MZ_OK || !info || info->compressed_size < 0 || info->uncompressed_size < 0 || info->disk_offset < 0) { + ok = fail(&c, "Corrupt central directory"); + break; + } + Span span = {info->disk_offset, info->compressed_size}; + g_array_append_val(spans, span); + declared = sat_add(declared, (uint64_t)info->uncompressed_size); + c.total = sat_add(c.total, sat_add((uint64_t)info->uncompressed_size, 1)); + } + if (ok && err != MZ_END_OF_LIST) { + ok = fail(&c, "Not a valid zip archive: %s", archive); + } + if (!ok) { + goto done; + } + g_array_sort(spans, compare_spans); + for (guint i = 1; i < spans->len; i++) { + const Span *prev = &g_array_index(spans, Span, i - 1), *next = &g_array_index(spans, Span, i); + if ((uint64_t)next->offset < sat_add((uint64_t)prev->offset, sat_add(30, (uint64_t)prev->size))) { + ok = fail(&c, "Overlapping entries in archive"); + goto done; + } + } + if (g_mkdir_with_parents(destination, 0755) != 0) { + ok = fail(&c, "Cannot create %s: %s", destination, g_strerror(errno)); + goto done; + } + struct statvfs fs; + if (statvfs(destination, &fs) == 0 && fs.f_frsize && declared / fs.f_frsize > (uint64_t)fs.f_bavail) { + ok = fail(&c, "Not enough free space to extract %s", archive); + goto done; + } + + for (err = mz_zip_goto_first_entry(zip); ok && err == MZ_OK; err = mz_zip_goto_next_entry(zip)) { + mz_zip_file *info = NULL; + if (mz_zip_entry_get_info(zip, &info) != MZ_OK || !info) { + ok = fail(&c, "Corrupt central directory"); + break; + } + ok = handle_entry(&c, zip, info, destination, overwrite, password, buf); + } + if (ok && err != MZ_END_OF_LIST) { + ok = fail(&c, "Not a valid zip archive: %s", archive); + } + +done: + if (zip) { + if (opened) { + mz_zip_close(zip); + } + mz_zip_delete(&zip); + } + if (stream) { + mz_stream_os_close(stream); + mz_stream_os_delete(&stream); + } + g_array_unref(spans); + g_free(buf); + return c.error; +} + +typedef struct { + gboolean unzip; + char *source; + char *destination; + char *password; + int flag; + nszip_progress_callback progress; + nszip_complete_callback complete; + char *error; +} Job; + +typedef struct { + nszip_progress_callback callback; + int percent; +} ProgressMessage; + +static gboolean progress_on_main(gpointer data) { + ProgressMessage *m = data; + m->callback(m->percent); + g_free(m); + return G_SOURCE_REMOVE; +} + +static void post_progress(int percent, void *user) { + Job *job = user; + if (!job->progress) { + return; + } + ProgressMessage *m = g_new(ProgressMessage, 1); + m->callback = job->progress; + m->percent = percent; + g_idle_add_full(G_PRIORITY_DEFAULT, progress_on_main, m, NULL); +} + +static gboolean complete_on_main(gpointer data) { + Job *job = data; + if (job->complete) { + job->complete(job->error); + } + g_free(job->source); + g_free(job->destination); + if (job->password) { + memset(job->password, 0, strlen(job->password)); + } + g_free(job->password); + g_free(job->error); + g_free(job); + return G_SOURCE_REMOVE; +} + +static gpointer worker(gpointer data) { + Job *job = data; + if (job->unzip) { + job->error = nszip_unzip_sync(job->source, job->destination, job->flag, job->password, post_progress, job); + } else { + job->error = nszip_zip_sync(job->source, job->destination, job->flag, job->password, post_progress, job); + } + + g_idle_add_full(G_PRIORITY_DEFAULT, complete_on_main, job, NULL); + return NULL; +} + +static void start(gboolean unzip, const char *source, const char *destination, int flag, const char *password, nszip_progress_callback progress, nszip_complete_callback complete) { + if (!complete) { + return; + } + Job *job = g_new0(Job, 1); + job->unzip = unzip; + job->source = g_strdup(source); + job->destination = g_strdup(destination); + job->password = password && *password ? g_strdup(password) : NULL; + job->flag = flag; + job->progress = progress; + job->complete = complete; + GError *error = NULL; + GThread *thread = g_thread_try_new("nszip", worker, job, &error); + if (!thread) { + job->error = g_strdup(error ? error->message : "Cannot start worker thread"); + g_clear_error(&error); + g_idle_add_full(G_PRIORITY_DEFAULT, complete_on_main, job, NULL); + return; + } + g_thread_unref(thread); +} + +void nszip_zip(const char *directory, const char *archive, int32_t keep_parent, const char *password, nszip_progress_callback progress, nszip_complete_callback complete) { + start(FALSE, directory, archive, keep_parent, password, progress, complete); +} + +void nszip_unzip(const char *archive, const char *destination, int32_t overwrite, const char *password, nszip_progress_callback progress, nszip_complete_callback complete) { + start(TRUE, archive, destination, overwrite, password, progress, complete); +} diff --git a/packages/zip/platforms/linux/src/nszip_internal.h b/packages/zip/platforms/linux/src/nszip_internal.h new file mode 100644 index 00000000..c87a457e --- /dev/null +++ b/packages/zip/platforms/linux/src/nszip_internal.h @@ -0,0 +1,13 @@ +#ifndef NSZIP_INTERNAL_H +#define NSZIP_INTERNAL_H + +#include + +typedef void (*nszip_progress_fn)(int percent, void *user); + +char *nszip_zip_sync(const char *directory, const char *archive, int keep_parent, const char *password, nszip_progress_fn progress, void *user); +char *nszip_unzip_sync(const char *archive, const char *destination, int overwrite, const char *password, nszip_progress_fn progress, void *user); + +extern uint64_t nszip_zip64_threshold; + +#endif diff --git a/packages/zip/platforms/windows/src/ZipArchiver.cs b/packages/zip/platforms/windows/src/ZipArchiver.cs new file mode 100644 index 00000000..c2de37d4 --- /dev/null +++ b/packages/zip/platforms/windows/src/ZipArchiver.cs @@ -0,0 +1,1242 @@ +#nullable enable +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace NativeScriptZip +{ + public static class ZipArchiver + { + public static Task ZipAsync(string directory, string archive, bool keepParent, string password, Action onProgress) => + Task.Run(() => + { + ArgumentNullException.ThrowIfNull(directory); + ArgumentNullException.ThrowIfNull(archive); + ZipWriter.Write(directory, archive, keepParent, string.IsNullOrEmpty(password) ? null : password, onProgress); + return archive; + }); + + public static Task UnzipAsync(string archive, string destination, bool overwrite, string password, Action onProgress) => + Task.Run(() => + { + ArgumentNullException.ThrowIfNull(archive); + ArgumentNullException.ThrowIfNull(destination); + ZipReader.Extract(archive, destination, overwrite, string.IsNullOrEmpty(password) ? null : password, onProgress); + return destination; + }); + } + + internal static class ZipFormat + { + public const uint SigLocal = 0x04034b50; + public const uint SigCentral = 0x02014b50; + public const uint SigEnd = 0x06054b50; + public const uint SigZip64End = 0x06064b50; + public const uint SigZip64Locator = 0x07064b50; + public const ushort Store = 0; + public const ushort Deflate = 8; + public const ushort MethodAes = 99; + public const ushort FlagEncrypted = 0x0001; + public const ushort FlagDataDescriptor = 0x0008; + public const ushort FlagUtf8 = 0x0800; + public const ushort ExtraZip64 = 0x0001; + public const ushort ExtraAes = 0x9901; + public const ushort ExtraUnicodePath = 0x7075; + public const ushort MadeByUnix = (3 << 8) | 63; + public const int Chunk = 256 * 1024; + public const int MacLength = 10; + public const long MaxCentralDirectory = 1L << 30; + + public static void W16(Span b, int at, ushort v) => BinaryPrimitives.WriteUInt16LittleEndian(b.Slice(at), v); + public static void W32(Span b, int at, uint v) => BinaryPrimitives.WriteUInt32LittleEndian(b.Slice(at), v); + public static void W64(Span b, int at, ulong v) => BinaryPrimitives.WriteUInt64LittleEndian(b.Slice(at), v); + public static ushort R16(ReadOnlySpan b, int at) => BinaryPrimitives.ReadUInt16LittleEndian(b.Slice(at)); + public static uint R32(ReadOnlySpan b, int at) => BinaryPrimitives.ReadUInt32LittleEndian(b.Slice(at)); + public static ulong R64(ReadOnlySpan b, int at) => BinaryPrimitives.ReadUInt64LittleEndian(b.Slice(at)); + + public static void WriteAesExtra(Span b, ushort method) + { + W16(b, 0, ExtraAes); + W16(b, 2, 7); + W16(b, 4, 2); + b[6] = (byte)'A'; + b[7] = (byte)'E'; + b[8] = 3; + W16(b, 9, method); + } + + public static (ushort time, ushort date) ToDos(DateTime t) + { + if (t.Year < 1980) + { + return (0, (1 << 5) | 1); + } + int year = Math.Min(t.Year, 2107); + return ((ushort)((t.Hour << 11) | (t.Minute << 5) | (t.Second >> 1)), (ushort)(((year - 1980) << 9) | (t.Month << 5) | t.Day)); + } + + public static DateTime? FromDos(ushort time, ushort date) + { + try + { + return new DateTime(((date >> 9) & 0x7f) + 1980, (date >> 5) & 0x0f, date & 0x1f, (time >> 11) & 0x1f, (time >> 5) & 0x3f, Math.Min((time & 0x1f) * 2, 59), DateTimeKind.Local); + } + catch (ArgumentOutOfRangeException) + { + return null; + } + } + + public static void TryDelete(string path) + { + try + { + File.Delete(path); + } + catch + { + } + } + } + + internal sealed class Progress + { + private readonly Action? callback; + private ulong done; + private ulong total; + private int last; + public bool Enabled = true; + + public Progress(Action? callback) => this.callback = callback; + + public static ulong Add(ulong a, ulong b) => a > ulong.MaxValue - b ? ulong.MaxValue : a + b; + + public void AddTotal(ulong units) => total = Add(total, units); + + public void Advance(ulong units) + { + if (!Enabled) + { + return; + } + done = Add(done, units); + int percent = total > 0 ? (int)Math.Min(100.0, (double)done * 100.0 / total) : 100; + if (percent > last) + { + last = percent; + callback?.Invoke(percent); + } + } + } + + internal static class Crc32 + { + private static readonly uint[] Table = Build(); + + private static uint[] Build() + { + var t = new uint[8 * 256]; + for (uint i = 0; i < 256; i++) + { + uint c = i; + for (int k = 0; k < 8; k++) + { + c = (c & 1) != 0 ? 0xEDB88320u ^ (c >> 1) : c >> 1; + } + t[i] = c; + } + for (int i = 0; i < 256; i++) + { + for (int s = 1; s < 8; s++) + { + uint prev = t[(s - 1) * 256 + i]; + t[s * 256 + i] = (prev >> 8) ^ t[prev & 0xff]; + } + } + return t; + } + + public static uint Byte(uint crc, byte b) => Table[(crc ^ b) & 0xff] ^ (crc >> 8); + + public static uint Update(uint crc, ReadOnlySpan data) + { + var t = Table; + crc = ~crc; + int i = 0; + for (; data.Length - i >= 8; i += 8) + { + uint one = BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(i)) ^ crc; + uint two = BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(i + 4)); + crc = t[7 * 256 + (one & 0xff)] ^ t[6 * 256 + ((one >> 8) & 0xff)] ^ t[5 * 256 + ((one >> 16) & 0xff)] ^ t[4 * 256 + (one >> 24)] + ^ t[3 * 256 + (two & 0xff)] ^ t[2 * 256 + ((two >> 8) & 0xff)] ^ t[256 + ((two >> 16) & 0xff)] ^ t[two >> 24]; + } + for (; i < data.Length; i++) + { + crc = t[(crc ^ data[i]) & 0xff] ^ (crc >> 8); + } + return ~crc; + } + } + + internal sealed class AesCtr : IDisposable + { + private const int Blocks = 256; + private readonly Aes aes; + private readonly byte[] counters = new byte[Blocks * 16]; + private readonly byte[] stream = new byte[Blocks * 16]; + private int pos = Blocks * 16; + private ulong counter; + + public AesCtr(byte[] key) + { + aes = Aes.Create(); + aes.Key = key; + } + + public void Transform(Span data) + { + for (int i = 0; i < data.Length; i++) + { + if (pos == stream.Length) + { + for (int b = 0; b < Blocks; b++) + { + BinaryPrimitives.WriteUInt64LittleEndian(counters.AsSpan(b * 16), ++counter); + } + aes.EncryptEcb(counters, stream, PaddingMode.None); + pos = 0; + } + data[i] ^= stream[pos++]; + } + } + + public void Dispose() => aes.Dispose(); + } + + internal static class WinZipAes + { + public static int SaltLength(int strength) => strength switch + { + 1 => 8, + 2 => 12, + 3 => 16, + _ => throw new InvalidDataException("Unsupported AES strength"), + }; + + public static (byte[] enc, byte[] mac, byte[] verifier) Derive(string password, byte[] salt, int strength) + { + int keyLength = SaltLength(strength) * 2; + byte[] secret = Encoding.UTF8.GetBytes(password); + byte[] d = Rfc2898DeriveBytes.Pbkdf2(secret, salt, 1000, HashAlgorithmName.SHA1, keyLength * 2 + 2); + var result = (d[..keyLength], d[keyLength..(keyLength * 2)], d[(keyLength * 2)..]); + CryptographicOperations.ZeroMemory(d); + CryptographicOperations.ZeroMemory(secret); + return result; + } + } + + internal sealed class ZipCrypto + { + private uint k0 = 0x12345678, k1 = 0x23456789, k2 = 0x34567890; + + public ZipCrypto(string password) + { + foreach (byte b in Encoding.UTF8.GetBytes(password)) + { + Update(b); + } + } + + private void Update(byte c) + { + unchecked + { + k0 = Crc32.Byte(k0, c); + k1 = (k1 + (k0 & 0xff)) * 134775813 + 1; + k2 = Crc32.Byte(k2, (byte)(k1 >> 24)); + } + } + + public void Decrypt(Span data) + { + for (int i = 0; i < data.Length; i++) + { + uint t = (k2 | 2) & 0xffff; + byte c = (byte)(data[i] ^ (byte)((t * (t ^ 1)) >> 8)); + Update(c); + data[i] = c; + } + } + } + + internal sealed class EntrySink : Stream + { + private readonly Stream output; + private readonly AesCtr? ctr; + private readonly IncrementalHash? hmac; + private byte[]? scratch; + + public EntrySink(Stream output, AesCtr? ctr, IncrementalHash? hmac) + { + this.output = output; + this.ctr = ctr; + this.hmac = hmac; + } + + public override void Write(byte[] buffer, int offset, int count) => Write(buffer.AsSpan(offset, count)); + + public override void Write(ReadOnlySpan data) + { + if (ctr == null) + { + output.Write(data); + return; + } + scratch ??= new byte[ZipFormat.Chunk]; + while (data.Length > 0) + { + int n = Math.Min(data.Length, scratch.Length); + var chunk = scratch.AsSpan(0, n); + data.Slice(0, n).CopyTo(chunk); + ctr.Transform(chunk); + hmac!.AppendData(chunk); + output.Write(chunk); + data = data.Slice(n); + } + } + + public override bool CanRead => false; + public override bool CanSeek => false; + public override bool CanWrite => true; + public override long Length => throw new NotSupportedException(); + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + public override void Flush() { } + public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + } + + internal sealed class EntryReader : Stream + { + private readonly Stream file; + private readonly AesCtr? ctr; + private readonly IncrementalHash? hmac; + private readonly ZipCrypto? zipCrypto; + private long remaining; + + public EntryReader(Stream file, long length, AesCtr? ctr, IncrementalHash? hmac, ZipCrypto? zipCrypto) + { + this.file = file; + remaining = length; + this.ctr = ctr; + this.hmac = hmac; + this.zipCrypto = zipCrypto; + } + + public override int Read(byte[] buffer, int offset, int count) => Read(buffer.AsSpan(offset, count)); + + public override int Read(Span buffer) + { + if (remaining <= 0 || buffer.Length == 0) + { + return 0; + } + int n = file.Read(buffer.Slice(0, (int)Math.Min(buffer.Length, remaining))); + if (n <= 0) + { + throw new EndOfStreamException("Unexpected end of archive"); + } + remaining -= n; + var chunk = buffer.Slice(0, n); + if (hmac != null) + { + hmac.AppendData(chunk); + ctr!.Transform(chunk); + } + else + { + zipCrypto?.Decrypt(chunk); + } + return n; + } + + public void Drain() + { + var tmp = new byte[64 * 1024]; + while (Read(tmp) > 0) + { + } + } + + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => false; + public override long Length => throw new NotSupportedException(); + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + public override void Flush() { } + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + } + + internal static class ZipWriter + { + internal static ulong Zip64Threshold = uint.MaxValue; + + private sealed class Source + { + public string Name = ""; + public string? Path; + public bool Directory; + public long Size; + public DateTime Modified; + } + + private sealed class Record + { + public byte[] Name = Array.Empty(); + public ushort Version, Flags, Method, Time, Date, AesMethod; + public uint Crc; + public ulong CompressedSize, Size, Offset; + public bool Directory, Aes; + } + + private static ulong LocalZip64Limit => Zip64Threshold > 65536 ? Zip64Threshold - 65536 : 0; + + public static void Write(string directory, string archive, bool keepParent, string? password, Action? onProgress) + { + var root = new DirectoryInfo(directory); + if (!root.Exists) + { + throw new DirectoryNotFoundException("Directory does not exist, invalid directory path: " + directory); + } + var sources = new List(); + string prefix = ""; + if (keepParent && root.Parent != null) + { + prefix = root.Name + "/"; + sources.Add(new Source { Name = prefix, Directory = true, Modified = root.LastWriteTime }); + } + string rootFull = Path.TrimEndingDirectorySeparator(root.FullName) + Path.DirectorySeparatorChar; + Collect(root, rootFull, prefix, sources); + string archivePath = Path.GetFullPath(archive); + sources.RemoveAll(s => s.Path != null && string.Equals(Path.GetFullPath(s.Path), archivePath, OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)); + + var progress = new Progress(onProgress); + foreach (var s in sources) + { + progress.AddTotal((ulong)s.Size + 1); + } + string? parent = Path.GetDirectoryName(Path.GetFullPath(archive)); + if (!string.IsNullOrEmpty(parent)) + { + Directory.CreateDirectory(parent); + } + + var records = new List(sources.Count); + var input = new byte[ZipFormat.Chunk]; + bool success = false; + try + { + using (var output = new FileStream(archive, FileMode.Create, FileAccess.ReadWrite, FileShare.None, 1 << 20)) + { + foreach (var source in sources) + { + var record = new Record { Name = Encoding.UTF8.GetBytes(source.Name), Directory = source.Directory, Offset = (ulong)output.Position }; + if (record.Name.Length > 0xffff) + { + throw new PathTooLongException("Path too long for a zip entry: " + source.Name); + } + (record.Time, record.Date) = ZipFormat.ToDos(source.Modified); + if (source.Directory) + { + WriteDirectory(output, record); + } + else if (!WriteFile(output, record, source, password, source.Size > 0 ? ZipFormat.Deflate : ZipFormat.Store, progress, input)) + { + output.SetLength((long)record.Offset); + output.Position = (long)record.Offset; + progress.Enabled = false; + WriteFile(output, record, source, password, ZipFormat.Store, progress, input); + progress.Enabled = true; + } + records.Add(record); + progress.Advance(1); + } + ulong cdOffset = (ulong)output.Position; + foreach (var r in records) + { + WriteCentral(output, r); + } + WriteEnd(output, (ulong)records.Count, cdOffset, (ulong)output.Position - cdOffset); + } + success = true; + } + finally + { + if (!success) + { + ZipFormat.TryDelete(archive); + } + } + } + + private static void Collect(DirectoryInfo dir, string root, string prefix, List into) + { + var children = dir.GetFileSystemInfos(); + Array.Sort(children, (a, b) => string.CompareOrdinal(a.Name, b.Name)); + foreach (var child in children) + { + if (child is DirectoryInfo d) + { + if (d.LinkTarget != null) + { + continue; + } + string name = prefix + d.Name + "/"; + into.Add(new Source { Name = name, Directory = true, Modified = d.LastWriteTime }); + Collect(d, root, name, into); + } + else if (child is FileInfo f) + { + long size = f.Length; + if (OperatingSystem.IsWindows() && ZipReader.IsReservedName(f.Name)) + { + continue; + } + if (f.LinkTarget != null) + { + var comparison = OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + if (f.ResolveLinkTarget(true) is not FileInfo target || !target.Exists || !Path.GetFullPath(target.FullName).StartsWith(root, comparison)) + { + continue; + } + size = target.Length; + } + into.Add(new Source { Name = prefix + f.Name, Path = f.FullName, Size = size, Modified = f.LastWriteTime }); + } + } + } + + private static void WriteDirectory(Stream output, Record r) + { + r.Version = 20; + r.Flags = ZipFormat.FlagUtf8; + r.Method = ZipFormat.Store; + Span h = stackalloc byte[30]; + h.Clear(); + ZipFormat.W32(h, 0, ZipFormat.SigLocal); + ZipFormat.W16(h, 4, r.Version); + ZipFormat.W16(h, 6, r.Flags); + ZipFormat.W16(h, 10, r.Time); + ZipFormat.W16(h, 12, r.Date); + ZipFormat.W16(h, 26, (ushort)r.Name.Length); + output.Write(h); + output.Write(r.Name); + } + + private static bool WriteFile(FileStream output, Record r, Source s, string? password, ushort method, Progress progress, byte[] input) + { + bool aes = password != null; + bool zip64 = (ulong)s.Size >= LocalZip64Limit; + r.Aes = aes; + r.AesMethod = method; + r.Method = aes ? ZipFormat.MethodAes : method; + r.Flags = (ushort)(ZipFormat.FlagUtf8 | (aes ? ZipFormat.FlagEncrypted : 0)); + r.Version = (ushort)(aes ? 51 : zip64 ? 45 : 20); + + int extraLength = (zip64 ? 20 : 0) + (aes ? 11 : 0); + var header = new byte[30 + r.Name.Length + extraLength]; + var h = header.AsSpan(); + ZipFormat.W32(h, 0, ZipFormat.SigLocal); + ZipFormat.W16(h, 4, r.Version); + ZipFormat.W16(h, 6, r.Flags); + ZipFormat.W16(h, 8, r.Method); + ZipFormat.W16(h, 10, r.Time); + ZipFormat.W16(h, 12, r.Date); + ZipFormat.W32(h, 18, zip64 ? uint.MaxValue : 0); + ZipFormat.W32(h, 22, zip64 ? uint.MaxValue : 0); + ZipFormat.W16(h, 26, (ushort)r.Name.Length); + ZipFormat.W16(h, 28, (ushort)extraLength); + r.Name.CopyTo(h.Slice(30)); + int e = 30 + r.Name.Length; + if (zip64) + { + ZipFormat.W16(h, e, ZipFormat.ExtraZip64); + ZipFormat.W16(h, e + 2, 16); + e += 20; + } + if (aes) + { + ZipFormat.WriteAesExtra(h.Slice(e), method); + } + output.Write(header); + long dataStart = output.Position; + + AesCtr? ctr = null; + IncrementalHash? hmac = null; + uint crc = 0; + ulong size = 0; + try + { + if (aes) + { + byte[] salt = RandomNumberGenerator.GetBytes(16); + var (enc, mac, verifier) = WinZipAes.Derive(password!, salt, 3); + ctr = new AesCtr(enc); + hmac = IncrementalHash.CreateHMAC(HashAlgorithmName.SHA1, mac); + CryptographicOperations.ZeroMemory(enc); + CryptographicOperations.ZeroMemory(mac); + output.Write(salt); + output.Write(verifier); + } + var sink = new EntrySink(output, ctr, hmac); + using (var src = new FileStream(s.Path!, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 0, FileOptions.SequentialScan)) + using (Stream target = method == ZipFormat.Deflate ? new DeflateStream(sink, CompressionLevel.Optimal, leaveOpen: true) : sink) + { + int n; + while ((n = src.Read(input, 0, input.Length)) > 0) + { + crc = Crc32.Update(crc, input.AsSpan(0, n)); + size += (ulong)n; + target.Write(input, 0, n); + progress.Advance((ulong)n); + } + } + if (hmac != null) + { + output.Write(hmac.GetHashAndReset().AsSpan(0, ZipFormat.MacLength)); + } + } + finally + { + ctr?.Dispose(); + hmac?.Dispose(); + } + + long end = output.Position; + ulong compressed = (ulong)(end - dataStart); + ulong overhead = aes ? 16UL + 2 + ZipFormat.MacLength : 0; + if (method == ZipFormat.Deflate && compressed - overhead > size) + { + return false; + } + if (!zip64 && (size >= Zip64Threshold || compressed >= Zip64Threshold)) + { + throw new IOException(s.Path + " changed size while it was being zipped"); + } + r.Crc = aes ? 0 : crc; + r.CompressedSize = compressed; + r.Size = size; + + Span patch = stackalloc byte[12]; + ZipFormat.W32(patch, 0, r.Crc); + ZipFormat.W32(patch, 4, zip64 ? uint.MaxValue : (uint)compressed); + ZipFormat.W32(patch, 8, zip64 ? uint.MaxValue : (uint)size); + output.Position = (long)r.Offset + 14; + output.Write(patch); + if (zip64) + { + Span z = stackalloc byte[16]; + ZipFormat.W64(z, 0, size); + ZipFormat.W64(z, 8, compressed); + output.Position = (long)r.Offset + 30 + r.Name.Length + 4; + output.Write(z); + } + output.Position = end; + return true; + } + + private static void WriteCentral(Stream output, Record r) + { + bool zSize = r.Size >= Zip64Threshold, zCompressed = r.CompressedSize >= Zip64Threshold, zOffset = r.Offset >= Zip64Threshold; + int zip64Length = (zSize ? 8 : 0) + (zCompressed ? 8 : 0) + (zOffset ? 8 : 0); + int extraLength = (zip64Length > 0 ? zip64Length + 4 : 0) + (r.Aes ? 11 : 0); + uint external = r.Directory ? (0x41EDu << 16) | 0x10 : 0x81A4u << 16; + ushort version = zip64Length > 0 && r.Version < 45 ? (ushort)45 : r.Version; + + var h = new byte[46 + r.Name.Length + extraLength]; + var s = h.AsSpan(); + ZipFormat.W32(s, 0, ZipFormat.SigCentral); + ZipFormat.W16(s, 4, ZipFormat.MadeByUnix); + ZipFormat.W16(s, 6, version); + ZipFormat.W16(s, 8, r.Flags); + ZipFormat.W16(s, 10, r.Method); + ZipFormat.W16(s, 12, r.Time); + ZipFormat.W16(s, 14, r.Date); + ZipFormat.W32(s, 16, r.Crc); + ZipFormat.W32(s, 20, zCompressed ? uint.MaxValue : (uint)r.CompressedSize); + ZipFormat.W32(s, 24, zSize ? uint.MaxValue : (uint)r.Size); + ZipFormat.W16(s, 28, (ushort)r.Name.Length); + ZipFormat.W16(s, 30, (ushort)extraLength); + ZipFormat.W32(s, 38, external); + ZipFormat.W32(s, 42, zOffset ? uint.MaxValue : (uint)r.Offset); + r.Name.CopyTo(s.Slice(46)); + int e = 46 + r.Name.Length; + if (zip64Length > 0) + { + ZipFormat.W16(s, e, ZipFormat.ExtraZip64); + ZipFormat.W16(s, e + 2, (ushort)zip64Length); + e += 4; + if (zSize) + { + ZipFormat.W64(s, e, r.Size); + e += 8; + } + if (zCompressed) + { + ZipFormat.W64(s, e, r.CompressedSize); + e += 8; + } + if (zOffset) + { + ZipFormat.W64(s, e, r.Offset); + e += 8; + } + } + if (r.Aes) + { + ZipFormat.WriteAesExtra(s.Slice(e), r.AesMethod); + } + output.Write(h); + } + + private static void WriteEnd(Stream output, ulong count, ulong cdOffset, ulong cdSize) + { + bool zip64 = count >= 0xffff || cdOffset >= Zip64Threshold || cdSize >= Zip64Threshold; + if (zip64) + { + Span z = stackalloc byte[76]; + z.Clear(); + ZipFormat.W32(z, 0, ZipFormat.SigZip64End); + ZipFormat.W64(z, 4, 44); + ZipFormat.W16(z, 12, ZipFormat.MadeByUnix); + ZipFormat.W16(z, 14, 45); + ZipFormat.W64(z, 24, count); + ZipFormat.W64(z, 32, count); + ZipFormat.W64(z, 40, cdSize); + ZipFormat.W64(z, 48, cdOffset); + ZipFormat.W32(z, 56, ZipFormat.SigZip64Locator); + ZipFormat.W64(z, 64, cdOffset + cdSize); + ZipFormat.W32(z, 72, 1); + output.Write(z); + } + Span h = stackalloc byte[22]; + h.Clear(); + ZipFormat.W32(h, 0, ZipFormat.SigEnd); + ZipFormat.W16(h, 8, zip64 ? (ushort)0xffff : (ushort)count); + ZipFormat.W16(h, 10, zip64 ? (ushort)0xffff : (ushort)count); + ZipFormat.W32(h, 12, zip64 ? uint.MaxValue : (uint)cdSize); + ZipFormat.W32(h, 16, zip64 ? uint.MaxValue : (uint)cdOffset); + output.Write(h); + } + } + + internal static class ZipReader + { + private const string Cp437High = "ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ "; + private static readonly UTF8Encoding StrictUtf8 = new UTF8Encoding(false, true); + + private sealed class Entry + { + public string Name = ""; + public ushort MadeBy, Flags, Method, Time, Date, AesVersion, AesMethod; + public byte AesStrength; + public bool Aes; + public uint Crc, External; + public ulong CompressedSize, Size, Offset; + } + + public static void Extract(string archive, string destination, bool overwrite, string? password, Action? onProgress) + { + if (!File.Exists(archive)) + { + throw new FileNotFoundException("File does not exist, invalid archive path: " + archive); + } + using var file = new FileStream(archive, FileMode.Open, FileAccess.Read, FileShare.Read, 1 << 16); + var entries = ReadDirectory(file); + string root = Path.GetFullPath(destination); + Directory.CreateDirectory(root); + string rootPrefix = root.EndsWith(Path.DirectorySeparatorChar) ? root : root + Path.DirectorySeparatorChar; + + var progress = new Progress(onProgress); + ulong declared = 0; + foreach (var e in entries) + { + declared = Progress.Add(declared, e.Size); + progress.AddTotal(Progress.Add(e.Size, 1)); + } + try + { + if (declared > (ulong)new DriveInfo(root).AvailableFreeSpace) + { + throw new IOException("Not enough free space to extract " + archive); + } + } + catch (ArgumentException) + { + } + var buffer = new byte[ZipFormat.Chunk]; + var comparison = OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + foreach (var e in entries) + { + var segments = SafeSegments(e.Name); + string? target = segments is { Length: > 0 } ? Path.GetFullPath(Path.Combine(root, Path.Combine(segments))) : null; + bool directory = IsDirectory(e); + if (target == null || !target.StartsWith(rootPrefix, comparison) || !EnsureDirectories(root, segments!, directory ? segments!.Length : segments!.Length - 1)) + { + progress.Advance(Progress.Add(e.Size, 1)); + continue; + } + if (directory) + { + progress.Advance(1); + continue; + } + bool exists = File.Exists(target) || Directory.Exists(target); + if (IsSymlink(e) || Directory.Exists(target) || (!overwrite && exists)) + { + progress.Advance(Progress.Add(e.Size, 1)); + continue; + } + ExtractEntry(file, e, target, password, progress, buffer); + progress.Advance(1); + } + } + + private static List ReadDirectory(FileStream file) + { + long length = file.Length; + if (length < 22) + { + throw new InvalidDataException("Not a zip archive"); + } + int tailLength = (int)Math.Min(length, 22 + 0xffff); + long tailStart = length - tailLength; + var tail = new byte[tailLength]; + file.Position = tailStart; + file.ReadExactly(tail); + int at = -1, fitting = -1; + for (int i = tailLength - 22; i >= 0; i--) + { + if (ZipFormat.R32(tail, i) != ZipFormat.SigEnd) + { + continue; + } + if (at < 0) + { + at = i; + } + if (i + 22 + ZipFormat.R16(tail, i + 20) != tailLength) + { + continue; + } + if (fitting < 0) + { + fitting = i; + } + uint size = ZipFormat.R32(tail, i + 12), offset = ZipFormat.R32(tail, i + 16); + if (size == uint.MaxValue || offset == uint.MaxValue || ZipFormat.R16(tail, i + 10) == 0xffff || (ulong)offset + size == (ulong)(tailStart + i)) + { + fitting = i; + break; + } + } + if (fitting >= 0) + { + at = fitting; + } + if (at < 0) + { + throw new InvalidDataException("Not a zip archive (end of central directory not found)"); + } + ulong count = ZipFormat.R16(tail, at + 10); + ulong cdSize = ZipFormat.R32(tail, at + 12); + ulong cdOffset = ZipFormat.R32(tail, at + 16); + long eocd = tailStart + at; + + if ((count == 0xffff || cdSize == uint.MaxValue || cdOffset == uint.MaxValue) && eocd >= 20) + { + var locator = new byte[20]; + file.Position = eocd - 20; + file.ReadExactly(locator); + if (ZipFormat.R32(locator, 0) == ZipFormat.SigZip64Locator) + { + ulong zip64End = ZipFormat.R64(locator, 8); + if (zip64End > (ulong)(length - 56)) + { + throw new InvalidDataException("Corrupt zip64 end of central directory"); + } + var z = new byte[56]; + file.Position = (long)zip64End; + file.ReadExactly(z); + if (ZipFormat.R32(z, 0) != ZipFormat.SigZip64End) + { + throw new InvalidDataException("Corrupt zip64 end of central directory"); + } + count = ZipFormat.R64(z, 32); + cdSize = ZipFormat.R64(z, 40); + cdOffset = ZipFormat.R64(z, 48); + } + } + if (cdOffset > (ulong)length || cdSize > (ulong)length - cdOffset || cdSize > (ulong)ZipFormat.MaxCentralDirectory) + { + throw new InvalidDataException("Corrupt central directory"); + } + + var cd = new byte[cdSize]; + file.Position = (long)cdOffset; + file.ReadExactly(cd); + var entries = new List(); + int p = 0; + for (ulong i = 0; i < count; i++) + { + if (p + 46 > cd.Length || ZipFormat.R32(cd, p) != ZipFormat.SigCentral) + { + throw new InvalidDataException("Corrupt central directory"); + } + int nameLength = ZipFormat.R16(cd, p + 28), extraLength = ZipFormat.R16(cd, p + 30), commentLength = ZipFormat.R16(cd, p + 32); + if (p + 46 + nameLength + extraLength + commentLength > cd.Length) + { + throw new InvalidDataException("Corrupt central directory"); + } + var rawName = cd.AsSpan(p + 46, nameLength); + var e = new Entry + { + MadeBy = ZipFormat.R16(cd, p + 4), + Flags = ZipFormat.R16(cd, p + 8), + Method = ZipFormat.R16(cd, p + 10), + Time = ZipFormat.R16(cd, p + 12), + Date = ZipFormat.R16(cd, p + 14), + Crc = ZipFormat.R32(cd, p + 16), + CompressedSize = ZipFormat.R32(cd, p + 20), + Size = ZipFormat.R32(cd, p + 24), + External = ZipFormat.R32(cd, p + 38), + Offset = ZipFormat.R32(cd, p + 42), + Name = DecodeName(rawName), + }; + + int x = p + 46 + nameLength, xEnd = x + extraLength; + while (x + 4 <= xEnd) + { + ushort id = ZipFormat.R16(cd, x), len = ZipFormat.R16(cd, x + 2); + int d = x + 4; + if (d + len > xEnd) + { + break; + } + if (id == ZipFormat.ExtraZip64) + { + int q = d; + if (e.Size == uint.MaxValue && q + 8 <= d + len) + { + e.Size = ZipFormat.R64(cd, q); + q += 8; + } + if (e.CompressedSize == uint.MaxValue && q + 8 <= d + len) + { + e.CompressedSize = ZipFormat.R64(cd, q); + q += 8; + } + if (e.Offset == uint.MaxValue && q + 8 <= d + len) + { + e.Offset = ZipFormat.R64(cd, q); + } + } + else if (id == ZipFormat.ExtraAes && len >= 7) + { + e.Aes = true; + e.AesVersion = ZipFormat.R16(cd, d); + e.AesStrength = cd[d + 4]; + e.AesMethod = ZipFormat.R16(cd, d + 5); + } + else if (id == ZipFormat.ExtraUnicodePath && len > 5 && cd[d] == 1 && (e.Flags & ZipFormat.FlagUtf8) == 0 && ZipFormat.R32(cd, d + 1) == Crc32.Update(0, rawName)) + { + try + { + e.Name = StrictUtf8.GetString(cd, d + 5, len - 5); + } + catch (DecoderFallbackException) + { + } + } + x = d + len; + } + entries.Add(e); + p += 46 + nameLength + extraLength + commentLength; + } + var sorted = entries.ToArray(); + Array.Sort(sorted, (a, b) => a.Offset.CompareTo(b.Offset)); + for (int i = 1; i < sorted.Length; i++) + { + if (sorted[i].Offset < Progress.Add(sorted[i - 1].Offset, Progress.Add(30, sorted[i - 1].CompressedSize))) + { + throw new InvalidDataException("Overlapping entries in archive"); + } + } + return entries; + } + + private static string DecodeName(ReadOnlySpan bytes) + { + try + { + return StrictUtf8.GetString(bytes); + } + catch (DecoderFallbackException) + { + var sb = new StringBuilder(bytes.Length); + foreach (byte b in bytes) + { + sb.Append(b < 0x80 ? (char)b : Cp437High[b - 0x80]); + } + return sb.ToString(); + } + } + + private static bool EnsureDirectories(string root, string[] segments, int count) + { + string path = root; + for (int i = 0; i < count; i++) + { + path = Path.Combine(path, segments[i]); + var info = new DirectoryInfo(path); + if (info.Exists || File.Exists(path)) + { + if (!info.Exists || (info.Attributes & FileAttributes.ReparsePoint) != 0) + { + return false; + } + } + else + { + Directory.CreateDirectory(path); + } + } + return true; + } + + private static readonly HashSet ReservedNames = new(StringComparer.OrdinalIgnoreCase) + { + "CON", "PRN", "AUX", "NUL", "CONIN$", "CONOUT$", + "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "COM¹", "COM²", "COM³", + "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", "LPT¹", "LPT²", "LPT³", + }; + + internal static bool IsReservedName(string name) + { + int dot = name.IndexOf('.'); + return ReservedNames.Contains((dot < 0 ? name : name.Substring(0, dot)).TrimEnd(' ')); + } + + private static string SafeSegment(string part, char[] invalid) + { + var chars = part.ToCharArray(); + for (int i = 0; i < chars.Length; i++) + { + if (chars[i] < 0x20 || Array.IndexOf(invalid, chars[i]) >= 0) + { + chars[i] = '_'; + } + } + string safe = new string(chars); + if (OperatingSystem.IsWindows()) + { + if (safe.EndsWith('.') || safe.EndsWith(' ')) + { + safe += "_"; + } + if (IsReservedName(safe)) + { + safe = "_" + safe; + } + } + return safe; + } + + private static string[]? SafeSegments(string name) + { + string normalized = name.Replace('\\', '/'); + if (normalized.StartsWith('/') || (normalized.Length > 1 && char.IsAsciiLetter(normalized[0]) && normalized[1] == ':')) + { + return null; + } + var invalid = Path.GetInvalidFileNameChars(); + var kept = new List(); + foreach (var part in normalized.Split('/')) + { + if (part.Length == 0 || part == ".") + { + continue; + } + if (part == "..") + { + return null; + } + kept.Add(SafeSegment(part, invalid)); + } + return kept.ToArray(); + } + + private static bool IsDirectory(Entry e) => e.Name.EndsWith('/') || e.Name.EndsWith('\\') || (e.Size == 0 && (e.External & 0x10) != 0); + + private static bool IsSymlink(Entry e) => (e.MadeBy >> 8) == 3 && ((e.External >> 16) & 0xF000) == 0xA000; + + private static void ExtractEntry(FileStream file, Entry e, string target, string? password, Progress progress, byte[] buffer) + { + long length = file.Length; + if (e.Offset > (ulong)Math.Max(0, length - 30)) + { + throw new InvalidDataException($"Corrupt local header for {e.Name}"); + } + var h = new byte[30]; + file.Position = (long)e.Offset; + file.ReadExactly(h); + if (ZipFormat.R32(h, 0) != ZipFormat.SigLocal) + { + throw new InvalidDataException($"Corrupt local header for {e.Name}"); + } + long data = (long)e.Offset + 30 + ZipFormat.R16(h, 26) + ZipFormat.R16(h, 28); + if (data > length || e.CompressedSize > (ulong)(length - data)) + { + throw new InvalidDataException($"Truncated entry {e.Name}"); + } + file.Position = data; + ulong payload = e.CompressedSize; + ushort method = e.Method; + bool verifyCrc = true; + AesCtr? ctr = null; + IncrementalHash? hmac = null; + ZipCrypto? zipCrypto = null; + try + { + if ((e.Flags & ZipFormat.FlagEncrypted) != 0) + { + if (password == null) + { + throw new InvalidDataException($"Entry {e.Name} is encrypted, a password is required"); + } + if (method == ZipFormat.MethodAes) + { + if (!e.Aes || e.AesStrength < 1 || e.AesStrength > 3) + { + throw new InvalidDataException($"Corrupt AES entry {e.Name}"); + } + int saltLength = WinZipAes.SaltLength(e.AesStrength); + if (payload < (ulong)(saltLength + 2 + ZipFormat.MacLength)) + { + throw new InvalidDataException($"Corrupt AES entry {e.Name}"); + } + var header = new byte[saltLength + 2]; + file.ReadExactly(header); + var (enc, mac, verifier) = WinZipAes.Derive(password, header[..saltLength], e.AesStrength); + bool match = CryptographicOperations.FixedTimeEquals(verifier, header.AsSpan(saltLength, 2)); + if (match) + { + ctr = new AesCtr(enc); + hmac = IncrementalHash.CreateHMAC(HashAlgorithmName.SHA1, mac); + } + CryptographicOperations.ZeroMemory(enc); + CryptographicOperations.ZeroMemory(mac); + if (!match) + { + throw new InvalidDataException("Wrong password"); + } + payload -= (ulong)(saltLength + 2 + ZipFormat.MacLength); + method = e.AesMethod; + verifyCrc = e.AesVersion != 2; + } + else + { + if (payload < 12) + { + throw new InvalidDataException($"Corrupt encrypted entry {e.Name}"); + } + var header = new byte[12]; + file.ReadExactly(header); + zipCrypto = new ZipCrypto(password); + zipCrypto.Decrypt(header); + byte check = (e.Flags & ZipFormat.FlagDataDescriptor) != 0 ? (byte)(e.Time >> 8) : (byte)(e.Crc >> 24); + if (header[11] != check) + { + throw new InvalidDataException("Wrong password"); + } + payload -= 12; + } + } + if (method != ZipFormat.Store && method != ZipFormat.Deflate) + { + throw new NotSupportedException($"Unsupported compression method {method} for {e.Name}"); + } + + var raw = new EntryReader(file, (long)payload, ctr, hmac, zipCrypto); + string temp = Path.Combine(Path.GetDirectoryName(target)!, ".nszip-" + Convert.ToHexString(RandomNumberGenerator.GetBytes(6))); + uint crc = 0; + ulong written = 0; + bool ok = false; + try + { + using (var output = new FileStream(temp, FileMode.CreateNew, FileAccess.Write, FileShare.None, 1 << 16)) + { + using (Stream source = method == ZipFormat.Deflate ? new DeflateStream(raw, CompressionMode.Decompress, leaveOpen: true) : raw) + { + int n; + while ((n = source.Read(buffer, 0, buffer.Length)) > 0) + { + if ((ulong)n > e.Size - written) + { + throw new InvalidDataException($"Size mismatch for {e.Name}"); + } + crc = Crc32.Update(crc, buffer.AsSpan(0, n)); + written += (ulong)n; + output.Write(buffer, 0, n); + progress.Advance((ulong)n); + } + } + raw.Drain(); + if (hmac != null) + { + var expected = new byte[ZipFormat.MacLength]; + file.ReadExactly(expected); + if (!CryptographicOperations.FixedTimeEquals(hmac.GetHashAndReset().AsSpan(0, ZipFormat.MacLength), expected)) + { + throw new InvalidDataException($"AES authentication failed for {e.Name}, the archive is corrupt"); + } + } + if (written != e.Size) + { + throw new InvalidDataException($"Size mismatch for {e.Name}"); + } + if (verifyCrc && crc != e.Crc) + { + throw new InvalidDataException($"CRC mismatch for {e.Name}"); + } + } + if (ZipFormat.FromDos(e.Time, e.Date) is DateTime modified) + { + File.SetLastWriteTime(temp, modified); + } + File.Move(temp, target, overwrite: true); + ok = true; + } + finally + { + if (!ok) + { + ZipFormat.TryDelete(temp); + } + } + } + finally + { + ctr?.Dispose(); + hmac?.Dispose(); + } + } + } +}