mirror of
https://github.com/knutwurst/patchdl.git
synced 2026-10-06 05:00:22 +02:00
Manifest JSON parser used substring scans with no per-piece scope; a key defined in a later piece could be misattributed to the current one, and the escape handling silently dropped the byte after a backslash even for unknown escapes. json_string_after/json_u64_after now take an optional limit pointer (NULL = legacy unbounded), and the manifest loops pass obj_end so per-piece reads can't leak across pieces. JSON escapes are decoded properly: \" \\ \/ \n \r \t \b \f; unknown \X drops the backslash and keeps the payload byte. Sanity caps on assembled manifests: PATCHDL_MAX_PIECES (4096), PATCHDL_MAX_PIECE_BYTES (8 GiB), PATCHDL_MAX_TOTAL_BYTES (200 GiB). A malformed manifest with a single multi-TB piece or millions of entries is now rejected before any disk activity. patchdl_buf_t gains an optional `max` field; write_cb fails the transfer when growth would exceed it. patchdl_http_get preserves the caller-set max across its internal memset(). verxml_query caps at 16 MiB, fetch_manifest and download_manifest at 64 MiB. host_allowed switches to strcasecmp (DNS is case-insensitive; an upstream redirect could otherwise drop out of the list). CURLOPT_PROTOCOLS_STR / REDIR_PROTOCOLS_STR pin all traffic and redirects to HTTPS. CURLOPT_NOSIGNAL=1 prevents libcurl from raising SIGPIPE in a worker. DNS label parser bounds-checks the length byte before incrementing pos, so a malformed response with a 0xFF label near the end can no longer read past the receive buffer. extract_title_id used `p[8]` as the loop guard, which crossed the NUL terminator on strings shorter than 9 chars (UB). Replaced with a strlen-based bound.
97 lines
4.5 KiB
C
97 lines
4.5 KiB
C
#pragma once
|
|
|
|
#include <stddef.h>
|
|
|
|
typedef struct {
|
|
char *data;
|
|
size_t size;
|
|
size_t cap;
|
|
size_t max; /* 0 = unbounded (legacy). Otherwise write_cb fails past this. */
|
|
} patchdl_buf_t;
|
|
|
|
patchdl_buf_t *patchdl_buf_new(void);
|
|
void patchdl_buf_free(patchdl_buf_t *b);
|
|
|
|
/* Call once, single-threaded, before any concurrent download worker starts /
|
|
after they have all joined. curl's global/OpenSSL init is otherwise lazy and
|
|
races across threads. */
|
|
void patchdl_net_global_init(void);
|
|
void patchdl_net_global_cleanup(void);
|
|
|
|
int patchdl_http_get(const char *url, patchdl_buf_t *out);
|
|
|
|
/* ---- parallel piece download (used by the connection pool) ------------- */
|
|
|
|
/* One piece of a split manifest package. `url` is heap-allocated. */
|
|
typedef struct {
|
|
char *url;
|
|
long long offset; /* byte offset of this piece in the assembled file */
|
|
long long size; /* exact length of this piece */
|
|
char hash[80]; /* manifest SHA-256 hex, or "" */
|
|
} patchdl_piece_t;
|
|
|
|
typedef struct {
|
|
patchdl_piece_t *pieces;
|
|
int count;
|
|
long long total; /* assembled file size = sum of piece sizes */
|
|
} patchdl_manifest_t;
|
|
|
|
/* Fetch + parse a Sony JSON manifest into a validated, contiguously-tiled
|
|
piece list. Returns 0 on success (caller frees with patchdl_manifest_free),
|
|
-1 on fetch/parse/tiling failure. */
|
|
int patchdl_fetch_manifest(const char *manifest_url, patchdl_manifest_t *out);
|
|
void patchdl_manifest_free(patchdl_manifest_t *m);
|
|
|
|
/* Live state shared with one in-flight piece download. The worker owns these;
|
|
the curl callbacks read `abort` (set elsewhere) and publish progress into
|
|
`bytes_slot` (single-writer per worker slot). */
|
|
typedef struct {
|
|
volatile long long *bytes_slot; /* bytes written so far for this piece */
|
|
volatile int *abort; /* non-zero -> stop this transfer */
|
|
} patchdl_piece_ctx_t;
|
|
|
|
/* Download one whole piece and pwrite it into `fd` at `file_offset`. Concurrent
|
|
non-overlapping pieces of the same fd are safe. Returns 0 on success (and
|
|
fdatasyncs fd), -1 on network/IO/abort, -2 on a SHA-256 mismatch. */
|
|
int patchdl_http_download_piece(const char *url, int fd,
|
|
long long file_offset, long long file_size,
|
|
const char *expected_sha256_or_null,
|
|
patchdl_piece_ctx_t *ctx);
|
|
|
|
/* Read-only: SHA-256 a [offset, offset+size) region of fd into out_hex
|
|
(caller provides >= 65 bytes). Returns 0 on success. */
|
|
int patchdl_sha256_fd_region(int fd, long long offset, long long size,
|
|
char *out_hex);
|
|
|
|
/* Progress callback. Return non-zero to ABORT the in-flight download (used to
|
|
cancel large patch downloads); return 0 to continue. */
|
|
typedef int (*patchdl_download_progress_cb)(void *ctx,
|
|
long long downloaded,
|
|
long long total);
|
|
|
|
/* Stream a URL to a file on disk (for large PKG downloads). Returns 0 on
|
|
success and writes the byte count to *bytes_out. */
|
|
int patchdl_http_download(const char *url, const char *dest_path,
|
|
long long *bytes_out);
|
|
int patchdl_http_download_progress(const char *url, const char *dest_path,
|
|
long long *bytes_out,
|
|
patchdl_download_progress_cb cb, void *ctx);
|
|
|
|
/* Download a Sony JSON package manifest by concatenating every entry in
|
|
"pieces" into one installable PKG. When `verify` is non-zero each piece is
|
|
checked against its manifest SHA-256 (a mismatch returns -2). When `resume`
|
|
is non-zero an existing partial at dest_path is kept: fully-downloaded pieces
|
|
are skipped and only the remainder is fetched (survives a reboot). On any
|
|
failure the partial is left in place for a later resume. */
|
|
int patchdl_http_download_manifest(const char *manifest_url, const char *dest_path,
|
|
long long *bytes_out);
|
|
int patchdl_http_download_manifest_progress(const char *manifest_url,
|
|
const char *dest_path,
|
|
long long *bytes_out,
|
|
patchdl_download_progress_cb cb,
|
|
void *ctx, int verify, int resume);
|
|
|
|
/* Diagnostic: run the GET pipeline for `url` and write a JSON report
|
|
(dns result/ip, curl code, http status, bytes) into `out_json`. */
|
|
void patchdl_net_diag(const char *url, char *out_json, size_t sz);
|