From ca67b09c015d4af3ae3cce12aa72e60941dbb8b5 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 18:29:52 +0200 Subject: Adding debian version 2.06-13+deb12u1. Signed-off-by: Daniel Baumann --- .../grub-extras/disabled/gpxe/src/net/tcp/http.c | 603 +++++++++++++++++++++ 1 file changed, 603 insertions(+) create mode 100644 debian/grub-extras/disabled/gpxe/src/net/tcp/http.c (limited to 'debian/grub-extras/disabled/gpxe/src/net/tcp/http.c') diff --git a/debian/grub-extras/disabled/gpxe/src/net/tcp/http.c b/debian/grub-extras/disabled/gpxe/src/net/tcp/http.c new file mode 100644 index 0000000..a02408a --- /dev/null +++ b/debian/grub-extras/disabled/gpxe/src/net/tcp/http.c @@ -0,0 +1,603 @@ +/* + * 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_LICENCE ( GPL2_OR_LATER ); + +/** + * @file + * + * Hyper Text Transfer Protocol (HTTP) + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +FEATURE ( FEATURE_PROTOCOL, "HTTP", DHCP_EB_FEATURE_HTTP, 1 ); + +/** HTTP receive state */ +enum http_rx_state { + HTTP_RX_RESPONSE = 0, + HTTP_RX_HEADER, + HTTP_RX_DATA, + HTTP_RX_DEAD, +}; + +/** + * An HTTP request + * + */ +struct http_request { + /** Reference count */ + struct refcnt refcnt; + /** Data transfer interface */ + struct xfer_interface xfer; + + /** URI being fetched */ + struct uri *uri; + /** Transport layer interface */ + struct xfer_interface socket; + + /** TX process */ + struct process process; + + /** HTTP response code */ + unsigned int response; + /** HTTP Content-Length */ + size_t content_length; + /** Received length */ + size_t rx_len; + /** RX state */ + enum http_rx_state rx_state; + /** Line buffer for received header lines */ + struct line_buffer linebuf; +}; + +/** + * Free HTTP request + * + * @v refcnt Reference counter + */ +static void http_free ( struct refcnt *refcnt ) { + struct http_request *http = + container_of ( refcnt, struct http_request, refcnt ); + + uri_put ( http->uri ); + empty_line_buffer ( &http->linebuf ); + free ( http ); +}; + +/** + * Mark HTTP request as complete + * + * @v http HTTP request + * @v rc Return status code + */ +static void http_done ( struct http_request *http, int rc ) { + + /* Prevent further processing of any current packet */ + http->rx_state = HTTP_RX_DEAD; + + /* 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->rx_len ) ) { + DBGC ( http, "HTTP %p incorrect length %zd, should be %zd\n", + http, http->rx_len, http->content_length ); + rc = -EIO; + } + + /* Remove process */ + process_del ( &http->process ); + + /* Close all data transfer interfaces */ + xfer_nullify ( &http->socket ); + xfer_close ( &http->socket, rc ); + xfer_nullify ( &http->xfer ); + xfer_close ( &http->xfer, 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: + case 301: + case 302: + return 0; + case 404: + return -ENOENT; + case 403: + return -EPERM; + case 401: + return -EACCES; + default: + return -EIO; + } +} + +/** + * Handle HTTP response + * + * @v http HTTP request + * @v response HTTP response + * @ret rc Return status code + */ +static int http_rx_response ( struct http_request *http, char *response ) { + char *spc; + int rc; + + DBGC ( http, "HTTP %p response \"%s\"\n", http, response ); + + /* Check response starts with "HTTP/" */ + if ( strncmp ( response, "HTTP/", 5 ) != 0 ) + return -EIO; + + /* Locate and check response code */ + spc = strchr ( response, ' ' ); + if ( ! spc ) + return -EIO; + http->response = strtoul ( spc, NULL, 10 ); + if ( ( rc = http_response_to_rc ( http->response ) ) != 0 ) + return rc; + + /* Move to received headers */ + http->rx_state = HTTP_RX_HEADER; + return 0; +} + +/** + * Handle HTTP Location header + * + * @v http HTTP request + * @v value HTTP header value + * @ret rc Return status code + */ +static int http_rx_location ( struct http_request *http, const char *value ) { + int rc; + + /* Redirect to new location */ + DBGC ( http, "HTTP %p redirecting to %s\n", http, value ); + if ( ( rc = xfer_redirect ( &http->xfer, LOCATION_URI_STRING, + value ) ) != 0 ) { + DBGC ( http, "HTTP %p could not redirect: %s\n", + http, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * 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; + + http->content_length = strtoul ( value, &endp, 10 ); + if ( *endp != '\0' ) { + DBGC ( http, "HTTP %p invalid Content-Length \"%s\"\n", + http, value ); + return -EIO; + } + + /* Use seek() to notify recipient of filesize */ + xfer_seek ( &http->xfer, http->content_length, SEEK_SET ); + xfer_seek ( &http->xfer, 0, SEEK_SET ); + + 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 */ +static struct http_header_handler http_header_handlers[] = { + { + .header = "Location", + .rx = http_rx_location, + }, + { + .header = "Content-Length", + .rx = http_rx_content_length, + }, + { NULL, NULL } +}; + +/** + * Handle HTTP header + * + * @v http HTTP request + * @v header HTTP header + * @ret rc Return status code + */ +static int http_rx_header ( struct http_request *http, char *header ) { + struct http_header_handler *handler; + char *separator; + char *value; + int rc; + + /* 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 0; + } + + DBGC ( http, "HTTP %p header \"%s\"\n", http, header ); + + /* Split header at the ": " */ + separator = strstr ( header, ": " ); + if ( ! separator ) { + DBGC ( http, "HTTP %p malformed header\n", http ); + return -EIO; + } + *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 ) + return rc; + break; + } + } + return 0; +} + +/** An HTTP line-based data handler */ +struct http_line_handler { + /** Handle line + * + * @v http HTTP request + * @v line Line to handle + * @ret rc Return status code + */ + int ( * rx ) ( struct http_request *http, char *line ); +}; + +/** List of HTTP line-based data handlers */ +static struct http_line_handler http_line_handlers[] = { + [HTTP_RX_RESPONSE] = { .rx = http_rx_response }, + [HTTP_RX_HEADER] = { .rx = http_rx_header }, +}; + +/** + * Handle new data arriving via HTTP connection in the data phase + * + * @v http HTTP request + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int http_rx_data ( struct http_request *http, + struct io_buffer *iobuf ) { + int rc; + + /* Update received length */ + http->rx_len += iob_len ( iobuf ); + + /* Hand off data buffer */ + if ( ( rc = xfer_deliver_iob ( &http->xfer, iobuf ) ) != 0 ) + return rc; + + /* If we have reached the content-length, stop now */ + if ( http->content_length && + ( http->rx_len >= http->content_length ) ) { + http_done ( http, 0 ); + } + + return 0; +} + +/** + * Handle new data arriving via HTTP connection + * + * @v socket Transport layer interface + * @v iobuf I/O buffer + * @v meta Data transfer metadata + * @ret rc Return status code + */ +static int http_socket_deliver_iob ( struct xfer_interface *socket, + struct io_buffer *iobuf, + struct xfer_metadata *meta __unused ) { + struct http_request *http = + container_of ( socket, struct http_request, socket ); + struct http_line_handler *lh; + char *line; + ssize_t len; + int rc = 0; + + while ( iob_len ( iobuf ) ) { + switch ( http->rx_state ) { + case HTTP_RX_DEAD: + /* Do no further processing */ + goto done; + case HTTP_RX_DATA: + /* Once we're into the data phase, just fill + * the data buffer + */ + rc = http_rx_data ( http, iob_disown ( iobuf ) ); + goto done; + case HTTP_RX_RESPONSE: + case HTTP_RX_HEADER: + /* In the other phases, buffer and process a + * line at a time + */ + len = line_buffer ( &http->linebuf, iobuf->data, + iob_len ( iobuf ) ); + if ( len < 0 ) { + rc = len; + DBGC ( http, "HTTP %p could not buffer line: " + "%s\n", http, strerror ( rc ) ); + goto done; + } + iob_pull ( iobuf, len ); + line = buffered_line ( &http->linebuf ); + if ( line ) { + lh = &http_line_handlers[http->rx_state]; + if ( ( rc = lh->rx ( http, line ) ) != 0 ) + goto done; + } + break; + default: + assert ( 0 ); + break; + } + } + + done: + if ( rc ) + http_done ( http, rc ); + free_iob ( iobuf ); + return rc; +} + +/** + * HTTP process + * + * @v process Process + */ +static void http_step ( struct process *process ) { + struct http_request *http = + container_of ( process, struct http_request, process ); + const char *path = http->uri->path; + const char *host = http->uri->host; + const char *query = http->uri->query; + const char *user = http->uri->user; + const char *password = + ( http->uri->password ? http->uri->password : "" ); + size_t user_pw_len = ( user ? ( strlen ( user ) + 1 /* ":" */ + + strlen ( password ) ) : 0 ); + size_t user_pw_base64_len = base64_encoded_len ( user_pw_len ); + char user_pw[ user_pw_len + 1 /* NUL */ ]; + char user_pw_base64[ user_pw_base64_len + 1 /* NUL */ ]; + int rc; + + if ( xfer_window ( &http->socket ) ) { + + /* We want to execute only once */ + process_del ( &http->process ); + + /* Construct authorisation, if applicable */ + if ( user ) { + char *buf = user_pw; + ssize_t remaining = sizeof ( user_pw ); + size_t len; + + /* URI-decode the username and password */ + len = uri_decode ( user, buf, remaining ); + buf += len; + remaining -= len; + *(remaining--, buf++) = ':'; + len = uri_decode ( password, buf, remaining ); + buf += len; + remaining -= len; + assert ( remaining >= 0 ); + + /* Base64-encode the "user:password" string */ + base64_encode ( user_pw, user_pw_base64 ); + } + + /* Send GET request */ + if ( ( rc = xfer_printf ( &http->socket, + "GET %s%s%s HTTP/1.0\r\n" + "User-Agent: gPXE/" VERSION "\r\n" + "%s%s%s" + "Host: %s\r\n" + "\r\n", + ( path ? path : "/" ), + ( query ? "?" : "" ), + ( query ? query : "" ), + ( user ? + "Authorization: Basic " : "" ), + ( user ? user_pw_base64 : "" ), + ( user ? "\r\n" : "" ), + host ) ) != 0 ) { + http_done ( http, rc ); + } + } +} + +/** + * HTTP connection closed by network stack + * + * @v socket Transport layer interface + * @v rc Reason for close + */ +static void http_socket_close ( struct xfer_interface *socket, int rc ) { + struct http_request *http = + container_of ( socket, struct http_request, socket ); + + DBGC ( http, "HTTP %p socket closed: %s\n", + http, strerror ( rc ) ); + + http_done ( http, rc ); +} + +/** HTTP socket operations */ +static struct xfer_interface_operations http_socket_operations = { + .close = http_socket_close, + .vredirect = xfer_vreopen, + .window = unlimited_xfer_window, + .alloc_iob = default_xfer_alloc_iob, + .deliver_iob = http_socket_deliver_iob, + .deliver_raw = xfer_deliver_as_iob, +}; + +/** + * Close HTTP data transfer interface + * + * @v xfer Data transfer interface + * @v rc Reason for close + */ +static void http_xfer_close ( struct xfer_interface *xfer, int rc ) { + struct http_request *http = + container_of ( xfer, struct http_request, xfer ); + + DBGC ( http, "HTTP %p interface closed: %s\n", + http, strerror ( rc ) ); + + http_done ( http, rc ); +} + +/** HTTP data transfer interface operations */ +static struct xfer_interface_operations http_xfer_operations = { + .close = http_xfer_close, + .vredirect = ignore_xfer_vredirect, + .window = unlimited_xfer_window, + .alloc_iob = default_xfer_alloc_iob, + .deliver_iob = xfer_deliver_as_raw, + .deliver_raw = ignore_xfer_deliver_raw, +}; + +/** + * Initiate an HTTP connection, with optional filter + * + * @v xfer Data transfer interface + * @v uri Uniform Resource Identifier + * @v default_port Default port number + * @v filter Filter to apply to socket, or NULL + * @ret rc Return status code + */ +int http_open_filter ( struct xfer_interface *xfer, struct uri *uri, + unsigned int default_port, + int ( * filter ) ( struct xfer_interface *xfer, + struct xfer_interface **next ) ) { + struct http_request *http; + struct sockaddr_tcpip server; + struct xfer_interface *socket; + int rc; + + /* Sanity checks */ + if ( ! uri->host ) + return -EINVAL; + + /* Allocate and populate HTTP structure */ + http = zalloc ( sizeof ( *http ) ); + if ( ! http ) + return -ENOMEM; + http->refcnt.free = http_free; + xfer_init ( &http->xfer, &http_xfer_operations, &http->refcnt ); + http->uri = uri_get ( uri ); + xfer_init ( &http->socket, &http_socket_operations, &http->refcnt ); + process_init ( &http->process, http_step, &http->refcnt ); + + /* Open socket */ + memset ( &server, 0, sizeof ( server ) ); + server.st_port = htons ( uri_port ( http->uri, default_port ) ); + socket = &http->socket; + if ( filter ) { + if ( ( rc = filter ( socket, &socket ) ) != 0 ) + goto err; + } + if ( ( rc = xfer_open_named_socket ( socket, SOCK_STREAM, + ( struct sockaddr * ) &server, + uri->host, NULL ) ) != 0 ) + goto err; + + /* Attach to parent interface, mortalise self, and return */ + xfer_plug_plug ( &http->xfer, xfer ); + ref_put ( &http->refcnt ); + return 0; + + err: + DBGC ( http, "HTTP %p could not create request: %s\n", + http, strerror ( rc ) ); + http_done ( http, rc ); + ref_put ( &http->refcnt ); + return rc; +} + +/** + * Initiate an HTTP connection + * + * @v xfer Data transfer interface + * @v uri Uniform Resource Identifier + * @ret rc Return status code + */ +static int http_open ( struct xfer_interface *xfer, struct uri *uri ) { + return http_open_filter ( xfer, uri, HTTP_PORT, NULL ); +} + +/** HTTP URI opener */ +struct uri_opener http_uri_opener __uri_opener = { + .scheme = "http", + .open = http_open, +}; -- cgit v1.2.3