/* * Copyright (C) 2007 Michael Brown . * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** * @file * * Hyper Text Transfer Protocol (HTTP) * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static struct async_operations http_async_operations; static inline struct http_request * stream_to_http ( struct stream_application *app ) { return container_of ( app, struct http_request, stream ); } /** * Mark HTTP request as complete * * @v http HTTP request * @v rc Return status code * */ static void http_done ( struct http_request *http, int rc ) { /* Close stream connection */ stream_close ( &http->stream ); /* Prevent further processing of any current packet */ http->rx_state = HTTP_RX_DEAD; /* Free up any dynamically allocated storage */ empty_line_buffer ( &http->linebuf ); /* If we had a Content-Length, and the received content length * isn't correct, flag an error */ if ( http->content_length && ( http->content_length != http->buffer->fill ) ) { DBGC ( http, "HTTP %p incorrect length %zd, should be %zd\n", http, http->buffer->fill, http->content_length ); rc = -EIO; } /* Mark async operation as complete */ async_done ( &http->async, rc ); } /** * Convert HTTP response code to return status code * * @v response HTTP response code * @ret rc Return status code */ static int http_response_to_rc ( unsigned int response ) { switch ( response ) { case 200: return 0; case 404: return -ENOENT; case 403: return -EPERM; default: return -EIO; } } /** * Handle HTTP response * * @v http HTTP request * @v response HTTP response */ static void http_rx_response ( struct http_request *http, char *response ) { char *spc; int rc = -EIO; DBGC ( http, "HTTP %p response \"%s\"\n", http, response ); /* Check response starts with "HTTP/" */ if ( strncmp ( response, "HTTP/", 5 ) != 0 ) goto err; /* Locate and check response code */ spc = strchr ( response, ' ' ); if ( ! spc ) goto err; http->response = strtoul ( spc, NULL, 10 ); if ( ( rc = http_response_to_rc ( http->response ) ) != 0 ) goto err; /* Move to received headers */ http->rx_state = HTTP_RX_HEADER; return; err: DBGC ( http, "HTTP %p bad response\n", http ); http_done ( http, rc ); return; } /** * Handle HTTP Content-Length header * * @v http HTTP request * @v value HTTP header value * @ret rc Return status code */ static int http_rx_content_length ( struct http_request *http, const char *value ) { char *endp; int rc; http->content_length = strtoul ( value, &endp, 10 ); if ( *endp != '\0' ) { DBGC ( http, "HTTP %p invalid Content-Length \"%s\"\n", http, value ); return -EIO; } /* Try to presize the receive buffer */ if ( ( rc = expand_buffer ( http->buffer, http->content_length ) ) != 0 ) { /* May as well abandon the download now; it will fail */ DBGC ( http, "HTTP %p could not presize buffer: %s\n", http, strerror ( rc ) ); return rc; } return 0; } /** * An HTTP header handler * */ struct http_header_handler { /** Name (e.g. "Content-Length") */ const char *header; /** Handle received header * * @v http HTTP request * @v value HTTP header value * @ret rc Return status code * * If an error is returned, the download will be aborted. */ int ( * rx ) ( struct http_request *http, const char *value ); }; /** List of HTTP header handlers */ struct http_header_handler http_header_handlers[] = { { .header = "Content-Length", .rx = http_rx_content_length, }, { NULL, NULL } }; /** * Handle HTTP header * * @v http HTTP request * @v header HTTP header */ static void http_rx_header ( struct http_request *http, char *header ) { struct http_header_handler *handler; char *separator; char *value; int rc = -EIO; /* An empty header line marks the transition to the data phase */ if ( ! header[0] ) { DBGC ( http, "HTTP %p start of data\n", http ); empty_line_buffer ( &http->linebuf ); http->rx_state = HTTP_RX_DATA; return; } DBGC ( http, "HTTP %p header \"%s\"\n", http, header ); /* Split header at the ": " */ separator = strstr ( header, ": " ); if ( ! separator ) goto err; *separator = '\0'; value = ( separator + 2 ); /* Hand off to header handler, if one exists */ for ( handler = http_header_handlers ; handler->header ; handler++ ) { if ( strcasecmp ( header, handler->header ) == 0 ) { if ( ( rc = handler->rx ( http, value ) ) != 0 ) goto err; break; } } return; err: DBGC ( http, "HTTP %p bad header\n", http ); http_done ( http, rc ); return; } /** * Handle new data arriving via HTTP connection in the data phase * * @v http HTTP request * @v data New data * @v len Length of new data */ static void http_rx_data ( struct http_request *http, const char *data, size_t len ) { int rc; /* Fill data buffer */ if ( ( rc = fill_buffer ( http->buffer, data, http->buffer->fill, len ) ) != 0 ) { DBGC ( http, "HTTP %p failed to fill data buffer: %s\n", http, strerror ( rc ) ); http_done ( http, rc ); return; } /* Update progress */ http->async.completed = http->buffer->fill; http->async.total = http->content_length; /* If we have reached the content-length, stop now */ if ( http->content_length && ( http->buffer->fill >= http->content_length ) ) { http_done ( http, 0 ); } } /** * Handle new data arriving via HTTP connection * * @v http HTTP request * @v data New data * @v len Length of new data */ static void http_newdata ( struct stream_application *app, void *data, size_t len ) { struct http_request *http = stream_to_http ( app ); const char *buf = data; char *line; int rc; while ( len ) { if ( http->rx_state == HTTP_RX_DEAD ) { /* Do no further processing */ return; } else if ( http->rx_state == HTTP_RX_DATA ) { /* Once we're into the data phase, just fill * the data buffer */ http_rx_data ( http, buf, len ); return; } else { /* In the other phases, buffer and process a * line at a time */ if ( ( rc = line_buffer ( &http->linebuf, &buf, &len ) ) != 0 ) { DBGC ( http, "HTTP %p could not buffer line: " "%s\n", http, strerror ( rc ) ); http_done ( http, rc ); return; } if ( ( line = buffered_line ( &http->linebuf ) ) ) { switch ( http->rx_state ) { case HTTP_RX_RESPONSE: http_rx_response ( http, line ); break; case HTTP_RX_HEADER: http_rx_header ( http, line ); break; default: assert ( 0 ); break; } } } } } /** * Send HTTP data * * @v app Stream application * @v buf Temporary data buffer * @v len Length of temporary data buffer */ static void http_senddata ( struct stream_application *app, void *buf, size_t len ) { struct http_request *http = stream_to_http ( app ); const char *path = http->uri->path; const char *host = http->uri->host; const char *query = http->uri->query; len = snprintf ( buf, len, "GET %s%s%s HTTP/1.1\r\n" "User-Agent: gPXE/" VERSION "\r\n" "Host: %s\r\n" "\r\n", ( path ? path : "/" ), ( query ? "?" : "" ), ( query ? query : "" ), host ); stream_send ( app, ( buf + http->tx_offset ), ( len - http->tx_offset ) ); } /** * HTTP data acknowledged * * @v app Stream application * @v len Length of acknowledged data */ static void http_acked ( struct stream_application *app, size_t len ) { struct http_request *http = stream_to_http ( app ); http->tx_offset += len; } /** * HTTP connection closed by network stack * * @v app Stream application */ static void http_closed ( struct stream_application *app, int rc ) { struct http_request *http = stream_to_http ( app ); DBGC ( http, "HTTP %p connection closed: %s\n", http, strerror ( rc ) ); http_done ( http, rc ); } /** HTTP stream operations */ static struct stream_application_operations http_stream_operations = { .closed = http_closed, .acked = http_acked, .newdata = http_newdata, .senddata = http_senddata, }; /** * Initiate a HTTP connection * * @v uri Uniform Resource Identifier * @v buffer Buffer into which to download file * @v parent Parent asynchronous operation * @ret rc Return status code */ int http_get ( struct uri *uri, struct buffer *buffer, struct async *parent ) { struct http_request *http = NULL; struct sockaddr_tcpip *st; int rc; /* Allocate and populate HTTP structure */ http = malloc ( sizeof ( *http ) ); if ( ! http ) return -ENOMEM; memset ( http, 0, sizeof ( *http ) ); http->uri = uri; http->buffer = buffer; async_init ( &http->async, &http_async_operations, parent ); http->stream.op = &http_stream_operations; st = ( struct sockaddr_tcpip * ) &http->server; st->st_port = htons ( uri_port ( http->uri, HTTP_PORT ) ); /* Open TCP connection */ if ( ( rc = tcp_open ( &http->stream ) ) != 0 ) goto err; if ( strcmp ( http->uri->scheme, "https" ) == 0 ) { st->st_port = htons ( uri_port ( http->uri, HTTPS_PORT ) ); if ( ( rc = add_tls ( &http->stream ) ) != 0 ) goto err; } /* Start name resolution. The download proper will start when * name resolution completes. */ if ( ( rc = resolv ( uri->host, &http->server, &http->async ) ) != 0 ) goto err; return 0; err: DBGC ( http, "HTTP %p could not create request: %s\n", http, strerror ( rc ) ); async_uninit ( &http->async ); free ( http ); return rc; } /** * Handle name resolution completion * * @v async HTTP asynchronous operation * @v signal SIGCHLD */ static void http_sigchld ( struct async *async, enum signal signal __unused ) { struct http_request *http = container_of ( async, struct http_request, async ); int rc; /* If name resolution failed, abort now */ async_wait ( async, &rc, 1 ); if ( rc != 0 ) { http_done ( http, rc ); return; } /* Otherwise, start the HTTP connection */ if ( ( rc = stream_connect ( &http->stream, &http->server ) ) != 0 ) { DBGC ( http, "HTTP %p could not connect stream: %s\n", http, strerror ( rc ) ); http_done ( http, rc ); return; } } /** * Free HTTP connection * * @v async Asynchronous operation */ static void http_reap ( struct async *async ) { free ( container_of ( async, struct http_request, async ) ); } /** HTTP asynchronous operations */ static struct async_operations http_async_operations = { .reap = http_reap, .signal = { [SIGCHLD] = http_sigchld, }, }; /** HTTP download protocol */ struct download_protocol http_download_protocol __download_protocol = { .name = "http", .start_download = http_get, }; /** HTTPS download protocol */ struct download_protocol https_download_protocol __download_protocol = { .name = "https", .start_download = http_get, };