Files
ps5upload/payload/installer/http_range.c
T
phantomptrandClaude Opus 5.5 c14782944d PS5Upload v5.35.0
Upload, install, convert and manage PS5 game packages over the network.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 04:31:13 -07:00

198 lines
6.7 KiB
C

#define _GNU_SOURCE /* strcasestr on glibc (CI host); harmless on macOS/BSD */
#include "http_range.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdarg.h>
#include <inttypes.h>
inst_range_result_t inst_parse_range(const char *rv, uint64_t total,
uint64_t *start, uint64_t *end) {
if (rv == NULL) return INST_RANGE_NONE;
const char *b = strstr(rv, "bytes=");
if (b == NULL) return INST_RANGE_NONE;
b += 6;
/* multi-range -> let caller serve the whole file */
if (strchr(b, ',') != NULL) return INST_RANGE_NONE;
const char *dash = strchr(b, '-');
if (dash == NULL) return INST_RANGE_NONE;
uint64_t s, e;
if (dash == b) {
/* suffix: bytes=-n */
char *ep = NULL;
unsigned long long n = strtoull(dash + 1, &ep, 10);
if (ep == dash + 1) return INST_RANGE_NONE; /* no digits -> malformed */
if (total == 0 || n == 0) return INST_RANGE_UNSAT;
if (n > total) n = total;
s = total - n;
e = total - 1;
} else {
char *ep = NULL;
unsigned long long a = strtoull(b, &ep, 10);
if (ep != dash) return INST_RANGE_NONE; /* junk before dash */
s = a;
if (*(dash + 1) >= '0' && *(dash + 1) <= '9') {
e = strtoull(dash + 1, NULL, 10);
} else {
e = (total > 0) ? total - 1 : 0; /* open-ended */
}
}
if (total == 0 || s >= total || s > e) return INST_RANGE_UNSAT;
if (e >= total) e = total - 1;
*start = s;
*end = e;
return INST_RANGE_OK;
}
int inst_http_header(const char *req, const char *name, char *out, size_t cap) {
size_t nlen = strlen(name);
const char *p = req;
/* skip the request line */
const char *eol = strstr(p, "\r\n");
if (eol == NULL) return 0;
p = eol + 2;
while (*p && !(p[0] == '\r' && p[1] == '\n')) {
const char *line = p;
const char *le = strstr(line, "\r\n");
if (le == NULL) le = line + strlen(line);
if ((size_t)(le - line) > nlen && line[nlen] == ':') {
int match = 1;
for (size_t i = 0; i < nlen; i++) {
if (tolower((unsigned char)line[i]) !=
tolower((unsigned char)name[i])) { match = 0; break; }
}
if (match) {
const char *v = line + nlen + 1;
while (v < le && (*v == ' ' || *v == '\t')) v++;
size_t vlen = (size_t)(le - v);
if (vlen >= cap) vlen = cap - 1;
memcpy(out, v, vlen);
out[vlen] = '\0';
return 1;
}
}
if (*le == '\0') break;
p = le + 2;
}
return 0;
}
int inst_http_percent_decode(const char *in, char *out, size_t cap) {
size_t o = 0;
for (const char *p = in; *p; p++) {
char c = *p;
if (c == '%') {
if (!isxdigit((unsigned char)p[1]) || !isxdigit((unsigned char)p[2]))
return -1;
char h[3] = { p[1], p[2], 0 };
c = (char)strtol(h, NULL, 16);
p += 2;
}
if (o + 1 >= cap) return -1;
out[o++] = c;
}
if (o >= cap) return -1;
out[o] = '\0';
return 0;
}
int inst_http_parse_head(const char *req, size_t len, inst_http_req_t *out) {
(void)len;
memset(out, 0, sizeof(*out));
char raw_target[INST_HTTP_TARGET_MAX];
char version[16];
/* request line: METHOD SP TARGET SP VERSION */
if (sscanf(req, "%7s %1023s %15s", out->method, raw_target, version) != 3)
return -1;
out->is_get = (strcmp(out->method, "GET") == 0);
out->is_head = (strcmp(out->method, "HEAD") == 0);
/* strip query */
char *q = strchr(raw_target, '?');
if (q) *q = '\0';
if (inst_http_percent_decode(raw_target, out->target, sizeof(out->target)) != 0)
return -1;
/* keep-alive basis: HTTP/1.1 default unless Connection: close */
char conn[64];
int has_conn = inst_http_header(req, "Connection", conn, sizeof(conn));
int wants_close = has_conn && (strcasestr(conn, "close") != NULL);
int wants_keep = has_conn && (strcasestr(conn, "keep-alive") != NULL);
int http11 = (strcmp(version, "HTTP/1.1") == 0);
out->keep_alive = !wants_close && (wants_keep || http11);
return 0;
}
int inst_http_imf_date(time_t t, char *out, size_t cap) {
struct tm tmv;
if (gmtime_r(&t, &tmv) == NULL) return -1;
size_t n = strftime(out, cap, "%a, %d %b %Y %H:%M:%S GMT", &tmv);
return n > 0 ? 0 : -1;
}
static int hdr_emit(char *out, size_t cap, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int n = vsnprintf(out, cap, fmt, ap);
va_end(ap);
if (n < 0 || (size_t)n >= cap) return -1;
return n;
}
static const char *conn_tok(int keep_alive) {
return keep_alive ? "keep-alive" : "close";
}
int inst_http_hdr_200(char *out, size_t cap, uint64_t total,
time_t date, time_t fixed_lm, int keep_alive) {
char d[64], lm[64];
if (inst_http_imf_date(date, d, sizeof(d)) != 0) return -1;
if (inst_http_imf_date(fixed_lm, lm, sizeof(lm)) != 0) return -1;
return hdr_emit(out, cap,
"HTTP/1.1 200 OK\r\n"
"Date: %s\r\n"
"Last-Modified: %s\r\n"
"Content-Type: application/octet-stream\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: %" PRIu64 "\r\n"
"Connection: %s\r\n\r\n",
d, lm, total, conn_tok(keep_alive));
}
int inst_http_hdr_206(char *out, size_t cap, uint64_t start, uint64_t end,
uint64_t total, time_t date, time_t fixed_lm, int keep_alive) {
char d[64], lm[64];
if (inst_http_imf_date(date, d, sizeof(d)) != 0) return -1;
if (inst_http_imf_date(fixed_lm, lm, sizeof(lm)) != 0) return -1;
uint64_t clen = end - start + 1;
return hdr_emit(out, cap,
"HTTP/1.1 206 Partial Content\r\n"
"Date: %s\r\n"
"Last-Modified: %s\r\n"
"Content-Type: application/octet-stream\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: %" PRIu64 "\r\n"
"Content-Range: bytes %" PRIu64 "-%" PRIu64 "/%" PRIu64 "\r\n"
"Connection: %s\r\n\r\n",
d, lm, clen, start, end, total, conn_tok(keep_alive));
}
int inst_http_hdr_416(char *out, size_t cap, uint64_t total, int keep_alive) {
return hdr_emit(out, cap,
"HTTP/1.1 416 Range Not Satisfiable\r\n"
"Content-Range: bytes */%" PRIu64 "\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: 0\r\n"
"Connection: %s\r\n\r\n",
total, conn_tok(keep_alive));
}
int inst_http_hdr_404(char *out, size_t cap, int keep_alive) {
return hdr_emit(out, cap,
"HTTP/1.1 404 Not Found\r\n"
"Content-Length: 0\r\n"
"Connection: %s\r\n\r\n",
conn_tok(keep_alive));
}