| 1 | /* |
| 2 | http_ps3.c: HTTP/1.1 over the PS3's sockets, with TLS 1.2 from mbedTLS. |
| 3 |
|
| 4 | From NZ:P PS3's ps3_https.c (daedra000/nzp-ps3), by way of Resonant: |
| 5 | - every request has its own connection, TLS context and random generator, |
| 6 | so the catalogue, the art and a download run side by side; |
| 7 | - a redirect is only followed on the same host (dl.ps3.world never sends one). |
| 8 |
|
| 9 | The system's own HTTPS (cellHttps) only speaks the TLS of ~2015, which |
| 10 | current certificates refuse. mbedTLS checks servers against the Mozilla |
| 11 | roots (ca_bundle.h, made by CI). |
| 12 | */ |
| 13 | #include <stdio.h> |
| 14 | #include <stdlib.h> |
| 15 | #include <string.h> |
| 16 | #include <stdarg.h> |
| 17 | #include <strings.h> |
| 18 | #include <ctype.h> |
| 19 | #include <unistd.h> |
| 20 | #include <errno.h> |
| 21 |
|
| 22 | #include <net/net.h> |
| 23 | #include <net/netdb.h> |
| 24 | #include <net/socket.h> |
| 25 | #include <net/poll.h> |
| 26 | #include <netinet/in.h> |
| 27 | #include <arpa/inet.h> |
| 28 | #include <sys/time.h> |
| 29 | #include <lv2/system.h> |
| 30 | #include <lv2/systime.h> |
| 31 |
|
| 32 | #include "mbedtls/ssl.h" |
| 33 | #include "mbedtls/entropy.h" |
| 34 | #include "mbedtls/ctr_drbg.h" |
| 35 | #include "mbedtls/x509_crt.h" |
| 36 | #include "mbedtls/error.h" |
| 37 | #include "mbedtls/net_sockets.h" |
| 38 | #include "mbedtls/platform_time.h" |
| 39 |
|
| 40 | #include "../plat.h" |
| 41 | #include "../lang.h" |
| 42 | #include "../version.h" |
| 43 |
|
| 44 | #ifndef EWOULDBLOCK |
| 45 | #define EWOULDBLOCK EAGAIN |
| 46 | #endif |
| 47 |
|
| 48 | #if __has_include("ca_bundle.h") |
| 49 | #include "ca_bundle.h" |
| 50 | #define HAVE_CA_BUNDLE 1 |
| 51 | #elif !defined(ALLOW_NO_HTTPS) |
| 52 | // a build without root certificates can't talk to dl.ps3.world at all: stop here |
| 53 | // rather than ship one (CI writes ca_bundle.h; -DALLOW_NO_HTTPS for a test build) |
| 54 | #error "src/ps3/ca_bundle.h is missing: see the mbedTLS step in .github/workflows/ps3.yml" |
| 55 | #endif |
| 56 |
|
| 57 | #define MAX_REDIRECTS 8 |
| 58 |
|
| 59 | static plat_mutex_t *init_lock; // TLS roots, DNS |
| 60 | static int tls_state; // 0 not tried, 1 ok, -1 failed |
| 61 | static mbedtls_x509_crt cacert; // read-only once parsed: shared |
| 62 | static int num_roots; |
| 63 |
|
| 64 | typedef struct { |
| 65 | int fd; |
| 66 | int tls; |
| 67 | mbedtls_ssl_context ssl; |
| 68 | mbedtls_ssl_config conf; |
| 69 | mbedtls_entropy_context entropy; |
| 70 | mbedtls_ctr_drbg_context drbg; |
| 71 | int tls_made; |
| 72 | unsigned char buf[16384]; |
| 73 | int pos, len; |
| 74 | volatile int *cancel; |
| 75 | int read_timeout_s; // this long with nothing arriving: give up |
| 76 | uint64_t deadline_us; // the whole request (0: none, a stream) |
| 77 | } conn_t; |
| 78 |
|
| 79 | // mbedTLS 3.6 asks the platform for a millisecond clock (MBEDTLS_PLATFORM_MS_TIME_ALT) |
| 80 | mbedtls_ms_time_t mbedtls_ms_time(void) |
| 81 | { |
| 82 | return (mbedtls_ms_time_t)(sysGetSystemTime() / 1000); |
| 83 | } |
| 84 |
|
| 85 | #define SLICE_S 1 // recv waits this long at a time, so a cancel is seen within a second |
| 86 |
|
| 87 | static int Expired(conn_t *c, uint64_t since) |
| 88 | { |
| 89 | uint64_t now = sysGetSystemTime(); |
| 90 | if (c->deadline_us && now > c->deadline_us) return 1; |
| 91 | return now - since > (uint64_t)c->read_timeout_s * 1000000; |
| 92 | } |
| 93 |
|
| 94 | typedef struct { |
| 95 | int tls; |
| 96 | int port; |
| 97 | char host[256]; |
| 98 | char path[4096]; |
| 99 | } url_t; |
| 100 |
|
| 101 | static void SetErr(char *err, int size, const char *fmt, ...) |
| 102 | { |
| 103 | va_list ap; |
| 104 | va_start(ap, fmt); |
| 105 | vsnprintf(err, size, tr(fmt), ap); // (in the UI's language) |
| 106 | va_end(ap); |
| 107 | } |
| 108 |
|
| 109 | static int PS3Entropy(void *data, unsigned char *out, size_t len, size_t *olen) |
| 110 | { |
| 111 | size_t done = 0; |
| 112 | (void)data; |
| 113 | while (done < len) { |
| 114 | size_t n = len - done > 0x1000 ? 0x1000 : len - done; |
| 115 | if (sysGetRandomNumber(out + done, n) != 0) |
| 116 | return MBEDTLS_ERR_ENTROPY_SOURCE_FAILED; |
| 117 | done += n; |
| 118 | } |
| 119 | *olen = len; |
| 120 | return 0; |
| 121 | } |
| 122 |
|
| 123 | // the shared part: the root certificates, parsed once |
| 124 | static int Tls_Roots(void) |
| 125 | { |
| 126 | int ret; |
| 127 |
|
| 128 | plat_mutex_lock(init_lock); |
| 129 | if (tls_state) { |
| 130 | plat_mutex_unlock(init_lock); |
| 131 | return tls_state > 0; |
| 132 | } |
| 133 | tls_state = -1; |
| 134 | mbedtls_x509_crt_init(&cacert); |
| 135 | #ifdef HAVE_CA_BUNDLE |
| 136 | ret = mbedtls_x509_crt_parse(&cacert, (const unsigned char *)ca_bundle_pem, sizeof(ca_bundle_pem)); |
| 137 | for (mbedtls_x509_crt *c = &cacert; c && c->raw.len; c = c->next) |
| 138 | num_roots++; |
| 139 | plat_log("https: %d root certificates (%d skipped)", num_roots, ret > 0 ? ret : 0); |
| 140 | #else |
| 141 | (void)ret; |
| 142 | #endif |
| 143 | // fail closed: no roots means no HTTPS, not HTTPS that trusts anyone |
| 144 | if (num_roots) |
| 145 | tls_state = 1; |
| 146 | else |
| 147 | plat_log("https: no root certificates: HTTPS is off"); |
| 148 | plat_mutex_unlock(init_lock); |
| 149 | return tls_state > 0; |
| 150 | } |
| 151 |
|
| 152 | static int BioSend(void *ctx, const unsigned char *buf, size_t len) |
| 153 | { |
| 154 | conn_t *c = ctx; |
| 155 | int n = send(c->fd, buf, len, 0); |
| 156 | if (n < 0) |
| 157 | return (errno == EAGAIN || errno == EWOULDBLOCK) ? MBEDTLS_ERR_SSL_WANT_WRITE : MBEDTLS_ERR_NET_SEND_FAILED; |
| 158 | return n; |
| 159 | } |
| 160 |
|
| 161 | static int BioRecv(void *ctx, unsigned char *buf, size_t len) |
| 162 | { |
| 163 | conn_t *c = ctx; |
| 164 | int n = recv(c->fd, buf, len, 0); |
| 165 | if (n < 0) { |
| 166 | if (errno == EAGAIN || errno == EWOULDBLOCK) |
| 167 | return MBEDTLS_ERR_SSL_TIMEOUT; |
| 168 | // [unverified] the errno lv2 gives when SO_RCVTIMEO runs out: if a stall lands |
| 169 | // here instead of as EAGAIN, every one-second stall ends the request |
| 170 | static int logged; |
| 171 | if (!logged) { logged = 1; plat_log("https: recv failed, errno %d (EAGAIN is %d)", errno, EAGAIN); } |
| 172 | return MBEDTLS_ERR_NET_RECV_FAILED; |
| 173 | } |
| 174 | return n; |
| 175 | } |
| 176 |
|
| 177 | static void Conn_Close(conn_t *c) |
| 178 | { |
| 179 | if (c->tls) { |
| 180 | mbedtls_ssl_close_notify(&c->ssl); |
| 181 | mbedtls_ssl_free(&c->ssl); |
| 182 | c->tls = 0; |
| 183 | } |
| 184 | if (c->tls_made) { |
| 185 | mbedtls_ssl_config_free(&c->conf); |
| 186 | mbedtls_ctr_drbg_free(&c->drbg); |
| 187 | mbedtls_entropy_free(&c->entropy); |
| 188 | c->tls_made = 0; |
| 189 | } |
| 190 | if (c->fd >= 0) { |
| 191 | close(c->fd); |
| 192 | c->fd = -1; |
| 193 | } |
| 194 | } |
| 195 |
|
| 196 | // numbers-and-dots addresses skip DNS |
| 197 | static int Resolve(const char *host, struct in_addr *out) |
| 198 | { |
| 199 | struct hostent *he; |
| 200 | int ok = 0; |
| 201 |
|
| 202 | if (inet_aton(host, out)) |
| 203 | return 1; |
| 204 | // lv2's resolver isn't known to be reentrant: one lookup at a time |
| 205 | plat_mutex_lock(init_lock); |
| 206 | he = gethostbyname(host); |
| 207 | if (he && he->h_addr_list && he->h_addr_list[0]) { |
| 208 | memcpy(out, he->h_addr_list[0], sizeof(*out)); |
| 209 | ok = 1; |
| 210 | } |
| 211 | plat_mutex_unlock(init_lock); |
| 212 | return ok; |
| 213 | } |
| 214 |
|
| 215 | static int Conn_Open(conn_t *c, const char *host, int port, int tls, int timeout_s, char *err, int errsize) |
| 216 | { |
| 217 | struct sockaddr_in sa; |
| 218 | struct timeval tv; |
| 219 | int ret; |
| 220 | uint64_t t0 = sysGetSystemTime(); |
| 221 |
|
| 222 | c->fd = -1; |
| 223 | c->tls = 0; |
| 224 | c->tls_made = 0; |
| 225 | c->pos = c->len = 0; |
| 226 |
|
| 227 | memset(&sa, 0, sizeof(sa)); |
| 228 | sa.sin_family = AF_INET; |
| 229 | sa.sin_port = htons(port); |
| 230 | if (!Resolve(host, &sa.sin_addr)) { |
| 231 | SetErr(err, errsize, "Can't find %s (is the PS3 online?)", host); |
| 232 | return 0; |
| 233 | } |
| 234 | if ((c->fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { |
| 235 | SetErr(err, errsize, "socket() failed (%d)", errno); |
| 236 | return 0; |
| 237 | } |
| 238 | c->read_timeout_s = timeout_s; |
| 239 | tv.tv_sec = SLICE_S; |
| 240 | tv.tv_usec = 0; |
| 241 | setsockopt(c->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); |
| 242 | tv.tv_sec = timeout_s; |
| 243 | setsockopt(c->fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); |
| 244 | // connect without blocking, so an address that never answers (a stale LAN IP) gives |
| 245 | // up after the timeout instead of the stack's minute and more; a cancel is seen too |
| 246 | int nb = 1, nbset = setsockopt(c->fd, SOL_SOCKET, SO_NBIO, &nb, sizeof(nb)) == 0; |
| 247 | if (connect(c->fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) { |
| 248 | int ok = 0, e = errno; |
| 249 | if (nbset) { |
| 250 | // (in progress, whatever errno says it as): wait until it's writable, a second at a time |
| 251 | for (;;) { |
| 252 | struct pollfd pf = { c->fd, POLLOUT, 0 }; |
| 253 | int r = poll(&pf, 1, SLICE_S * 1000); |
| 254 | if (r > 0) { |
| 255 | int soerr = 0; |
| 256 | socklen_t sl = sizeof(soerr); |
| 257 | ok = getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &soerr, &sl) != 0 || soerr == 0; |
| 258 | if (!ok) e = soerr; |
| 259 | break; |
| 260 | } |
| 261 | if (r < 0 || (c->cancel && *c->cancel) || sysGetSystemTime() - t0 > (uint64_t)timeout_s * 1000000) { |
| 262 | e = r < 0 ? errno : ETIMEDOUT; |
| 263 | break; |
| 264 | } |
| 265 | } |
| 266 | } |
| 267 | if (!ok) { |
| 268 | SetErr(err, errsize, "Can't connect to %s:%d (%d)", host, port, e); |
| 269 | Conn_Close(c); |
| 270 | return 0; |
| 271 | } |
| 272 | } |
| 273 | if (nbset) { |
| 274 | nb = 0; |
| 275 | setsockopt(c->fd, SOL_SOCKET, SO_NBIO, &nb, sizeof(nb)); |
| 276 | } |
| 277 | if (!tls) |
| 278 | return 1; |
| 279 |
|
| 280 | if (!Tls_Roots()) { |
| 281 | SetErr(err, errsize, "Secure connections are unavailable"); |
| 282 | Conn_Close(c); |
| 283 | return 0; |
| 284 | } |
| 285 | mbedtls_entropy_init(&c->entropy); |
| 286 | mbedtls_ctr_drbg_init(&c->drbg); |
| 287 | mbedtls_ssl_config_init(&c->conf); |
| 288 | c->tls_made = 1; |
| 289 | mbedtls_entropy_add_source(&c->entropy, PS3Entropy, NULL, 32, MBEDTLS_ENTROPY_SOURCE_STRONG); |
| 290 | if ((ret = mbedtls_ctr_drbg_seed(&c->drbg, mbedtls_entropy_func, &c->entropy, |
| 291 | (const unsigned char *)"ps3world", 8)) != 0 || |
| 292 | (ret = mbedtls_ssl_config_defaults(&c->conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, |
| 293 | MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { |
| 294 | SetErr(err, errsize, "TLS setup -0x%04x", -ret); |
| 295 | Conn_Close(c); |
| 296 | return 0; |
| 297 | } |
| 298 | mbedtls_ssl_conf_authmode(&c->conf, MBEDTLS_SSL_VERIFY_REQUIRED); |
| 299 | mbedtls_ssl_conf_ca_chain(&c->conf, &cacert, NULL); |
| 300 | mbedtls_ssl_conf_rng(&c->conf, mbedtls_ctr_drbg_random, &c->drbg); |
| 301 |
|
| 302 | mbedtls_ssl_init(&c->ssl); |
| 303 | c->tls = 1; |
| 304 | if ((ret = mbedtls_ssl_setup(&c->ssl, &c->conf)) != 0 || (ret = mbedtls_ssl_set_hostname(&c->ssl, host)) != 0) { |
| 305 | SetErr(err, errsize, "TLS setup -0x%04x", -ret); |
| 306 | Conn_Close(c); |
| 307 | return 0; |
| 308 | } |
| 309 | mbedtls_ssl_set_bio(&c->ssl, c, BioSend, BioRecv, NULL); |
| 310 | while ((ret = mbedtls_ssl_handshake(&c->ssl)) != 0) { |
| 311 | if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) |
| 312 | continue; |
| 313 | if (ret == MBEDTLS_ERR_SSL_TIMEOUT && !(c->cancel && *c->cancel) && !Expired(c, t0)) |
| 314 | continue; // a quiet second: still within the timeout |
| 315 | if (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) { |
| 316 | char info[256]; |
| 317 | mbedtls_x509_crt_verify_info(info, sizeof(info), "", mbedtls_ssl_get_verify_result(&c->ssl)); |
| 318 | for (char *p = info; *p; p++) if (*p == '\n') *p = ' '; |
| 319 | plat_log("https: %s: certificate rejected: %s", host, info); |
| 320 | SetErr(err, errsize, "%s's certificate was rejected (is the PS3's clock right?)", host); |
| 321 | } else { |
| 322 | char e[96]; |
| 323 | mbedtls_strerror(ret, e, sizeof(e)); |
| 324 | plat_log("https: %s: handshake -0x%04x %s", host, -ret, e); |
| 325 | SetErr(err, errsize, "Secure connection to %s failed (-0x%04x)", host, -ret); |
| 326 | } |
| 327 | Conn_Close(c); |
| 328 | return 0; |
| 329 | } |
| 330 | return 1; |
| 331 | } |
| 332 |
|
| 333 | static int Conn_Write(conn_t *c, const char *buf, int len) |
| 334 | { |
| 335 | while (len > 0) { |
| 336 | int n = c->tls ? mbedtls_ssl_write(&c->ssl, (const unsigned char *)buf, len) : send(c->fd, buf, len, 0); |
| 337 | if (c->tls && (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE)) |
| 338 | continue; |
| 339 | if (n <= 0) |
| 340 | return 0; |
| 341 | buf += n; |
| 342 | len -= n; |
| 343 | } |
| 344 | return 1; |
| 345 | } |
| 346 |
|
| 347 | // refills c->buf: >0 bytes, 0 at the end of the stream, <0 an error (-2 cancelled) |
| 348 | static int Conn_Fill(conn_t *c) |
| 349 | { |
| 350 | int n; |
| 351 | uint64_t since = sysGetSystemTime(); |
| 352 | for (;;) { |
| 353 | if (c->cancel && *c->cancel) |
| 354 | return -2; |
| 355 | n = c->tls ? mbedtls_ssl_read(&c->ssl, c->buf, sizeof(c->buf)) : recv(c->fd, c->buf, sizeof(c->buf), 0); |
| 356 | if (c->tls) { |
| 357 | if (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE) |
| 358 | continue; |
| 359 | if (n == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) |
| 360 | n = 0; |
| 361 | } |
| 362 | // a quiet second (recv waits one at a time): again, unless the time's up |
| 363 | int quiet = c->tls ? n == MBEDTLS_ERR_SSL_TIMEOUT : (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)); |
| 364 | if (quiet && !Expired(c, since)) |
| 365 | continue; |
| 366 | break; |
| 367 | } |
| 368 | c->pos = 0; |
| 369 | c->len = n > 0 ? n : 0; |
| 370 | return n; |
| 371 | } |
| 372 |
|
| 373 | static int Conn_Line(conn_t *c, char *out, int size) |
| 374 | { |
| 375 | int o = 0; |
| 376 | for (;;) { |
| 377 | if (c->pos >= c->len && Conn_Fill(c) <= 0) |
| 378 | return 0; |
| 379 | char ch = c->buf[c->pos++]; |
| 380 | if (ch == '\n') { |
| 381 | if (o && out[o - 1] == '\r') o--; |
| 382 | out[o] = 0; |
| 383 | return 1; |
| 384 | } |
| 385 | if (o < size - 1) |
| 386 | out[o++] = ch; |
| 387 | } |
| 388 | } |
| 389 |
|
| 390 | static int Url_Parse(const char *in, url_t *u) |
| 391 | { |
| 392 | const char *p, *slash, *colon; |
| 393 | int hlen, o = 0; |
| 394 |
|
| 395 | if (!strncasecmp(in, "https://", 8)) { u->tls = 1; u->port = 443; p = in + 8; } |
| 396 | else if (!strncasecmp(in, "http://", 7)) { u->tls = 0; u->port = 80; p = in + 7; } |
| 397 | else return 0; |
| 398 |
|
| 399 | slash = strchr(p, '/'); |
| 400 | if (!slash) slash = p + strlen(p); |
| 401 | colon = memchr(p, ':', slash - p); |
| 402 | hlen = (colon ? colon : slash) - p; |
| 403 | if (hlen <= 0 || hlen >= (int)sizeof(u->host)) |
| 404 | return 0; |
| 405 | memcpy(u->host, p, hlen); |
| 406 | u->host[hlen] = 0; |
| 407 | for (int i = 0; i < hlen; i++) // a host name, not a way to slip more into the request |
| 408 | if ((unsigned char)u->host[i] <= 0x20 || u->host[i] == 0x7f || u->host[i] == '/' || u->host[i] == '@') |
| 409 | return 0; |
| 410 | if (colon) |
| 411 | u->port = atoi(colon + 1); |
| 412 | if (!*slash) slash = "/"; |
| 413 | // spaces, control characters (a CR/LF in a key from a server would start a |
| 414 | // new header) and non-ASCII bytes all go percent-encoded |
| 415 | for (; *slash && o < (int)sizeof(u->path) - 4; slash++) { |
| 416 | unsigned char ch = (unsigned char)*slash; |
| 417 | if (ch <= 0x20 || ch >= 0x7f) { snprintf(u->path + o, 4, "%%%02X", ch); o += 3; } |
| 418 | else u->path[o++] = *slash; |
| 419 | } |
| 420 | u->path[o] = 0; |
| 421 | return 1; |
| 422 | } |
| 423 |
|
| 424 | static int Net_Up(void) |
| 425 | { |
| 426 | // main_ps3.c brings the network up at start-up; this is the check |
| 427 | extern int ps3_net_ok; |
| 428 | return ps3_net_ok; |
| 429 | } |
| 430 |
|
| 431 | void http_init(void) |
| 432 | { |
| 433 | init_lock = plat_mutex_new(); |
| 434 | } |
| 435 |
|
| 436 | int http_request(const http_req_t *req, http_res_t *res) |
| 437 | { |
| 438 | conn_t *c; |
| 439 | url_t *u; |
| 440 | char *line, *location, *head; |
| 441 | int redirects = 0, status, chunked, ok = 0; |
| 442 | int timeout = req->timeout_s > 0 ? req->timeout_s : 30; |
| 443 | const char *method = req->method ? req->method : "GET"; |
| 444 | long long length, got; |
| 445 |
|
| 446 | memset(res, 0, sizeof(*res)); |
| 447 | res->length = -1; |
| 448 |
|
| 449 | if (!Net_Up()) { |
| 450 | SetErr(res->err, sizeof(res->err), "%s", tr("The network isn't available")); |
| 451 | return 0; |
| 452 | } |
| 453 |
|
| 454 | c = calloc(1, sizeof(*c)); |
| 455 | u = calloc(1, sizeof(*u)); |
| 456 | line = malloc(4096); |
| 457 | location = malloc(4096); |
| 458 | head = malloc(8192); |
| 459 | if (!c || !u || !line || !location || !head) { |
| 460 | SetErr(res->err, sizeof(res->err), "%s", tr("Out of memory")); |
| 461 | goto out; |
| 462 | } |
| 463 | c->fd = -1; |
| 464 | snprintf(location, 4096, "%s", req->url); |
| 465 |
|
| 466 | again: |
| 467 | if (!Url_Parse(location, u)) { |
| 468 | SetErr(res->err, sizeof(res->err), "%s", tr("Bad URL")); |
| 469 | goto out; |
| 470 | } |
| 471 | c->cancel = req->cancel; |
| 472 | if (req->total_s > 0 && !c->deadline_us) |
| 473 | c->deadline_us = sysGetSystemTime() + (uint64_t)req->total_s * 1000000; // across redirects too |
| 474 | if (!Conn_Open(c, u->host, u->port, u->tls, timeout, res->err, sizeof(res->err))) |
| 475 | goto out; |
| 476 |
|
| 477 | { |
| 478 | int n = snprintf(head, 8192, "%s %s HTTP/1.1\r\nHost: %s\r\nUser-Agent: PS3World/" APP_VERSION |
| 479 | " (PLAYSTATION 3)\r\nAccept-Encoding: identity\r\nConnection: close\r\n%s", |
| 480 | method, u->path, u->host, req->headers ? req->headers : ""); |
| 481 | if (req->body) |
| 482 | n += snprintf(head + n, 8192 - n, "Content-Length: %d\r\n", req->body_len); |
| 483 | snprintf(head + n, 8192 - n, "\r\n"); |
| 484 | } |
| 485 | if (!Conn_Write(c, head, strlen(head)) || |
| 486 | (req->body && req->body_len && !Conn_Write(c, req->body, req->body_len))) { |
| 487 | SetErr(res->err, sizeof(res->err), tr("Request to %s failed"), u->host); |
| 488 | goto done; |
| 489 | } |
| 490 |
|
| 491 | if (!Conn_Line(c, line, 4096) || sscanf(line, "HTTP/%*d.%*d %d", &status) != 1) { |
| 492 | SetErr(res->err, sizeof(res->err), tr("No reply from %s"), u->host); |
| 493 | goto done; |
| 494 | } |
| 495 | length = -1; |
| 496 | chunked = 0; |
| 497 | location[0] = 0; |
| 498 | res->content_type[0] = 0; |
| 499 | for (;;) { |
| 500 | if (!Conn_Line(c, line, 4096)) { |
| 501 | SetErr(res->err, sizeof(res->err), tr("Reply from %s cut short"), u->host); |
| 502 | goto done; |
| 503 | } |
| 504 | if (!line[0]) |
| 505 | break; |
| 506 | if (!strncasecmp(line, "Content-Length:", 15)) { |
| 507 | char *end; |
| 508 | length = strtoll(line + 15, &end, 10); |
| 509 | if (end == line + 15 || length < 0) { // not a length: don't guess |
| 510 | SetErr(res->err, sizeof(res->err), "%s", tr("The reply wasn't understood")); |
| 511 | goto done; |
| 512 | } |
| 513 | } |
| 514 | else if (!strncasecmp(line, "Transfer-Encoding:", 18)) { |
| 515 | for (char *q = line + 18; *q; q++) if (*q >= 'A' && *q <= 'Z') *q += 32; // names are case-insensitive |
| 516 | if (strstr(line + 18, "chunked")) chunked = 1; |
| 517 | } |
| 518 | else if (!strncasecmp(line, "Content-Type:", 13)) { |
| 519 | const char *v = line + 13; |
| 520 | while (*v == ' ') v++; |
| 521 | snprintf(res->content_type, sizeof(res->content_type), "%s", v); |
| 522 | } else if (!strncasecmp(line, "Location:", 9)) { |
| 523 | const char *v = line + 9; |
| 524 | while (*v == ' ') v++; |
| 525 | if (*v == '/') |
| 526 | snprintf(location, 4096, "%s://%s:%d%s", u->tls ? "https" : "http", u->host, u->port, v); |
| 527 | else |
| 528 | snprintf(location, 4096, "%s", v); |
| 529 | } |
| 530 | } |
| 531 |
|
| 532 | res->status = status; |
| 533 | if (status >= 300 && status < 400 && location[0] && !req->body && !req->no_redirect) { |
| 534 | Conn_Close(c); |
| 535 | if (u->tls && strncasecmp(location, "https://", 8)) { |
| 536 | SetErr(res->err, sizeof(res->err), "%s", tr("Insecure redirect refused")); |
| 537 | goto out; |
| 538 | } |
| 539 | // the same host only: anything served from elsewhere isn't what the catalogue checked |
| 540 | url_t *nu = malloc(sizeof(*nu)); |
| 541 | int same = nu && Url_Parse(location, nu) && !strcasecmp(nu->host, u->host) && nu->port == u->port && nu->tls == u->tls; |
| 542 | free(nu); |
| 543 | if (!same) { |
| 544 | SetErr(res->err, sizeof(res->err), "%s", tr("Redirect to another host refused")); |
| 545 | goto out; |
| 546 | } |
| 547 | if (++redirects > MAX_REDIRECTS) { |
| 548 | SetErr(res->err, sizeof(res->err), "%s", tr("Too many redirects")); |
| 549 | goto out; |
| 550 | } |
| 551 | goto again; |
| 552 | } |
| 553 | res->length = length; |
| 554 |
|
| 555 | got = 0; |
| 556 | for (;;) { |
| 557 | long long left = chunked ? 0 : (length >= 0 ? length - got : -1); |
| 558 | if (chunked) { |
| 559 | if (!Conn_Line(c, line, 4096)) { SetErr(res->err, sizeof(res->err), "%s", tr("Download cut short")); goto done; } |
| 560 | // a chunk's size in hex, maybe with ";extensions"; anything else is a broken reply |
| 561 | char *end; |
| 562 | left = strtoll(line, &end, 16); |
| 563 | while (*end == ' ' || *end == '\t') end++; |
| 564 | if (!isxdigit((unsigned char)line[0]) || end == line || left < 0 || left > (1ll << 40) || (*end && *end != ';')) { |
| 565 | SetErr(res->err, sizeof(res->err), "%s", tr("The reply wasn't understood")); |
| 566 | goto done; |
| 567 | } |
| 568 | if (left == 0) { ok = 1; break; } |
| 569 | } |
| 570 | if (left == 0) { ok = 1; break; } |
| 571 | while (left != 0) { |
| 572 | int n; |
| 573 | if (c->pos >= c->len) { |
| 574 | int r = Conn_Fill(c); |
| 575 | if (r == -2) { SetErr(res->err, sizeof(res->err), "%s", tr("Cancelled")); goto done; } |
| 576 | if (r <= 0) { |
| 577 | if (left < 0 && r == 0) { ok = 1; goto done; } // no length: the end is the close |
| 578 | SetErr(res->err, sizeof(res->err), "%s", tr("Download cut short")); |
| 579 | goto done; |
| 580 | } |
| 581 | } |
| 582 | n = c->len - c->pos; |
| 583 | if (left > 0 && n > left) |
| 584 | n = (int)left; |
| 585 | if (req->sink && !req->sink(req->ctx, c->buf + c->pos, n)) { |
| 586 | if (!res->err[0]) SetErr(res->err, sizeof(res->err), "%s", tr("Stopped")); |
| 587 | goto done; |
| 588 | } |
| 589 | c->pos += n; |
| 590 | got += n; |
| 591 | if (left > 0) |
| 592 | left -= n; |
| 593 | } |
| 594 | if (!chunked) { ok = 1; break; } |
| 595 | // the CRLF after a chunk, and nothing else |
| 596 | if (!Conn_Line(c, line, 4096) || line[0]) { |
| 597 | SetErr(res->err, sizeof(res->err), "%s", tr("The reply wasn't understood")); |
| 598 | goto done; |
| 599 | } |
| 600 | } |
| 601 |
|
| 602 | done: |
| 603 | Conn_Close(c); |
| 604 | out: |
| 605 | free(c); |
| 606 | free(u); |
| 607 | free(line); |
| 608 | free(location); |
| 609 | free(head); |
| 610 | return ok; |
| 611 | } |