Browse Source

[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 13 years ago
parent
commit
8f51db233a
1 changed files with 119 additions and 44 deletions
  1. 119
    44
      src/net/tcp/http.c

+ 119
- 44
src/net/tcp/http.c View File

52
 enum http_rx_state {
52
 enum http_rx_state {
53
 	HTTP_RX_RESPONSE = 0,
53
 	HTTP_RX_RESPONSE = 0,
54
 	HTTP_RX_HEADER,
54
 	HTTP_RX_HEADER,
55
+	HTTP_RX_CHUNK_LEN,
55
 	HTTP_RX_DATA,
56
 	HTTP_RX_DATA,
57
+	HTTP_RX_TRAILER,
56
 	HTTP_RX_DEAD,
58
 	HTTP_RX_DEAD,
57
 };
59
 };
58
 
60
 
78
 	unsigned int response;
80
 	unsigned int response;
79
 	/** HTTP Content-Length */
81
 	/** HTTP Content-Length */
80
 	size_t content_length;
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
 	/** Received length */
87
 	/** Received length */
82
 	size_t rx_len;
88
 	size_t rx_len;
83
 	/** RX state */
89
 	/** RX state */
229
 	return 0;
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
 /** An HTTP header handler */
256
 /** An HTTP header handler */
233
 struct http_header_handler {
257
 struct http_header_handler {
234
 	/** Name (e.g. "Content-Length") */
258
 	/** Name (e.g. "Content-Length") */
254
 		.header = "Content-Length",
278
 		.header = "Content-Length",
255
 		.rx = http_rx_content_length,
279
 		.rx = http_rx_content_length,
256
 	},
280
 	},
281
+	{
282
+		.header = "Transfer-Encoding",
283
+		.rx = http_rx_transfer_encoding,
284
+	},
257
 	{ NULL, NULL }
285
 	{ NULL, NULL }
258
 };
286
 };
259
 
287
 
270
 	char *value;
298
 	char *value;
271
 	int rc;
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
 	if ( ! header[0] ) {
302
 	if ( ! header[0] ) {
275
-		DBGC ( http, "HTTP %p start of data\n", http );
276
 		empty_line_buffer ( &http->linebuf );
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
 	DBGC ( http, "HTTP %p header \"%s\"\n", http, header );
316
 	DBGC ( http, "HTTP %p header \"%s\"\n", http, header );
300
 	return 0;
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
 /** An HTTP line-based data handler */
380
 /** An HTTP line-based data handler */
304
 struct http_line_handler {
381
 struct http_line_handler {
305
 	/** Handle line
382
 	/** Handle line
315
 static struct http_line_handler http_line_handlers[] = {
392
 static struct http_line_handler http_line_handlers[] = {
316
 	[HTTP_RX_RESPONSE]	= { .rx = http_rx_response },
393
 	[HTTP_RX_RESPONSE]	= { .rx = http_rx_response },
317
 	[HTTP_RX_HEADER]	= { .rx = http_rx_header },
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
  * Handle new data arriving via HTTP connection
400
  * Handle new data arriving via HTTP connection
349
  *
401
  *
357
 				 struct xfer_metadata *meta __unused ) {
409
 				 struct xfer_metadata *meta __unused ) {
358
 	struct http_line_handler *lh;
410
 	struct http_line_handler *lh;
359
 	char *line;
411
 	char *line;
360
-	ssize_t len;
412
+	size_t data_len;
413
+	ssize_t line_len;
361
 	int rc = 0;
414
 	int rc = 0;
362
 
415
 
363
-	while ( iob_len ( iobuf ) ) {
416
+	while ( iobuf && iob_len ( iobuf ) ) {
364
 		switch ( http->rx_state ) {
417
 		switch ( http->rx_state ) {
365
 		case HTTP_RX_DEAD:
418
 		case HTTP_RX_DEAD:
366
 			/* Do no further processing */
419
 			/* Do no further processing */
367
 			goto done;
420
 			goto done;
368
 		case HTTP_RX_DATA:
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
 		case HTTP_RX_RESPONSE:
447
 		case HTTP_RX_RESPONSE:
375
 		case HTTP_RX_HEADER:
448
 		case HTTP_RX_HEADER:
449
+		case HTTP_RX_CHUNK_LEN:
450
+		case HTTP_RX_TRAILER:
376
 			/* In the other phases, buffer and process a
451
 			/* In the other phases, buffer and process a
377
 			 * line at a time
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
 				DBGC ( http, "HTTP %p could not buffer line: "
458
 				DBGC ( http, "HTTP %p could not buffer line: "
384
 				       "%s\n", http, strerror ( rc ) );
459
 				       "%s\n", http, strerror ( rc ) );
385
 				goto done;
460
 				goto done;
386
 			}
461
 			}
387
-			iob_pull ( iobuf, len );
462
+			iob_pull ( iobuf, line_len );
388
 			line = buffered_line ( &http->linebuf );
463
 			line = buffered_line ( &http->linebuf );
389
 			if ( line ) {
464
 			if ( line ) {
390
 				lh = &http_line_handlers[http->rx_state];
465
 				lh = &http_line_handlers[http->rx_state];
448
 
523
 
449
 		/* Send GET request */
524
 		/* Send GET request */
450
 		if ( ( rc = xfer_printf ( &http->socket,
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
 					  "User-Agent: iPXE/" VERSION "\r\n"
527
 					  "User-Agent: iPXE/" VERSION "\r\n"
453
 					  "%s%s%s"
528
 					  "%s%s%s"
454
 					  "Host: %s\r\n"
529
 					  "Host: %s\r\n"

Loading…
Cancel
Save