mirror of
https://github.com/LisherSong/ps5-web-file-manager.git
synced 2026-10-06 07:00:28 +02:00
First release under the fork-marker convention: VERSION_TAG carries a trailing
`M`, so /api/version, the PS5 start-up notification, the stdout banner, the UI
footer and the ELF file name all read "v1.9.3M" in one move -- and a fork build
can no longer collide with an upstream artifact of the same version, a mix-up
that already happened twice. The footer carries a tooltip spelling the marker
out.
Two bodies of work.
1. Encrypted archives (ZIP / RAR / 7z)
- ZIP: ZipCrypto (traditional PKWARE) and WinZip AES-256, through
minizip-ng plus a vendored crypto layer (mz_crypt_wfm.c,
mz_strm_pkcrypt.c, mz_strm_wzaes.c).
- RAR: RARSetPassword, wired after RAROpenArchiveEx and before the first
RARReadHeaderEx. The ordering is load-bearing, not stylistic.
- 7z: 7zAES including -mhe=on encrypted headers, via a virtual
ISeekInStream that splices a pseudo-header + the real archive + the
decrypted header, so no offset stored inside the archive has to move.
A wrong password is reported as ZIPX_ERR_PASSWORD, and a failed attempt
leaves no staging directory behind.
2. Reporting, and the UI round that on-device testing produced
- A RAR whose dictionary exceeds what the build supports now gets its own
extract_dict_too_large code instead of being mis-reported as "entry too
large"; the message names both the required and the supported size. The
behaviour is deliberately unchanged -- such archives are still refused,
because admitting one means allocating the whole window up front, which
is why rarlab's own CLI refuses them by default.
- The upload entry is a menu again: one "Upload" button opening "Upload
files / Upload folder". The previous main-button-plus-small-arrow made
"upload folder" effectively undiscoverable.
- A drag-and-drop hint sits in the footer (hidden on the console browser,
where drag is not how anyone uploads).
- The extract button is now always present and merely disabled until
exactly one archive is selected, instead of appearing out of nowhere.
- Upload-and-extract on an encrypted archive now prompts for the password
directly. The retry table used to be keyed by PATH, and for a non-ASCII
directory the string the page holds and the string the server reports
are not the same bytes -- the lookup missed, so the user got a bare
error box and had to press Extract by hand before the prompt appeared.
It is now keyed by task id, which the server assigns and echoes back
verbatim.
- Error text passes through decodeFsText(), so a GBK entry name no longer
surfaces as `â®…ç§.psd`.
- Local names are encoded with encodeFsText() before being joined onto a
server-side path. fs_path_value() declines to rewrite a path if ANY code
point exceeds 0xFF, so concatenating a local name onto a server directory
produced a mixed representation and a silently dead path.
- The menu row highlight was losing the cascade to the generic button rule
(identical specificity, later in the file) while inheriting the toolbar's
3px focus ring, which overflowed a 46px row. Both rules are now scoped to
the panel and the keyboard cue is an inset ring, so it cannot escape the
row at any line height.
- The footer status line is clamped to a single line; a long
"uploading 3/12: some-name.zip" used to wrap out of the 46px footer.
- A first failed password attempt now says the archive is encrypted,
instead of blaming a password the user was never asked for.
Artifact
web-file-mgr-v1.9.3M.elf
903,448 B
sha256 8ca47d5aaca75085b32641300cce30fadb7df7749cb6b53d04f129bcecc286b7
e_machine 0x003e (x86-64 / PS5)
The file size is identical to the four builds before it, and every one of
them carries a different sha256: only .rodata moved, and by less than the
16 KiB section alignment absorbs. Compare sections with `readelf -SW` --
never infer "nothing changed" from the byte count.
Verification
- host suites: 140 ZIP + 37 RAR = 177 checks, 0 failures
- 7z suite: 27 cases, 0 failures. The `aeshe` entry that used to sit in
KNOWN_GAPS is gone -- the -mhe=on fixture now passes both the folder
decoder and the extraction facade
- frontend: .build/ui_retry_test.mjs (40 checks), .build/ui_upload_menu_test.mjs
(40 checks), .build/preview_check.mjs (12 assertions in headless Chromium
against the real page and a fixture API). Two layout regressions and the
highlight cascade bug were caught by the last one and by nothing else --
reading the source, both CSS rules "look correct"
- built twice from this tree: byte-identical (cmp clean). rsync refreshes
every asset mtime, so this is a genuine recompile, not make short-circuiting
on unchanged sources
- embedded assets verified in place with .build/check-elf-gzip.py, because
gen-asset-module.py gzips them and plain `strings` finds none of their text
- exercised end to end on a real PS5; the checklist is
docs/DEVICE-TEST-v1.9.3M.md
Docs
- docs/USER-GUIDE-zh-CN.md (new, simplified Chinese user guide)
- docs/DEVICE-TEST-v1.9.3M.md (new, on-device acceptance checklist)
- docs/REAL-CONSOLE-PROFILE.md (new, measured console behaviour)
- docs/archive/HANDOVER-v1.8-planning.md (superseded v1.8 design notes)
- CHANGELOG / README (both languages) / HANDOVER updated with the artifact
fingerprint, the section deltas and the new test counts
101 lines
3.7 KiB
C
101 lines
3.7 KiB
C
/* Bits of the zipx_* contract that are not specific to a container format.
|
|
|
|
The limit profiles and the status-to-text mapping describe the *engine
|
|
family*, not ZIP, so they live here rather than inside zip_extract.c. All
|
|
three engines (ZIP, RAR, 7z) link this one object; keeping them in the ZIP
|
|
file would force the RAR and 7z test builds to drag in minizip-ng and zlib
|
|
for the sake of three functions. */
|
|
|
|
#include "zip_extract.h"
|
|
|
|
/* Default limits.
|
|
*
|
|
* Tuned to cover real-world PS5 workloads without prompting:
|
|
* - PS5 system backup archives (~200-300 GiB total, individual chunks
|
|
* well under 64 GiB)
|
|
* - 3A-game archives with a single ~300 GiB uncompressed file
|
|
*
|
|
* Safety against decompression bombs is delegated to:
|
|
* 1. `check_space()` (statvfs-based real disk space check) before extract
|
|
* 2. `max_ratio` below (declared compression ratio cap)
|
|
* The size caps here are an early-fail UX guard, not a security boundary.
|
|
*/
|
|
static const zipx_limits_t k_default_limits = {
|
|
.max_entries = 200000,
|
|
.max_total_bytes = 2ULL * 1024 * 1024 * 1024 * 1024,
|
|
.max_file_bytes = 512ULL * 1024 * 1024 * 1024,
|
|
.max_ratio = 500,
|
|
/* Only entries that would individually materialise >=1 GiB are screened
|
|
by ratio; anything smaller is harmless (bounded by declared size + the
|
|
real free-space check) and is commonly highly compressible in
|
|
legitimate archives. */
|
|
.ratio_min_bytes = 1ULL * 1024 * 1024 * 1024,
|
|
.max_depth = 32,
|
|
.max_name_len = 255,
|
|
.max_path_len = 1024
|
|
};
|
|
|
|
/* Large profile for archives that exceed the default cap.
|
|
*
|
|
* - max_file_bytes = 1 TiB (single uncompressed file)
|
|
* - max_total_bytes = 4 TiB (whole archive)
|
|
* - max_ratio = 1000 (relaxed ratio cap; check_space still applies)
|
|
*
|
|
* Requires the user to opt in via the web UI (large=1) before these take
|
|
* effect. Default limits must always be strictly smaller than large so the
|
|
* large profile is unambiguously a relaxation.
|
|
*/
|
|
static const zipx_limits_t k_large_limits = {
|
|
.max_entries = 500000,
|
|
.max_total_bytes = 4ULL * 1024 * 1024 * 1024 * 1024,
|
|
.max_file_bytes = 1ULL * 1024 * 1024 * 1024 * 1024,
|
|
.max_ratio = 1000,
|
|
.ratio_min_bytes = 1ULL * 1024 * 1024 * 1024,
|
|
.max_depth = 32,
|
|
.max_name_len = 255,
|
|
.max_path_len = 1024
|
|
};
|
|
|
|
const zipx_limits_t *
|
|
zipx_default_limits(void) {
|
|
return &k_default_limits;
|
|
}
|
|
|
|
const zipx_limits_t *
|
|
zipx_limits_profile(int profile) {
|
|
switch(profile) {
|
|
case ZIPX_LIMITS_LARGE:
|
|
return &k_large_limits;
|
|
case ZIPX_LIMITS_DEFAULT:
|
|
default:
|
|
return &k_default_limits;
|
|
}
|
|
}
|
|
|
|
const char *
|
|
zipx_status_string(zipx_status_t status) {
|
|
switch(status) {
|
|
case ZIPX_OK: return "ok";
|
|
case ZIPX_ERR_CANCELED: return "canceled";
|
|
case ZIPX_ERR_OPEN: return "cannot open archive";
|
|
case ZIPX_ERR_FORMAT: return "corrupt archive";
|
|
case ZIPX_ERR_UNSUPPORTED: return "unsupported archive";
|
|
case ZIPX_ERR_UNSAFE_NAME: return "unsafe entry name";
|
|
case ZIPX_ERR_SPECIAL: return "unsupported entry type";
|
|
case ZIPX_ERR_DUPLICATE: return "duplicate entry name";
|
|
case ZIPX_ERR_LIMIT_ENTRIES: return "too many entries";
|
|
case ZIPX_ERR_LIMIT_FILE: return "entry too large";
|
|
case ZIPX_ERR_LIMIT_TOTAL: return "archive contents too large";
|
|
case ZIPX_ERR_LIMIT_RATIO: return "compression ratio too high";
|
|
case ZIPX_ERR_LIMIT_DEPTH: return "path too deep";
|
|
case ZIPX_ERR_LIMIT_NAME: return "path too long";
|
|
case ZIPX_ERR_LIMIT_DICT: return "dictionary too large";
|
|
case ZIPX_ERR_CONFLICT: return "target already exists";
|
|
case ZIPX_ERR_PASSWORD: return "password required or wrong";
|
|
case ZIPX_ERR_SPACE: return "not enough space";
|
|
case ZIPX_ERR_IO: return "read or write failed";
|
|
case ZIPX_ERR_CRC: return "crc mismatch";
|
|
default: return "internal error";
|
|
}
|
|
}
|