src/n_zip.c
Reading members out of a ZIP archive, to a file. More...
Structures
| Name | |
|---|---|
| struct | ZIP_SINK where unpacked bytes go: a file, or a buffer in memory |
Functions
| Name | |
|---|---|
| int | zip_unpack(const char * path, const char * name, const N_ZIP_MEMBER * member, ZIP_SINK * sink) unpack a member into a sink, checking its size and CRC-32 |
| int | zip_scan(const char * path, const char * name, int ending, N_ZIP_MEMBER * member, char * found_name, size_t found_len) find a member by its whole name, or by how its name ends |
| int | sink_put(ZIP_SINK * sink, const unsigned char * data, size_t len) put unpacked bytes where they go |
| int | seek_to(FILE * f, uint64_t at) helper: seek to a 64 bit offset where the platform's fseek takes a long |
| uint32_t | le32(const unsigned char * p) helper: a little endian 32 bit value |
| unsigned | le16(const unsigned char * p) helper: a little endian 16 bit value |
Defines
| Name | |
|---|---|
| N_ZIP_TAIL how much of an archive's end is searched for its end record: the record and the longest comment it may carry |
|
| N_ZIP_CHUNK the pieces a member is read and written in |
Detailed Description
Reading members out of a ZIP archive, to a file.
Author: Castagnier Mickael
Version: 1.0
Date: 28/09/2026
Functions Documentation
function zip_unpack
static int zip_unpack(
const char * path,
const char * name,
const N_ZIP_MEMBER * member,
ZIP_SINK * sink
)
unpack a member into a sink, checking its size and CRC-32
Parameters:
- path the archive
- name the member
- member what n_zip_find() said of it
- sink where the bytes go
Return: 0, or -1
function zip_scan
static int zip_scan(
const char * path,
const char * name,
int ending,
N_ZIP_MEMBER * member,
char * found_name,
size_t found_len
)
find a member by its whole name, or by how its name ends
Parameters:
- path the archive
- name the name, or its ending
- ending non-zero to match the ending only
- member receives what it is, may be NULL
- found_name receives the whole name when matching an ending, may be NULL
- found_len size of found_name
Return: 0 when found, -1 otherwise
function sink_put
static int sink_put(
ZIP_SINK * sink,
const unsigned char * data,
size_t len
)
put unpacked bytes where they go
Parameters:
- sink the destination
- data the bytes
- len how many
Return: 0, or -1 when the file refused them or the buffer is full
function seek_to
static int seek_to(
FILE * f,
uint64_t at
)
helper: seek to a 64 bit offset where the platform's fseek takes a long
function le32
static uint32_t le32(
const unsigned char * p
)
helper: a little endian 32 bit value
function le16
static unsigned le16(
const unsigned char * p
)
helper: a little endian 16 bit value
Macros Documentation
define N_ZIP_TAIL
#define N_ZIP_TAIL (22 + 65535)
how much of an archive's end is searched for its end record: the record and the longest comment it may carry
define N_ZIP_CHUNK
#define N_ZIP_CHUNK 262144
the pieces a member is read and written in
Source code
/*
* Nilorea Library
* Copyright (C) 2005-2026 Castagnier Mickael
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <zlib.h>
#include "nilorea/n_log.h"
#include "nilorea/n_zip.h"
#define N_ZIP_TAIL (22 + 65535)
#define N_ZIP_CHUNK 262144
static unsigned le16(const unsigned char* p) {
return (unsigned)p[0] | ((unsigned)p[1] << 8);
}
static uint32_t le32(const unsigned char* p) {
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}
static int seek_to(FILE* f, uint64_t at) {
if (at > (uint64_t)0x7fffffff) return -1;
return fseek(f, (long)at, SEEK_SET);
}
static int zip_scan(const char* path, const char* name, int ending, N_ZIP_MEMBER* member, char* found_name, size_t found_len) {
FILE* f = NULL;
unsigned char* tail = NULL;
long size = 0, start = 0, i = 0;
size_t got = 0, name_len = 0;
uint32_t dir_size = 0, dir_at = 0;
unsigned entries = 0, k = 0;
int found = -1;
if (!path || !name) return -1;
name_len = strlen(name);
f = fopen(path, "rb");
if (!f) return -1;
if (fseek(f, 0, SEEK_END) != 0 || (size = ftell(f)) < 22) {
fclose(f);
return -1;
}
start = size > N_ZIP_TAIL ? size - N_ZIP_TAIL : 0;
tail = (unsigned char*)malloc((size_t)(size - start));
if (!tail || fseek(f, start, SEEK_SET) != 0 || (got = fread(tail, 1, (size_t)(size - start), f)) < 22) {
free(tail);
fclose(f);
return -1;
}
/* The end of central directory record, the last one in the file. */
for (i = (long)got - 22; i >= 0; i--) {
if (le32(tail + i) == 0x06054b50u) break;
}
if (i < 0) {
free(tail);
fclose(f);
return -1;
}
entries = le16(tail + i + 10);
dir_size = le32(tail + i + 12);
dir_at = le32(tail + i + 16);
free(tail);
tail = NULL;
if (dir_at == 0xffffffffu || dir_size == 0xffffffffu || (long)dir_at + (long)dir_size > size) {
n_log(LOG_ERR, "zip: %s is ZIP64 or damaged, not read", path);
fclose(f);
return -1;
}
tail = (unsigned char*)malloc(dir_size ? dir_size : 1);
if (!tail || seek_to(f, dir_at) != 0 || fread(tail, 1, dir_size, f) != dir_size) {
free(tail);
fclose(f);
return -1;
}
fclose(f);
/* The central directory: one record a member, each 46 bytes and then
* its name, extra field and comment. */
for (k = 0, i = 0; k < entries && (uint32_t)i + 46 <= dir_size; k++) {
const unsigned char* r = tail + i;
unsigned n = 0, extra = 0, comment = 0;
if (le32(r) != 0x02014b50u) break;
n = le16(r + 28);
extra = le16(r + 30);
comment = le16(r + 32);
if ((uint32_t)i + 46 + n > dir_size) break;
if ((!ending && n == name_len && memcmp(r + 46, name, n) == 0) ||
(ending && n >= name_len && memcmp(r + 46 + n - name_len, name, name_len) == 0)) {
if (found_name && found_len) {
size_t copy = n < found_len - 1 ? n : found_len - 1;
memcpy(found_name, r + 46, copy);
found_name[copy] = '\0';
}
if (member) {
member->method = le16(r + 10);
member->crc = le32(r + 16);
member->compressed = le32(r + 20);
member->size = le32(r + 24);
member->offset = le32(r + 42);
}
found = (le32(r + 20) == 0xffffffffu || le32(r + 24) == 0xffffffffu) ? -1 : 0;
break;
}
i += 46 + (long)n + (long)extra + (long)comment;
}
free(tail);
return found;
}
int n_zip_find(const char* path, const char* name, N_ZIP_MEMBER* member) {
return zip_scan(path, name, 0, member, NULL, 0);
}
int n_zip_find_ending(const char* path, const char* ending, char* name, size_t name_len) {
if (!name || !name_len) return -1;
return zip_scan(path, ending, 1, NULL, name, name_len);
}
typedef struct {
FILE* file;
unsigned char* buffer;
size_t used;
size_t size;
} ZIP_SINK;
static int sink_put(ZIP_SINK* sink, const unsigned char* data, size_t len) {
if (sink->file) return fwrite(data, 1, len, sink->file) == len ? 0 : -1;
if (len > sink->size - sink->used) return -1;
memcpy(sink->buffer + sink->used, data, len);
sink->used += len;
return 0;
}
static int zip_unpack(const char* path, const char* name, const N_ZIP_MEMBER* member, ZIP_SINK* sink) {
unsigned char local[30];
unsigned char *in_buf = NULL, *out_buf = NULL;
FILE* f = NULL;
uint64_t left = 0, written = 0;
uLong crc = crc32(0L, Z_NULL, 0);
z_stream zs;
int zs_ready = 0, result = -1;
if (member->method != 0 && member->method != 8) {
n_log(LOG_ERR, "zip: %s in %s is compressed by method %u, not read", name, path, member->method);
return -1;
}
f = fopen(path, "rb");
if (!f) return -1;
/* The local header's own name and extra lengths, which may differ from
* the central directory's, say where the data begins. */
if (seek_to(f, member->offset) != 0 || fread(local, 1, 30, f) != 30 || le32(local) != 0x04034b50u ||
seek_to(f, member->offset + 30 + le16(local + 26) + le16(local + 28)) != 0) {
fclose(f);
return -1;
}
in_buf = (unsigned char*)malloc(N_ZIP_CHUNK);
out_buf = (unsigned char*)malloc(N_ZIP_CHUNK);
if (!in_buf || !out_buf) goto done;
memset(&zs, 0, sizeof(zs));
/* Raw deflate: a ZIP member has no zlib header. */
if (member->method == 8) {
if (inflateInit2(&zs, -MAX_WBITS) != Z_OK) goto done;
zs_ready = 1;
}
left = member->compressed;
while (left > 0) {
size_t take = left < N_ZIP_CHUNK ? (size_t)left : N_ZIP_CHUNK;
if (fread(in_buf, 1, take, f) != take) goto done;
left -= take;
if (member->method == 0) {
if (sink_put(sink, in_buf, take) != 0) goto done;
crc = crc32(crc, in_buf, (uInt)take);
written += take;
continue;
}
zs.next_in = in_buf;
zs.avail_in = (uInt)take;
do {
int rc = 0;
size_t have = 0;
zs.next_out = out_buf;
zs.avail_out = N_ZIP_CHUNK;
rc = inflate(&zs, Z_NO_FLUSH);
if (rc != Z_OK && rc != Z_STREAM_END && rc != Z_BUF_ERROR) goto done;
have = N_ZIP_CHUNK - zs.avail_out;
if (have && sink_put(sink, out_buf, have) != 0) goto done;
crc = crc32(crc, out_buf, (uInt)have);
written += have;
if (rc == Z_STREAM_END) break;
} while (zs.avail_in > 0 || zs.avail_out == 0);
}
if (written != member->size || (uint32_t)crc != member->crc) {
n_log(LOG_ERR, "zip: %s in %s came out %llu bytes, crc %08lx; %llu, %08x expected", name, path,
(unsigned long long)written, (unsigned long)crc, (unsigned long long)member->size, member->crc);
goto done;
}
result = 0;
done:
if (zs_ready) inflateEnd(&zs);
free(in_buf);
free(out_buf);
fclose(f);
return result;
}
int n_zip_extract(const char* path, const char* name, const char* out) {
N_ZIP_MEMBER member;
ZIP_SINK sink;
int result = -1;
if (!out || n_zip_find(path, name, &member) != 0) return -1;
memset(&sink, 0, sizeof(sink));
sink.file = fopen(out, "wb");
if (!sink.file) return -1;
result = zip_unpack(path, name, &member, &sink);
if (fclose(sink.file) != 0) result = -1;
if (result != 0) remove(out);
return result;
}
int n_zip_read(const char* path, const char* name, size_t max_size, unsigned char** data, size_t* len) {
N_ZIP_MEMBER member;
ZIP_SINK sink;
if (!data || !len) return -1;
*data = NULL;
*len = 0;
if (n_zip_find(path, name, &member) != 0) return -1;
if (member.size > max_size || member.size > (uint64_t)SIZE_MAX - 1) {
n_log(LOG_ERR, "zip: %s in %s is %llu bytes, more than the %zu allowed", name, path,
(unsigned long long)member.size, max_size);
return -1;
}
memset(&sink, 0, sizeof(sink));
sink.size = (size_t)member.size;
/* One more byte, so a text member can be ended with a zero. */
sink.buffer = (unsigned char*)malloc(sink.size + 1);
if (!sink.buffer) return -1;
if (zip_unpack(path, name, &member, &sink) != 0 || sink.used != sink.size) {
free(sink.buffer);
return -1;
}
sink.buffer[sink.size] = 0;
*data = sink.buffer;
*len = sink.size;
return 0;
}
Modules
- AABB3D
- ACCEPT_POOL
- ALLEGRO4
- ALLEGRO5
- ANIM_LIB
- ASTAR
- ATLAS_LIB
- AVRO
- CLOCK_SYNC
- COMMONS
- CONFIGFILE
- CYPHER_BASE64
- CYPHER_VIGENERE
- DEAD_RECKONING
- ENUMS
- EXCEPTIONS
- GAME_ENV
- GIT
- GUI
- HASH_TABLE
- ISOMETRIC_ENGINE
- LIST
- LOG
- LOGNODUP
- LZ4
- NETWORKING
- NETWORK_MSG
- N_COOKIES
- N_DIFF
- N_DIGEST
- N_DISK_CACHE
- N_DNS
- N_ECC
- N_ENTROPY
- N_FILES
- N_FLUIDS
- N_GUI_COLOR
- N_GUI_COLORWHEEL
- N_GUI_CURVE
- N_GUI_GRID
- N_GUI_POPUP
- N_HEX
- N_HTTP3
- N_HTTP_COOKIE
- N_HTTP_FORM
- N_HTTP_MIME
- N_HTTP_REQ
- N_HTTP_RESP
- N_HTTP_ROUTER
- N_HTTP_SERVER
- N_ICC
- N_JSON
- N_KAFKA
- N_LINALG
- N_MODULE
- N_OPENSSL_COMPAT
- N_PRETTY
- N_PTH
- N_QUERY
- N_RANDOM
- N_SHA256
- N_STR
- N_TIFF
- N_TIME
- N_USER
- N_X509
- N_ZIP
- PARTICLES
- PCRE
- PHYSICS
- SIGNALS
- STACK
- THREADS
- TRAJECTORY
- TREE
- ZLIB