Quellcode durchsuchen

[http] Support chunked transfer encoding

Booting from an HTTP SAN will require HTTP range requests, which are
defined only in HTTP/1.1 and above.  HTTP/1.1 mandates support for
"Transfer-Encoding: chunked", so we must support it.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
tags/v1.20.1
Michael Brown vor 13 Jahren
Ursprung
Commit
8f51db233a
1 geänderte Dateien mit 119 neuen und 44 gelöschten Zeilen
  1. 119
    44
      src/net/tcp/http.c

+ 119
- 44
src/net/tcp/http.c Datei anzeigen

@@ -52,7 +52,9 @@ FEATURE ( FEATURE_PROTOCOL, "HTTP", DHCP_EB_FEATURE_HTTP, 1 );
52 52
 enum http_rx_state {
53 53
 	HTTP_RX_RESPONSE = 0,
54 54
 	HTTP_RX_HEADER,
55
+	HTTP_RX_CHUNK_LEN,
55 56
 	HTTP_RX_DATA,
57
+	HTTP_RX_TRAILER,
56 58
 	HTTP_RX_DEAD,
57 59
 };
58 60
 
@@ -78,6 +80,10 @@ struct http_request {
78 80
 	unsigned int response;
79 81
 	/** HTTP Content-Length */
80 82
 	size_t content_length;
83
+	/** HTTP is using Transfer-Encoding: chunked */
84
+	int chunked;
85
+	/** Current chunk length */
86
+	size_t chunk_len;
81 87
 	/** Received length */
82 88
 	size_t rx_len;
83 89
 	/** RX state */
@@ -229,6 +235,24 @@ static int http_rx_content_length ( struct http_request *http,
229 235
 	return 0;
230 236
 }
231 237
 
238
+/**
239
+ * Handle HTTP Transfer-Encoding header
240
+ *
241
+ * @v http		HTTP request
242
+ * @v value		HTTP header value
243
+ * @ret rc		Return status code
244
+ */
245
+static int http_rx_transfer_encoding ( struct http_request *http,
246
+				       const char *value ) {
247
+
248
+	if ( strcmp ( value, "chunked" ) == 0 ) {
249
+		/* Mark connection as using chunked transfer encoding */
250
+		http->chunked = 1;
251
+	}
252
+
253
+	return 0;
254
+}
255
+
232 256
 /** An HTTP header handler */
233 257
 struct http_header_handler {
234 258
 	/** Name (e.g. "Content-Length") */
@@ -254,6 +278,10 @@ static struct http_header_handler http_header_handlers[] = {
254 278
 		.header = "Content-Length",
255 279
 		.rx = http_rx_content_length,
256 280
 	},
281
+	{
282
+		.header = "Transfer-Encoding",
283
+		.rx = http_rx_transfer_encoding,
284
+	},
257 285
 	{ NULL, NULL }
258 286
 };
259 287
 
@@ -270,12 +298,19 @@ static int http_rx_header ( struct http_request *http, char *header ) {
270 298
 	char *value;
271 299
 	int rc;
272 300
 
273
-	/* An empty header line marks the transition to the data phase */
301
+	/* An empty header line marks the end of this phase */
274 302
 	if ( ! header[0] ) {
275
-		DBGC ( http, "HTTP %p start of data\n", http );
276 303
 		empty_line_buffer ( &http->linebuf );
277
-		http->rx_state = HTTP_RX_DATA;
278
-		return 0;
304
+		if ( http->rx_state == HTTP_RX_HEADER ) {
305
+			DBGC ( http, "HTTP %p start of data\n", http );
306
+			http->rx_state = ( http->chunked ?
307
+					   HTTP_RX_CHUNK_LEN : HTTP_RX_DATA );
308
+			return 0;
309
+		} else {
310
+			DBGC ( http, "HTTP %p end of trailer\n", http );
311
+			http_done ( http, 0 );
312
+			return 0;
313
+		}
279 314
 	}
280 315
 
281 316
 	DBGC ( http, "HTTP %p header \"%s\"\n", http, header );
@@ -300,6 +335,48 @@ static int http_rx_header ( struct http_request *http, char *header ) {
300 335
 	return 0;
301 336
 }
302 337
 
338
+/**
339
+ * Handle HTTP chunk length
340
+ *
341
+ * @v http		HTTP request
342
+ * @v length		HTTP chunk length
343
+ * @ret rc		Return status code
344
+ */
345
+static int http_rx_chunk_len ( struct http_request *http, char *length ) {
346
+	char *endp;
347
+
348
+	/* Skip blank lines between chunks */
349
+	if ( length[0] == '\0' )
350
+		return 0;
351
+
352
+	/* Parse chunk length */
353
+	http->chunk_len = strtoul ( length, &endp, 16 );
354
+	if ( *endp != '\0' ) {
355
+		DBGC ( http, "HTTP %p invalid chunk length \"%s\"\n",
356
+		       http, length );
357
+		return -EIO;
358
+	}
359
+
360
+	/* Terminate chunked encoding if applicable */
361
+	if ( http->chunk_len == 0 ) {
362
+		DBGC ( http, "HTTP %p end of chunks\n", http );
363
+		http->chunked = 0;
364
+		http->rx_state = HTTP_RX_TRAILER;
365
+		return 0;
366
+	}
367
+
368
+	/* Use seek() to notify recipient of new filesize */
369
+	DBGC ( http, "HTTP %p start of chunk of length %zd\n",
370
+	       http, http->chunk_len );
371
+	xfer_seek ( &http->xfer, ( http->rx_len + http->chunk_len ) );
372
+	xfer_seek ( &http->xfer, http->rx_len );
373
+
374
+	/* Start receiving data */
375
+	http->rx_state = HTTP_RX_DATA;
376
+
377
+	return 0;
378
+}
379
+
303 380
 /** An HTTP line-based data handler */
304 381
 struct http_line_handler {
305 382
 	/** Handle line
@@ -315,35 +392,10 @@ struct http_line_handler {
315 392
 static struct http_line_handler http_line_handlers[] = {
316 393
 	[HTTP_RX_RESPONSE]	= { .rx = http_rx_response },
317 394
 	[HTTP_RX_HEADER]	= { .rx = http_rx_header },
395
+	[HTTP_RX_CHUNK_LEN]	= { .rx = http_rx_chunk_len },
396
+	[HTTP_RX_TRAILER]	= { .rx = http_rx_header },
318 397
 };
319 398
 
320
-/**
321
- * Handle new data arriving via HTTP connection in the data phase
322
- *
323
- * @v http		HTTP request
324
- * @v iobuf		I/O buffer
325
- * @ret rc		Return status code
326
- */
327
-static int http_rx_data ( struct http_request *http,
328
-			  struct io_buffer *iobuf ) {
329
-	int rc;
330
-
331
-	/* Update received length */
332
-	http->rx_len += iob_len ( iobuf );
333
-
334
-	/* Hand off data buffer */
335
-	if ( ( rc = xfer_deliver_iob ( &http->xfer, iobuf ) ) != 0 )
336
-		return rc;
337
-
338
-	/* If we have reached the content-length, stop now */
339
-	if ( http->content_length &&
340
-	     ( http->rx_len >= http->content_length ) ) {
341
-		http_done ( http, 0 );
342
-	}
343
-
344
-	return 0;
345
-}
346
-
347 399
 /**
348 400
  * Handle new data arriving via HTTP connection
349 401
  *
@@ -357,34 +409,57 @@ static int http_socket_deliver ( struct http_request *http,
357 409
 				 struct xfer_metadata *meta __unused ) {
358 410
 	struct http_line_handler *lh;
359 411
 	char *line;
360
-	ssize_t len;
412
+	size_t data_len;
413
+	ssize_t line_len;
361 414
 	int rc = 0;
362 415
 
363
-	while ( iob_len ( iobuf ) ) {
416
+	while ( iobuf && iob_len ( iobuf ) ) {
364 417
 		switch ( http->rx_state ) {
365 418
 		case HTTP_RX_DEAD:
366 419
 			/* Do no further processing */
367 420
 			goto done;
368 421
 		case HTTP_RX_DATA:
369
-			/* Once we're into the data phase, just fill
370
-			 * the data buffer
371
-			 */
372
-			rc = http_rx_data ( http, iob_disown ( iobuf ) );
373
-			goto done;
422
+			/* Pass received data to caller */
423
+			data_len = iob_len ( iobuf );
424
+			if ( http->chunk_len && ( http->chunk_len < data_len )){
425
+				data_len = http->chunk_len;
426
+				rc = xfer_deliver_raw ( &http->xfer,
427
+							iobuf->data, data_len );
428
+				iob_pull ( iobuf, data_len );
429
+			} else {
430
+				rc = xfer_deliver_iob ( &http->xfer,
431
+							iob_disown ( iobuf ) );
432
+			}
433
+			if ( rc != 0 )
434
+				goto done;
435
+			if ( http->chunk_len ) {
436
+				http->chunk_len -= data_len;
437
+				if ( http->chunk_len == 0 )
438
+					http->rx_state = HTTP_RX_CHUNK_LEN;
439
+			}
440
+			http->rx_len += data_len;
441
+			if ( http->content_length &&
442
+			     ( http->rx_len >= http->content_length ) ) {
443
+				http_done ( http, 0 );
444
+				goto done;
445
+			}
446
+			break;
374 447
 		case HTTP_RX_RESPONSE:
375 448
 		case HTTP_RX_HEADER:
449
+		case HTTP_RX_CHUNK_LEN:
450
+		case HTTP_RX_TRAILER:
376 451
 			/* In the other phases, buffer and process a
377 452
 			 * line at a time
378 453
 			 */
379
-			len = line_buffer ( &http->linebuf, iobuf->data,
380
-					    iob_len ( iobuf ) );
381
-			if ( len < 0 ) {
382
-				rc = len;
454
+			line_len = line_buffer ( &http->linebuf, iobuf->data,
455
+						 iob_len ( iobuf ) );
456
+			if ( line_len < 0 ) {
457
+				rc = line_len;
383 458
 				DBGC ( http, "HTTP %p could not buffer line: "
384 459
 				       "%s\n", http, strerror ( rc ) );
385 460
 				goto done;
386 461
 			}
387
-			iob_pull ( iobuf, len );
462
+			iob_pull ( iobuf, line_len );
388 463
 			line = buffered_line ( &http->linebuf );
389 464
 			if ( line ) {
390 465
 				lh = &http_line_handlers[http->rx_state];
@@ -448,7 +523,7 @@ static void http_step ( struct process *process ) {
448 523
 
449 524
 		/* Send GET request */
450 525
 		if ( ( rc = xfer_printf ( &http->socket,
451
-					  "GET %s%s HTTP/1.0\r\n"
526
+					  "GET %s%s HTTP/1.1\r\n"
452 527
 					  "User-Agent: iPXE/" VERSION "\r\n"
453 528
 					  "%s%s%s"
454 529
 					  "Host: %s\r\n"

Laden…
Abbrechen
Speichern