One small, zero-dependency DLL that lets a program browse and read files the same way everywhere: a folder on a local disk, a network share, entries inside a ZIP or TAR archive, an in-memory store, or a custom backend you register yourself — all through URIs like zip:///C:/data/a.zip!/docs/readme.txt and the same five calls (pv_list, pv_stat, pv_read_to_file, pv_read_text, pv_copy).
#include "providerkit.h"
#include <stdio.h>
int main(void) {
char *out;
pv_list("zip:///C:/data/a.zip!/docs", &out);
printf("%s\n", out); /* {"entries":[...],"provider":"zip"} */
pv_free_string(out);
pv_read_to_file("zip:///C:/data/a.zip!/docs/readme.txt", "C:/tmp/readme.txt");
pv_copy("file:///C:/tmp/readme.txt", "mem://scratch/docs/readme.txt");
char *text = pv_read_text("mem://scratch/docs/readme.txt", 65536);
pv_free_string(text);
return 0;
}- One interface, many backends.
file://(local disks andfile://host/sharenetwork shares),zip://andtar://(entries inside archives),mem://(an in-memory store), and any scheme you register. Operations dispatch on the scheme; the calling code does not change. - Consistent JSON output.
pv_listreturns direct children (name, full-URIpath,is_dir,size,modified_unixor null) sorted by name, plus the provider name.pv_statreports a missing target as{"exists":false,...}rather than an error.pv_providerslists every scheme with its{read, write, list}capabilities. - Cross-provider copy. Copy a file from any readable provider to
file://ormem://— for example extracting one archive entry straight into memory. Archives are read-only destinations in v0.1 and say so withPV_ERR_NOT_SUPPORTED. - Streaming reads. Built-in file, ZIP and TAR content is copied in 64 KiB chunks, never held whole in memory (the
mem://store is in memory by definition). - A real ZIP reader, honestly scoped. End Of Central Directory + central directory parsing, STORED entries read fully with a CRC-32 check on every extraction, DEFLATE entries listed and stated but not read in v0.1 (see Limitations).
- A real TAR reader. Plain uncompressed TAR with ustar headers, header checksums verified, list/stat/read fully supported.
- A plugin SDK. Register a scheme with four C callbacks (list, stat, optional read_to_file, free_string) and it works through every
pv_*operation, including cross-provider copies out of it. The exact callback contract is documented ininclude/providerkit.h. - Distinct errors, plain messages. Unknown scheme, malformed URI, not found, too large, CRC mismatch and "not supported" are different codes (
PV_ERR_*), each with a thread-localpv_last_error()message. - Zero dependencies. Rust, hand-written ZIP/TAR/CRC-32/JSON code, no crates.
file:///abs/path file://host/share/path
zip:///path/to/a.zip!/inner/path
tar:///path/to/a.tar!/inner/path
mem://storename/path
<custom>://anything (scheme must be registered)
The ! separates an archive from the path inside it; zip:///a.zip and zip:///a.zip! both name the archive root. Inner paths may omit the leading /; . components are dropped and .. is rejected, so an inner path can never point outside its container. In file:// paths, %XX percent-escapes are decoded.
Registering a custom provider from C:
#include "providerkit.h"
#include <stdlib.h>
#include <string.h>
static int demo_list(void *ud, const char *uri, char **out) {
const char *json = "{\"entries\":[],\"provider\":\"demo\"}";
size_t n = strlen(json) + 1;
char *copy = (char *)malloc(n);
memcpy(copy, json, n);
*out = copy;
return PV_OK;
}
static int demo_stat(void *ud, const char *uri, char **out) {
const char *json = "{\"exists\":false,\"is_dir\":false,\"size\":0,\"modified_unix\":null}";
size_t n = strlen(json) + 1;
char *copy = (char *)malloc(n);
memcpy(copy, json, n);
*out = copy;
return PV_OK;
}
static void demo_free(void *ud, char *p) { free(p); }
int main(void) {
PvProviderCallbacks cb;
memset(&cb, 0, sizeof cb);
cb.struct_size = sizeof cb;
cb.list = demo_list;
cb.stat = demo_stat;
cb.read_to_file = NULL; /* reads return PV_ERR_NOT_SUPPORTED */
cb.free_string = demo_free; /* releases the strings list/stat return */
pv_register_provider("demo", &cb, NULL);
char *out;
pv_list("demo://anything", &out); /* routed to demo_list */
pv_free_string(out);
pv_unregister_provider("demo");
return 0;
}The contract in one paragraph: on PV_OK, list/stat store a provider-allocated NUL-terminated JSON string in *out_json; ProviderKit copies it and calls the provider's free_string exactly once to release the original. On a nonzero return, *out_json must be NULL and the code is propagated to the caller. Full details are in the header.
cargo build --release # providerkit.dll / libproviderkit.so / static lib
cargo test --locked # the full test suite
Rust 1.80+, edition 2021, crate types cdylib + rlib + staticlib. No dependencies to download.
20 tests, all fixtures built in-test (a temporary directory tree; a ZIP archive assembled byte-by-byte with stored entries, a DEFLATE-header entry and a deliberately wrong CRC; a TAR archive assembled header-by-header):
- URI grammar: absolute and UNC
file://paths, percent-decoding, archive root forms,..rejection, unknown scheme, invalid schemes. - File backend: list ordering and fields, stat of present/missing paths, read_text cap, list-on-a-file error, same-file copy refusal, directory-copy refusal.
- ZIP: list/stat of stored and DEFLATE entries (including DOS timestamps), stored extraction, DEFLATE read returning
PV_ERR_NOT_SUPPORTED, CRC mismatch returningPV_ERR_INTEGRITYand removing the partial output, missing/corrupt archive errors. - TAR: list/stat/read, ustar directory entries, a corrupted header caught by its checksum.
- mem and cross-provider copies: file → mem → file round trip, zip → mem, copy into zip/tar refused, providers list capabilities.
- Custom providers, registered both through the Rust API and through the C ABI (
pv_register_provider): list/stat routing, stat JSON re-emitted with the provider name, copy out to a file, duplicate-scheme and built-in-collision errors, unregister behaviour, and a provider without a read callback reportingPV_ERR_NOT_SUPPORTEDon read.
- DEFLATE is not decompressed in v0.1. ZIP entries using method 8 can be listed and stated; reading one returns
PV_ERR_NOT_SUPPORTEDwith a message saying exactly that. No compressed TAR variants (.tar.gz,.tar.xz) are supported either. - Archives are read-only. Copying or writing into a
zip://ortar://URI returnsPV_ERR_NOT_SUPPORTED. Custom providers are also read-only as copy destinations in v0.1. - No ZIP64, multi-disk ZIP, encrypted ZIP, or other compression methods (bzip2, LZMA, ...). Each returns
PV_ERR_NOT_SUPPORTEDor, for a file that is not a ZIP at all,PV_ERR_INTEGRITY. - Copies are single files. There is no recursive directory copy in v0.1; copying a directory returns
PV_ERR_NOT_SUPPORTED. - TAR limits: header checksums and the ustar prefix field are supported; base-256 numeric fields are not, and non-regular entries (links, devices) can be listed but not read.
- Copying into
mem://loads the file into memory — that is what the store is. Copying a very large file there needs that much RAM. pv_read_textis for small files by design — it refuses anything over the caller'smax_bytescap and anything that is not valid UTF-8. Usepv_read_to_filefor binary or large content.- Timestamps inside ZIP are MS-DOS local-time fields with no timezone; ProviderKit converts them as if they were UTC, which is the only consistent choice available.
MIT — see LICENSE. Part of the WinDevKit family; see AGENTS.md for build discipline, ROADMAP.md for what is next, QUALITY.md for the scored quality record, and docs/PARITY.md for the C ABI / Rust API parity table.