Line data Source code
1 : #define _GNU_SOURCE
2 : #include "fd_sshttp_private.h"
3 : #include "fd_ssarchive.h"
4 :
5 : #include "../../../waltz/http/picohttpparser.h"
6 : #include "../../../waltz/openssl/fd_openssl_tile.h"
7 : #include "../../../util/log/fd_log.h"
8 : #include "../../../flamenco/types/fd_types_custom.h"
9 :
10 : #include <unistd.h>
11 : #include <errno.h>
12 : #include <poll.h>
13 : #include <stdlib.h>
14 :
15 : #include <sys/socket.h>
16 : #include <netinet/in.h>
17 :
18 : /* FIXME: Cleanup / standardize all the error logging. */
19 :
20 : _Bool fd_sshttp_fuzz = 0;
21 :
22 : FD_FN_CONST ulong
23 0 : fd_sshttp_align( void ) {
24 0 : return alignof(fd_sshttp_t);
25 0 : }
26 :
27 : FD_FN_CONST ulong
28 0 : fd_sshttp_footprint( void ) {
29 0 : ulong l;
30 0 : l = FD_LAYOUT_INIT;
31 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_sshttp_t), sizeof(fd_sshttp_t) );
32 0 : return FD_LAYOUT_FINI( l, fd_sshttp_align() );
33 0 : }
34 :
35 : void *
36 0 : fd_sshttp_new( void * shmem ) {
37 0 : if( FD_UNLIKELY( !shmem ) ) {
38 0 : FD_LOG_WARNING(( "NULL shmem" ));
39 0 : return NULL;
40 0 : }
41 :
42 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_sshttp_align() ) ) ) {
43 0 : FD_LOG_WARNING(( "unaligned shmem" ));
44 0 : return NULL;
45 0 : }
46 :
47 0 : FD_SCRATCH_ALLOC_INIT( l, shmem );
48 0 : fd_sshttp_t * sshttp = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sshttp_t), sizeof(fd_sshttp_t) );
49 :
50 0 : sshttp->state = FD_SSHTTP_STATE_INIT;
51 0 : sshttp->content_len = 0UL;
52 0 : fd_cstr_fini( sshttp->snapshot_name );
53 :
54 0 : #if FD_HAS_OPENSSL
55 0 : sshttp->ssl = NULL;
56 0 : sshttp->ssl_ctx = NULL;
57 :
58 0 : if( !fd_sshttp_fuzz ) {
59 0 : SSL_CTX * ssl_ctx = SSL_CTX_new( TLS_client_method() );
60 0 : if( FD_UNLIKELY( !ssl_ctx ) ) {
61 0 : FD_LOG_ERR(( "SSL_CTX_new failed" ));
62 0 : }
63 :
64 0 : if( FD_UNLIKELY( !SSL_CTX_set_min_proto_version( ssl_ctx, TLS1_3_VERSION ) ) ) {
65 0 : FD_LOG_ERR(( "SSL_CTX_set_min_proto_version(ssl_ctx,TLS1_3_VERSION) failed" ));
66 0 : }
67 :
68 : /* transfering ownership of ssl_ctx by assignment */
69 0 : sshttp->ssl_ctx = ssl_ctx;
70 :
71 0 : fd_ossl_load_certs( sshttp->ssl_ctx );
72 0 : }
73 0 : #endif
74 :
75 0 : FD_COMPILER_MFENCE();
76 0 : sshttp->magic = FD_SSHTTP_MAGIC;
77 0 : FD_COMPILER_MFENCE();
78 :
79 0 : return (void *)sshttp;
80 0 : }
81 :
82 : fd_sshttp_t *
83 0 : fd_sshttp_join( void * shhttp ) {
84 0 : if( FD_UNLIKELY( !shhttp ) ) {
85 0 : FD_LOG_WARNING(( "NULL shhttp" ));
86 0 : return NULL;
87 0 : }
88 :
89 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shhttp, fd_sshttp_align() ) ) ) {
90 0 : FD_LOG_WARNING(( "misaligned shhttp" ));
91 0 : return NULL;
92 0 : }
93 :
94 0 : fd_sshttp_t * sshttp = (fd_sshttp_t *)shhttp;
95 :
96 0 : if( FD_UNLIKELY( sshttp->magic!=FD_SSHTTP_MAGIC ) ) {
97 0 : FD_LOG_WARNING(( "bad magic" ));
98 0 : return NULL;
99 0 : }
100 :
101 0 : return sshttp;
102 0 : }
103 :
104 : #if FD_HAS_OPENSSL
105 : static void
106 0 : http_init_ssl( fd_sshttp_t * http ) {
107 0 : FD_TEST( http->hostname );
108 0 : FD_TEST( http->ssl_ctx );
109 :
110 0 : http->ssl = SSL_new( http->ssl_ctx );
111 0 : if( FD_UNLIKELY( !http->ssl ) ) {
112 0 : FD_LOG_ERR(( "SSL_new failed" ));
113 0 : }
114 :
115 0 : static uchar const alpn_protos[] = { 8, 'h', 't', 't', 'p', '/', '1', '.', '1' };
116 0 : int alpn_res = SSL_set_alpn_protos( http->ssl, alpn_protos, sizeof(alpn_protos) );
117 0 : if( FD_UNLIKELY( alpn_res!=0 ) ) {
118 0 : FD_LOG_ERR(( "SSL_set_alpn_protos failed (%d)", alpn_res ));
119 0 : }
120 :
121 : /* set SNI */
122 0 : int set1_host_res = SSL_set1_host( http->ssl, http->hostname );
123 0 : if( FD_UNLIKELY( !set1_host_res ) ) {
124 0 : FD_LOG_ERR(( "SSL_set1_host failed (%d)", set1_host_res ));
125 0 : }
126 0 : }
127 : #endif
128 :
129 : void
130 : fd_sshttp_init( fd_sshttp_t * http,
131 : fd_ip4_port_t addr,
132 : char const * hostname,
133 : int is_https,
134 : char const * path,
135 : ulong path_len,
136 0 : long now ) {
137 0 : FD_TEST( http->state==FD_SSHTTP_STATE_INIT );
138 :
139 0 : http->hostname = hostname;
140 0 : http->is_https = is_https;
141 :
142 0 : if( FD_LIKELY( is_https ) ) {
143 0 : #if FD_HAS_OPENSSL
144 0 : http_init_ssl( http );
145 : #else
146 : FD_LOG_ERR(( "cannot make HTTPS connection without OpenSSL" ));
147 : #endif
148 0 : }
149 :
150 0 : http->hops = 4UL;
151 0 : http->request_sent = 0UL;
152 0 : if( FD_LIKELY( is_https ) ) {
153 0 : FD_TEST( fd_cstr_printf_check( http->request, sizeof(http->request), &http->request_len,
154 0 : "GET %.*s HTTP/1.1\r\n"
155 0 : "User-Agent: Firedancer\r\n"
156 0 : "Accept: */*\r\n"
157 0 : "Accept-Encoding: identity\r\n"
158 0 : "Host: %s\r\n\r\n",
159 0 : (int)path_len, path, hostname ) );
160 0 : } else {
161 0 : FD_TEST( fd_cstr_printf_check( http->request, sizeof(http->request), &http->request_len,
162 0 : "GET %.*s HTTP/1.1\r\n"
163 0 : "User-Agent: Firedancer\r\n"
164 0 : "Accept: */*\r\n"
165 0 : "Accept-Encoding: identity\r\n"
166 0 : "Host: " FD_IP4_ADDR_FMT "\r\n\r\n",
167 0 : (int)path_len, path, FD_IP4_ADDR_FMT_ARGS( addr.addr ) ) );
168 0 : }
169 :
170 0 : http->response_len = 0UL;
171 0 : http->content_len = 0UL;
172 0 : http->content_read = 0UL;
173 0 : http->empty_recvs = 0UL;
174 :
175 0 : http->addr = addr;
176 0 : http->sockfd = socket( AF_INET, SOCK_STREAM|SOCK_NONBLOCK, 0 );
177 0 : if( FD_UNLIKELY( -1==http->sockfd ) ) FD_LOG_ERR(( "socket() failed (%d-%s)", errno, fd_io_strerror( errno ) ));
178 :
179 0 : struct sockaddr_in addr_in = {
180 0 : .sin_family = AF_INET,
181 0 : .sin_port = addr.port,
182 0 : .sin_addr = { .s_addr = addr.addr }
183 0 : };
184 :
185 0 : if( FD_LIKELY( -1==connect( http->sockfd, fd_type_pun_const( &addr_in ), sizeof(addr_in) ) ) ) {
186 0 : if( FD_UNLIKELY( errno!=EINPROGRESS ) ) {
187 0 : if( FD_UNLIKELY( -1==close( http->sockfd ) ) ) FD_LOG_ERR(( "close() failed (%d-%s)", errno, fd_io_strerror( errno ) ));
188 0 : }
189 0 : }
190 :
191 0 : if( FD_LIKELY( is_https ) ) {
192 0 : http->state = FD_SSHTTP_STATE_CONNECT;
193 0 : http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
194 0 : } else {
195 0 : http->state = FD_SSHTTP_STATE_REQ;
196 0 : http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
197 0 : }
198 0 : }
199 :
200 : #if FD_HAS_OPENSSL
201 : static int
202 : http_connect_ssl( fd_sshttp_t * http,
203 0 : long now ) {
204 0 : if( FD_UNLIKELY( now>http->deadline ) ) {
205 0 : FD_LOG_WARNING(("deadline exceeded during connect"));
206 0 : fd_sshttp_cancel( http );
207 0 : return FD_SSHTTP_ADVANCE_ERROR;
208 0 : }
209 :
210 0 : FD_TEST( http->ssl );
211 0 : SSL_set_fd( http->ssl, http->sockfd );
212 0 : int ssl_err = SSL_connect( http->ssl );
213 0 : if( FD_UNLIKELY( ssl_err!=1 ) ) {
214 0 : int ssl_err_code = SSL_get_error( http->ssl, ssl_err );
215 0 : if( FD_UNLIKELY( ssl_err_code!=SSL_ERROR_WANT_READ && ssl_err_code!=SSL_ERROR_WANT_WRITE ) ) {
216 0 : FD_LOG_WARNING(( "SSL_connect failed (%d)", ssl_err ));
217 0 : SSL_free( http->ssl );
218 0 : http->ssl = NULL;
219 0 : return FD_SSHTTP_ADVANCE_ERROR;
220 0 : }
221 : /* in progress */
222 0 : return FD_SSHTTP_ADVANCE_AGAIN;
223 0 : }
224 :
225 0 : http->state = FD_SSHTTP_STATE_REQ;
226 0 : http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
227 0 : return FD_SSHTTP_ADVANCE_AGAIN;
228 0 : }
229 :
230 : static int
231 : http_shutdown_ssl( fd_sshttp_t * http,
232 0 : long now ) {
233 0 : if( FD_UNLIKELY( now>http->deadline ) ) {
234 0 : FD_LOG_WARNING(("deadline exceeded during shutdown"));
235 0 : fd_sshttp_cancel( http );
236 0 : return FD_SSHTTP_ADVANCE_ERROR;
237 0 : }
238 :
239 0 : int res = SSL_shutdown( http->ssl );
240 0 : if( FD_LIKELY( res<=0 ) ) {
241 0 : int ssl_err_code = SSL_get_error( http->ssl, res );
242 0 : if( FD_UNLIKELY( ssl_err_code!=SSL_ERROR_WANT_READ && ssl_err_code!=SSL_ERROR_WANT_WRITE && res!=0 ) ) {
243 0 : FD_LOG_WARNING(( "SSL_shutdown failed (%d)", ssl_err_code ));
244 0 : SSL_free( http->ssl );
245 0 : http->ssl = NULL;
246 0 : return FD_SSHTTP_ADVANCE_ERROR;
247 0 : }
248 :
249 0 : return FD_SSHTTP_ADVANCE_AGAIN;
250 0 : }
251 :
252 0 : http->state = http->next_state;
253 0 : return FD_SSHTTP_ADVANCE_AGAIN;
254 0 : }
255 :
256 : static long
257 : http_recv_ssl( fd_sshttp_t * http,
258 : void * buf,
259 0 : ulong bufsz ) {
260 0 : int read_res = SSL_read( http->ssl, buf, (int)bufsz );
261 0 : if( FD_UNLIKELY( read_res<=0 ) ) {
262 0 : int ssl_err = SSL_get_error( http->ssl, read_res );
263 :
264 0 : if( FD_UNLIKELY( ssl_err!=SSL_ERROR_WANT_READ && ssl_err!=SSL_ERROR_WANT_WRITE ) ) {
265 0 : FD_LOG_WARNING(( "SSL_read failed (%d)", ssl_err ));
266 0 : return FD_SSHTTP_ADVANCE_ERROR;
267 0 : }
268 :
269 0 : return FD_SSHTTP_ADVANCE_AGAIN;
270 0 : }
271 :
272 0 : return (long)read_res;
273 0 : }
274 :
275 : static long
276 : http_send_ssl( fd_sshttp_t * http,
277 : void * buf,
278 0 : ulong bufsz ) {
279 0 : int write_res = SSL_write( http->ssl, buf, (int)bufsz );
280 0 : if( FD_UNLIKELY( write_res<=0 ) ) {
281 0 : int ssl_err = SSL_get_error( http->ssl, write_res );
282 :
283 0 : if( FD_UNLIKELY( ssl_err!=SSL_ERROR_WANT_READ && ssl_err!=SSL_ERROR_WANT_WRITE ) ) {
284 0 : FD_LOG_WARNING(( "SSL_write failed (%d)", ssl_err ));
285 0 : return FD_SSHTTP_ADVANCE_ERROR;
286 0 : }
287 :
288 0 : return FD_SSHTTP_ADVANCE_AGAIN;
289 0 : }
290 :
291 0 : return (long)write_res;
292 0 : }
293 :
294 : static int
295 : setup_redirect( fd_sshttp_t * http,
296 0 : long now ) {
297 0 : fd_sshttp_cancel( http );
298 0 : fd_sshttp_init( http, http->addr, http->hostname, http->is_https, http->location, http->location_len, now );
299 0 : return FD_SSHTTP_ADVANCE_AGAIN;
300 0 : }
301 :
302 : #endif
303 :
304 : void
305 0 : fd_sshttp_cancel( fd_sshttp_t * http ) {
306 0 : if( FD_LIKELY( http->state!=FD_SSHTTP_STATE_INIT && -1!=http->sockfd ) ) {
307 0 : if( FD_UNLIKELY( -1==close( http->sockfd ) ) ) FD_LOG_ERR(( "close() failed (%d-%s)", errno, fd_io_strerror( errno ) ));
308 0 : http->sockfd = -1;
309 0 : }
310 0 : http->state = FD_SSHTTP_STATE_INIT;
311 :
312 0 : #if FD_HAS_OPENSSL
313 0 : if( FD_LIKELY( http->ssl ) ) {
314 0 : SSL_free( http->ssl );
315 0 : http->ssl = NULL;
316 0 : }
317 0 : #endif
318 0 : }
319 :
320 : static long
321 : http_send( fd_sshttp_t * http,
322 : void * buf,
323 0 : ulong bufsz ) {
324 0 : #if FD_HAS_OPENSSL
325 0 : if( FD_LIKELY( http->is_https ) ) return http_send_ssl( http, buf, bufsz );
326 0 : #endif
327 :
328 0 : long sent = sendto( http->sockfd, buf, bufsz, 0, NULL, 0 );
329 0 : if( FD_UNLIKELY( -1==sent && errno==EAGAIN ) ) return FD_SSHTTP_ADVANCE_AGAIN;
330 0 : else if( FD_UNLIKELY( -1==sent ) ) {
331 0 : FD_LOG_WARNING(( "sendto() failed (%d-%s)", errno, fd_io_strerror( errno ) ));
332 0 : fd_sshttp_cancel( http );
333 0 : return FD_SSHTTP_ADVANCE_ERROR;
334 0 : }
335 :
336 0 : return sent;
337 0 : }
338 :
339 : static long
340 : http_recv( fd_sshttp_t * http,
341 : void * buf,
342 0 : ulong bufsz ) {
343 0 : #if FD_HAS_OPENSSL
344 0 : if( FD_LIKELY( http->is_https ) ) return http_recv_ssl( http, buf, bufsz );
345 0 : #endif
346 :
347 0 : long read = recvfrom( http->sockfd, buf, bufsz, 0, NULL, NULL );
348 0 : if( FD_UNLIKELY( -1==read && errno==EAGAIN ) ) {
349 0 : if( FD_UNLIKELY( ++http->empty_recvs>8UL && !fd_sshttp_fuzz ) ) {
350 : /* If we have gone several iterations without having any data to
351 : read, sleep the thread for up to one millisecond, or until
352 : the socket is readable again, whichever comes first. */
353 0 : struct pollfd pfd = {
354 0 : .fd = http->sockfd,
355 0 : .events = POLLIN,
356 0 : };
357 0 : if( -1==fd_syscall_poll( &pfd, 1 /*fds*/, 1 /*ms*/ ) ) {
358 0 : FD_LOG_ERR(("fd_syscall_poll() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
359 0 : }
360 0 : }
361 0 : return FD_SSHTTP_ADVANCE_AGAIN;
362 0 : } else if( FD_UNLIKELY( -1==read ) ) {
363 0 : fd_sshttp_cancel( http );
364 0 : return FD_SSHTTP_ADVANCE_ERROR;
365 0 : }
366 0 : http->empty_recvs = 0UL;
367 :
368 0 : return read;
369 0 : }
370 :
371 : static int
372 : send_request( fd_sshttp_t * http,
373 0 : long now ) {
374 0 : if( FD_UNLIKELY( now>http->deadline ) ) {
375 0 : fd_sshttp_cancel( http );
376 0 : return FD_SSHTTP_ADVANCE_ERROR;
377 0 : }
378 :
379 0 : long sent = http_send( http, http->request+http->request_sent, http->request_len-http->request_sent );
380 0 : if( FD_UNLIKELY( sent<=0 ) ) return (int)sent;
381 :
382 0 : http->request_sent += (ulong)sent;
383 0 : if( FD_UNLIKELY( http->request_sent==http->request_len ) ) {
384 0 : http->state = FD_SSHTTP_STATE_RESP;
385 0 : http->response_len = 0UL;
386 0 : http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
387 0 : }
388 :
389 0 : return FD_SSHTTP_ADVANCE_AGAIN;
390 0 : }
391 :
392 : static int
393 : follow_redirect( fd_sshttp_t * http,
394 : struct phr_header * headers,
395 : ulong header_cnt,
396 0 : long now ) {
397 0 : if( FD_UNLIKELY( !http->hops ) ) {
398 0 : FD_LOG_WARNING(( "too many redirects" ));
399 0 : fd_sshttp_cancel( http );
400 0 : return FD_SSHTTP_ADVANCE_ERROR;
401 0 : }
402 :
403 0 : http->hops--;
404 :
405 0 : ulong location_len;
406 0 : char const * location = NULL;
407 :
408 0 : for( ulong i=0UL; i<header_cnt; i++ ) {
409 0 : if( FD_UNLIKELY( !strncasecmp( headers[ i ].name, "location", headers[ i ].name_len ) ) ) {
410 0 : if( FD_UNLIKELY( !headers [ i ].value_len || headers[ i ].value[ 0 ]!='/' ) ) {
411 0 : FD_LOG_WARNING(( "invalid location header `%.*s`", (int)headers[ i ].value_len, headers[ i ].value ));
412 0 : fd_sshttp_cancel( http );
413 0 : return FD_SSHTTP_ADVANCE_ERROR;
414 0 : }
415 :
416 0 : location_len = headers[ i ].value_len;
417 0 : location = headers[ i ].value;
418 :
419 0 : if( FD_UNLIKELY( location_len>=PATH_MAX-1UL ) ) {
420 0 : fd_sshttp_cancel( http );
421 0 : return FD_SSHTTP_ADVANCE_ERROR;
422 0 : }
423 :
424 0 : char snapshot_name[ PATH_MAX ];
425 0 : fd_memcpy( snapshot_name, location+1UL, location_len-1UL );
426 0 : snapshot_name[ location_len-1UL ] = '\0';
427 :
428 0 : int is_zstd;
429 0 : ulong full_entry_slot, incremental_entry_slot;
430 0 : uchar decoded_hash[ FD_HASH_FOOTPRINT ];
431 0 : int err = fd_ssarchive_parse_filename( snapshot_name, &full_entry_slot, &incremental_entry_slot, decoded_hash, &is_zstd );
432 :
433 0 : if( FD_UNLIKELY( err || !is_zstd ) ) {
434 0 : FD_LOG_WARNING(( "unrecognized snapshot file `%s` in redirect location header", snapshot_name ));
435 0 : fd_sshttp_cancel( http );
436 0 : return FD_SSHTTP_ADVANCE_ERROR;
437 0 : }
438 :
439 0 : char encoded_hash[ FD_BASE58_ENCODED_32_SZ ];
440 0 : fd_base58_encode_32( decoded_hash, NULL, encoded_hash );
441 :
442 0 : if( FD_LIKELY( incremental_entry_slot!=ULONG_MAX ) ) {
443 0 : FD_TEST( fd_cstr_printf_check( http->snapshot_name, PATH_MAX, NULL, "incremental-snapshot-%lu-%lu-%s.tar.zst", full_entry_slot, incremental_entry_slot, encoded_hash ) );
444 0 : } else {
445 0 : FD_TEST( fd_cstr_printf_check( http->snapshot_name, PATH_MAX, NULL, "snapshot-%lu-%s.tar.zst", full_entry_slot, encoded_hash ) );
446 0 : }
447 0 : break;
448 0 : }
449 0 : }
450 :
451 0 : if( FD_UNLIKELY( !location ) ) {
452 0 : FD_LOG_WARNING(( "no location header in redirect response" ));
453 0 : fd_sshttp_cancel( http );
454 0 : return FD_SSHTTP_ADVANCE_ERROR;
455 0 : }
456 :
457 0 : if( FD_UNLIKELY( !fd_cstr_printf_check( http->request, sizeof(http->request), &http->request_len,
458 0 : "GET %.*s HTTP/1.1\r\n"
459 0 : "User-Agent: Firedancer\r\n"
460 0 : "Accept: */*\r\n"
461 0 : "Accept-Encoding: identity\r\n"
462 0 : "Host: " FD_IP4_ADDR_FMT "\r\n\r\n",
463 0 : (int)location_len, location, FD_IP4_ADDR_FMT_ARGS( http->addr.addr ) ) ) ) {
464 0 : FD_LOG_WARNING(( "location header too long `%.*s`", (int)location_len, location ));
465 0 : fd_sshttp_cancel( http );
466 0 : return FD_SSHTTP_ADVANCE_ERROR;
467 0 : }
468 :
469 0 : FD_LOG_NOTICE(( "following redirect to %s://" FD_IP4_ADDR_FMT ":%hu%.*s",
470 0 : http->is_https ? "https" : "http", FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ),
471 0 : (int)location_len, location ));
472 :
473 0 : if( FD_UNLIKELY( http->is_https ) ) {
474 0 : http->next_state = FD_SSHTTP_STATE_REDIRECT;
475 0 : http->state = FD_SSHTTP_STATE_SHUTTING_DOWN;
476 0 : http->location_len = location_len;
477 0 : FD_TEST( location_len<PATH_MAX-1UL );
478 0 : fd_memcpy( http->location, location, location_len );
479 0 : http->location[ location_len ] = '\0';
480 0 : } else {
481 0 : if( FD_LIKELY( !fd_sshttp_fuzz ) ) {
482 0 : fd_sshttp_cancel( http );
483 0 : fd_sshttp_init( http, http->addr, http->hostname, http->is_https, location, location_len, now );
484 0 : } else {
485 0 : http->state = FD_SSHTTP_STATE_RESP;
486 0 : http->response_len = 0UL;
487 0 : }
488 0 : }
489 :
490 0 : return FD_SSHTTP_ADVANCE_AGAIN;
491 0 : }
492 :
493 : static int
494 : read_response( fd_sshttp_t * http,
495 : ulong * data_len,
496 : uchar * data,
497 0 : long now ) {
498 0 : if( FD_UNLIKELY( now>http->deadline ) ) {
499 0 : FD_LOG_WARNING(( "timeout reading response" ));
500 0 : fd_sshttp_cancel( http );
501 0 : return FD_SSHTTP_ADVANCE_ERROR;
502 0 : }
503 :
504 0 : long read = http_recv( http, http->response+http->response_len, sizeof(http->response)-http->response_len );
505 0 : if( FD_UNLIKELY( read<=0 ) ) return (int)read;
506 :
507 0 : http->response_len += (ulong)read;
508 :
509 0 : int minor_version;
510 0 : int status;
511 0 : const char * message;
512 0 : ulong message_len;
513 0 : struct phr_header headers[ 128UL ];
514 0 : ulong header_cnt = 128UL;
515 0 : int parsed = phr_parse_response( http->response,
516 0 : http->response_len,
517 0 : &minor_version,
518 0 : &status,
519 0 : &message,
520 0 : &message_len,
521 0 : headers,
522 0 : &header_cnt,
523 0 : http->response_len - (ulong)read );
524 0 : if( FD_UNLIKELY( parsed==-1 ) ) {
525 0 : FD_LOG_WARNING(( "malformed response body" ));
526 0 : fd_sshttp_cancel( http );
527 0 : return FD_SSHTTP_ADVANCE_ERROR;
528 0 : } else if( parsed==-2 ) {
529 0 : return FD_SSHTTP_ADVANCE_AGAIN;
530 0 : }
531 :
532 0 : int is_redirect = (status==301) | (status==302) | (status==303) | (status==304) | (status==307) | (status==308);
533 0 : if( FD_UNLIKELY( is_redirect ) ) {
534 0 : return follow_redirect( http, headers, header_cnt, now );
535 0 : }
536 :
537 0 : if( FD_UNLIKELY( status!=200 ) ) {
538 0 : FD_LOG_WARNING(( "unexpected response status %d", status ));
539 0 : fd_sshttp_cancel( http );
540 0 : return FD_SSHTTP_ADVANCE_ERROR;
541 0 : }
542 :
543 0 : http->content_read = 0UL;
544 0 : http->content_len = ULONG_MAX;
545 0 : for( ulong i=0UL; i<header_cnt; i++ ) {
546 0 : if( FD_LIKELY( headers[i].name_len!=14UL ) ) continue;
547 0 : if( FD_LIKELY( strncasecmp( headers[i].name, "content-length", 14UL ) ) ) continue;
548 :
549 0 : http->content_len = strtoul( headers[i].value, NULL, 10 );
550 0 : break;
551 0 : }
552 :
553 0 : if( FD_UNLIKELY( http->content_len==ULONG_MAX ) ) {
554 0 : FD_LOG_WARNING(( "no content-length header in response" ));
555 0 : fd_sshttp_cancel( http );
556 0 : return FD_SSHTTP_ADVANCE_ERROR;
557 0 : }
558 :
559 0 : http->state = FD_SSHTTP_STATE_DL;
560 0 : if( FD_UNLIKELY( (ulong)parsed<http->response_len ) ) {
561 0 : if( FD_UNLIKELY( *data_len<http->response_len-(ulong)parsed ) ) FD_LOG_ERR(( "data buffer too small %lu %lu %lu", *data_len, http->response_len, (ulong)parsed ));
562 0 : FD_TEST( *data_len>=http->response_len-(ulong)parsed );
563 0 : *data_len = http->response_len - (ulong)parsed;
564 0 : fd_memcpy( data, http->response+parsed, *data_len );
565 0 : http->content_read += *data_len;
566 0 : return FD_SSHTTP_ADVANCE_DATA;
567 0 : } else {
568 0 : FD_TEST( http->response_len==(ulong)parsed );
569 0 : return FD_SSHTTP_ADVANCE_AGAIN;
570 0 : }
571 0 : }
572 :
573 : static int
574 : read_body( fd_sshttp_t * http,
575 : ulong * data_len,
576 : uchar * data,
577 0 : long now ) {
578 : /* FIXME: Add a forward-progress timeout */
579 :
580 0 : if( FD_UNLIKELY( http->content_read>=http->content_len ) ) {
581 0 : if( FD_UNLIKELY( http->is_https ) ) {
582 0 : http->next_state = FD_SSHTTP_STATE_DONE;
583 0 : http->state = FD_SSHTTP_STATE_SHUTTING_DOWN;
584 0 : http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
585 0 : return FD_SSHTTP_ADVANCE_AGAIN;
586 0 : } else {
587 0 : fd_sshttp_cancel( http );
588 0 : http->state = FD_SSHTTP_STATE_INIT;
589 0 : return FD_SSHTTP_ADVANCE_DONE;
590 0 : }
591 0 : }
592 :
593 0 : FD_TEST( http->content_read<http->content_len );
594 0 : long read = http_recv( http, data, fd_ulong_min( *data_len, http->content_len-http->content_read ) );
595 0 : if( FD_UNLIKELY( read<=0 ) ) return (int)read;
596 :
597 0 : if( FD_UNLIKELY( !read ) ) return FD_SSHTTP_ADVANCE_AGAIN;
598 :
599 0 : *data_len = (ulong)read;
600 0 : http->content_read += (ulong)read;
601 :
602 0 : return FD_SSHTTP_ADVANCE_DATA;
603 0 : }
604 :
605 : char const *
606 0 : fd_sshttp_snapshot_name( fd_sshttp_t const * http ) {
607 0 : return http->snapshot_name;
608 0 : }
609 :
610 : ulong
611 0 : fd_sshttp_content_len( fd_sshttp_t const * http ) {
612 0 : return http->content_len;
613 0 : }
614 :
615 : int
616 : fd_sshttp_advance( fd_sshttp_t * http,
617 : ulong * data_len,
618 : uchar * data,
619 0 : long now ) {
620 0 : switch( http->state ) {
621 0 : case FD_SSHTTP_STATE_INIT: return FD_SSHTTP_ADVANCE_AGAIN;
622 0 : #if FD_HAS_OPENSSL
623 0 : case FD_SSHTTP_STATE_CONNECT: return http_connect_ssl( http, now );
624 0 : case FD_SSHTTP_STATE_SHUTTING_DOWN: return http_shutdown_ssl( http, now );
625 0 : case FD_SSHTTP_STATE_REDIRECT: return setup_redirect( http, now );
626 0 : #endif
627 0 : case FD_SSHTTP_STATE_REQ: return send_request( http, now );
628 0 : case FD_SSHTTP_STATE_RESP: return read_response( http, data_len, data, now );
629 0 : case FD_SSHTTP_STATE_DL: return read_body( http, data_len, data, now );
630 0 : case FD_SSHTTP_STATE_DONE:
631 0 : fd_sshttp_cancel( http );
632 0 : http->state = FD_SSHTTP_STATE_INIT;
633 0 : return FD_SSHTTP_ADVANCE_DONE;
634 0 : default: return FD_SSHTTP_ADVANCE_ERROR;
635 0 : }
636 0 : }
|