2 * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 * Hyper Text Transfer Protocol (HTTP)
34 #include <gpxe/async.h>
36 #include <gpxe/buffer.h>
37 #include <gpxe/download.h>
38 #include <gpxe/resolv.h>
41 #include <gpxe/http.h>
43 static struct async_operations http_async_operations;
45 static inline struct http_request *
46 stream_to_http ( struct stream_application *app ) {
47 return container_of ( app, struct http_request, stream );
51 * Mark HTTP request as complete
53 * @v http HTTP request
54 * @v rc Return status code
57 static void http_done ( struct http_request *http, int rc ) {
59 /* Close stream connection */
60 stream_close ( &http->stream );
62 /* Prevent further processing of any current packet */
63 http->rx_state = HTTP_RX_DEAD;
65 /* Free up any dynamically allocated storage */
66 empty_line_buffer ( &http->linebuf );
68 /* If we had a Content-Length, and the received content length
69 * isn't correct, flag an error
71 if ( http->content_length &&
72 ( http->content_length != http->buffer->fill ) ) {
73 DBGC ( http, "HTTP %p incorrect length %zd, should be %zd\n",
74 http, http->buffer->fill, http->content_length );
78 /* Mark async operation as complete */
79 async_done ( &http->async, rc );
83 * Convert HTTP response code to return status code
85 * @v response HTTP response code
86 * @ret rc Return status code
88 static int http_response_to_rc ( unsigned int response ) {
102 * Handle HTTP response
104 * @v http HTTP request
105 * @v response HTTP response
107 static void http_rx_response ( struct http_request *http, char *response ) {
111 DBGC ( http, "HTTP %p response \"%s\"\n", http, response );
113 /* Check response starts with "HTTP/" */
114 if ( strncmp ( response, "HTTP/", 5 ) != 0 )
117 /* Locate and check response code */
118 spc = strchr ( response, ' ' );
121 http->response = strtoul ( spc, NULL, 10 );
122 if ( ( rc = http_response_to_rc ( http->response ) ) != 0 )
125 /* Move to received headers */
126 http->rx_state = HTTP_RX_HEADER;
130 DBGC ( http, "HTTP %p bad response\n", http );
131 http_done ( http, rc );
136 * Handle HTTP Content-Length header
138 * @v http HTTP request
139 * @v value HTTP header value
140 * @ret rc Return status code
142 static int http_rx_content_length ( struct http_request *http,
143 const char *value ) {
147 http->content_length = strtoul ( value, &endp, 10 );
148 if ( *endp != '\0' ) {
149 DBGC ( http, "HTTP %p invalid Content-Length \"%s\"\n",
154 /* Try to presize the receive buffer */
155 if ( ( rc = expand_buffer ( http->buffer,
156 http->content_length ) ) != 0 ) {
157 /* May as well abandon the download now; it will fail */
158 DBGC ( http, "HTTP %p could not presize buffer: %s\n",
159 http, strerror ( rc ) );
167 * An HTTP header handler
170 struct http_header_handler {
171 /** Name (e.g. "Content-Length") */
173 /** Handle received header
175 * @v http HTTP request
176 * @v value HTTP header value
177 * @ret rc Return status code
179 * If an error is returned, the download will be aborted.
181 int ( * rx ) ( struct http_request *http, const char *value );
184 /** List of HTTP header handlers */
185 struct http_header_handler http_header_handlers[] = {
187 .header = "Content-Length",
188 .rx = http_rx_content_length,
196 * @v http HTTP request
197 * @v header HTTP header
199 static void http_rx_header ( struct http_request *http, char *header ) {
200 struct http_header_handler *handler;
205 /* An empty header line marks the transition to the data phase */
207 DBGC ( http, "HTTP %p start of data\n", http );
208 empty_line_buffer ( &http->linebuf );
209 http->rx_state = HTTP_RX_DATA;
213 DBGC ( http, "HTTP %p header \"%s\"\n", http, header );
215 /* Split header at the ": " */
216 separator = strstr ( header, ": " );
220 value = ( separator + 2 );
222 /* Hand off to header handler, if one exists */
223 for ( handler = http_header_handlers ; handler->header ; handler++ ) {
224 if ( strcasecmp ( header, handler->header ) == 0 ) {
225 if ( ( rc = handler->rx ( http, value ) ) != 0 )
233 DBGC ( http, "HTTP %p bad header\n", http );
234 http_done ( http, rc );
239 * Handle new data arriving via HTTP connection in the data phase
241 * @v http HTTP request
243 * @v len Length of new data
245 static void http_rx_data ( struct http_request *http,
246 const char *data, size_t len ) {
249 /* Fill data buffer */
250 if ( ( rc = fill_buffer ( http->buffer, data,
251 http->buffer->fill, len ) ) != 0 ) {
252 DBGC ( http, "HTTP %p failed to fill data buffer: %s\n",
253 http, strerror ( rc ) );
254 http_done ( http, rc );
258 /* Update progress */
259 http->async.completed = http->buffer->fill;
260 http->async.total = http->content_length;
262 /* If we have reached the content-length, stop now */
263 if ( http->content_length &&
264 ( http->buffer->fill >= http->content_length ) ) {
265 http_done ( http, 0 );
270 * Handle new data arriving via HTTP connection
272 * @v http HTTP request
274 * @v len Length of new data
276 static void http_newdata ( struct stream_application *app,
277 void *data, size_t len ) {
278 struct http_request *http = stream_to_http ( app );
279 const char *buf = data;
284 if ( http->rx_state == HTTP_RX_DEAD ) {
285 /* Do no further processing */
287 } else if ( http->rx_state == HTTP_RX_DATA ) {
288 /* Once we're into the data phase, just fill
291 http_rx_data ( http, buf, len );
294 /* In the other phases, buffer and process a
297 if ( ( rc = line_buffer ( &http->linebuf, &buf,
299 DBGC ( http, "HTTP %p could not buffer line: "
300 "%s\n", http, strerror ( rc ) );
301 http_done ( http, rc );
304 if ( ( line = buffered_line ( &http->linebuf ) ) ) {
305 switch ( http->rx_state ) {
306 case HTTP_RX_RESPONSE:
307 http_rx_response ( http, line );
310 http_rx_header ( http, line );
324 * @v app Stream application
325 * @v buf Temporary data buffer
326 * @v len Length of temporary data buffer
328 static void http_senddata ( struct stream_application *app,
329 void *buf, size_t len ) {
330 struct http_request *http = stream_to_http ( app );
331 const char *path = http->uri->path;
332 const char *host = http->uri->host;
337 len = snprintf ( buf, len,
338 "GET %s HTTP/1.1\r\n"
339 "User-Agent: gPXE/" VERSION "\r\n"
341 "\r\n", path, host );
343 stream_send ( app, ( buf + http->tx_offset ),
344 ( len - http->tx_offset ) );
348 * HTTP data acknowledged
350 * @v app Stream application
351 * @v len Length of acknowledged data
353 static void http_acked ( struct stream_application *app, size_t len ) {
354 struct http_request *http = stream_to_http ( app );
356 http->tx_offset += len;
360 * HTTP connection closed by network stack
362 * @v app Stream application
364 static void http_closed ( struct stream_application *app, int rc ) {
365 struct http_request *http = stream_to_http ( app );
367 DBGC ( http, "HTTP %p connection closed: %s\n",
368 http, strerror ( rc ) );
370 http_done ( http, rc );
373 /** HTTP stream operations */
374 static struct stream_application_operations http_stream_operations = {
375 .closed = http_closed,
377 .newdata = http_newdata,
378 .senddata = http_senddata,
382 * Initiate a HTTP connection
384 * @v uri Uniform Resource Identifier
385 * @v buffer Buffer into which to download file
386 * @v parent Parent asynchronous operation
387 * @ret rc Return status code
389 int http_get ( struct uri *uri, struct buffer *buffer, struct async *parent ) {
390 struct http_request *http = NULL;
391 struct sockaddr_tcpip *st;
394 /* Allocate and populate HTTP structure */
395 http = malloc ( sizeof ( *http ) );
398 memset ( http, 0, sizeof ( *http ) );
400 http->buffer = buffer;
401 async_init ( &http->async, &http_async_operations, parent );
402 http->stream.op = &http_stream_operations;
403 st = ( struct sockaddr_tcpip * ) &http->server;
404 st->st_port = htons ( uri_port ( http->uri, HTTP_PORT ) );
406 /* Open TCP connection */
407 if ( ( rc = tcp_open ( &http->stream ) ) != 0 )
409 if ( strcmp ( http->uri->scheme, "https" ) == 0 ) {
410 st->st_port = htons ( uri_port ( http->uri, HTTPS_PORT ) );
411 if ( ( rc = add_tls ( &http->stream ) ) != 0 )
415 /* Start name resolution. The download proper will start when
416 * name resolution completes.
418 if ( ( rc = resolv ( uri->host, &http->server, &http->async ) ) != 0 )
424 DBGC ( http, "HTTP %p could not create request: %s\n",
425 http, strerror ( rc ) );
426 async_uninit ( &http->async );
432 * Handle name resolution completion
434 * @v async HTTP asynchronous operation
437 static void http_sigchld ( struct async *async, enum signal signal __unused ) {
438 struct http_request *http =
439 container_of ( async, struct http_request, async );
442 /* If name resolution failed, abort now */
443 async_wait ( async, &rc, 1 );
445 http_done ( http, rc );
449 /* Otherwise, start the HTTP connection */
450 if ( ( rc = stream_connect ( &http->stream, &http->server ) ) != 0 ) {
451 DBGC ( http, "HTTP %p could not connect stream: %s\n",
452 http, strerror ( rc ) );
453 http_done ( http, rc );
459 * Free HTTP connection
461 * @v async Asynchronous operation
463 static void http_reap ( struct async *async ) {
464 free ( container_of ( async, struct http_request, async ) );
467 /** HTTP asynchronous operations */
468 static struct async_operations http_async_operations = {
471 [SIGCHLD] = http_sigchld,
475 /** HTTP download protocol */
476 struct download_protocol http_download_protocol __download_protocol = {
478 .start_download = http_get,
481 /** HTTPS download protocol */
482 struct download_protocol https_download_protocol __download_protocol = {
484 .start_download = http_get,