611 lines18 KBC
1/*
2http_ps3.c: HTTP/1.1 over the PS3's sockets, with TLS 1.2 from mbedTLS.
3
4From 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
9The system's own HTTPS (cellHttps) only speaks the TLS of ~2015, which
10current certificates refuse. mbedTLS checks servers against the Mozilla
11roots (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
59static plat_mutex_t *init_lock; // TLS roots, DNS
60static int tls_state; // 0 not tried, 1 ok, -1 failed
61static mbedtls_x509_crt cacert; // read-only once parsed: shared
62static int num_roots;
63
64typedef 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)
80mbedtls_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
87static 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
94typedef struct {
95 int tls;
96 int port;
97 char host[256];
98 char path[4096];
99} url_t;
100
101static 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
109static 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
124static 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
152static 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
161static 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
177static 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
197static 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
215static 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
333static 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)
348static 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
373static 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
390static 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
424static 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
431void http_init(void)
432{
433 init_lock = plat_mutex_new();
434}
435
436int 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
466again:
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
602done:
603 Conn_Close(c);
604out:
605 free(c);
606 free(u);
607 free(line);
608 free(location);
609 free(head);
610 return ok;
611}