blob: 694e1993418f26f460d629f0c560e1690e9e9213 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* An HTTP driver
*
* HTTP_PROTOCOL
* HTTP_SERVICE_BINDING_PROTOCOL
* IP4_CONFIG2_PROTOCOL
*/
#include <charset.h>
#include <efi_loader.h>
#include <image.h>
#include <malloc.h>
#include <mapmem.h>
#include <net.h>
static const efi_guid_t efi_http_service_binding_guid = EFI_HTTP_SERVICE_BINDING_PROTOCOL_GUID;
static const efi_guid_t efi_http_guid = EFI_HTTP_PROTOCOL_GUID;
/**
* struct efi_http_instance - EFI object representing an HTTP protocol instance
*
* @http: EFI_HTTP_PROTOCOL interface
* @handle: handle to efi object
* @configured: configuration status
* @http_load_addr: data buffer
* @file_size: size of data
* @current_offset: offset in data buffer
* @status_code: HTTP status code
* @num_headers: number of received headers
* @headers: array of headers
* @headers_buffer: raw buffer with headers
*/
struct efi_http_instance {
struct efi_http_protocol http;
efi_handle_t handle;
bool configured;
void *http_load_addr;
ulong file_size;
ulong current_offset;
u32 status_code;
ulong num_headers;
struct http_header headers[MAX_HTTP_HEADERS];
char headers_buffer[MAX_HTTP_HEADERS_SIZE];
};
static int num_instances;
/*
* efi_u32_to_httpstatus() - convert u32 to status
*
*/
enum efi_http_status_code efi_u32_to_httpstatus(u32 status);
/*
* efi_http_send_data() - sends data to client
*
*
* @client_buffer: client buffer to send data to
* @client_buffer_size: size of the client buffer
* @inst: HTTP instance for which to send data
*
* Return: status code
*/
static efi_status_t efi_http_send_data(void *client_buffer,
efi_uintn_t *client_buffer_size,
struct efi_http_instance *inst)
{
efi_status_t ret = EFI_SUCCESS;
ulong total_size, transfer_size;
uchar *ptr;
// Amount of data left;
total_size = inst->file_size;
transfer_size = total_size - inst->current_offset;
debug("efi_http: sending data to client, total size %lu\n", total_size);
// Amount of data the client is willing to receive
if (transfer_size > *client_buffer_size)
transfer_size = *client_buffer_size;
else
*client_buffer_size = transfer_size;
debug("efi_http: transfer size %lu\n", transfer_size);
if (!transfer_size) // Ok, only headers
goto out;
if (!client_buffer) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
// Send data
ptr = (uchar *)inst->http_load_addr + inst->current_offset;
memcpy(client_buffer, ptr, transfer_size);
inst->current_offset += transfer_size;
// Whole file served, clean the buffer:
if (inst->current_offset == inst->file_size) {
efi_free_pool(inst->http_load_addr);
inst->http_load_addr = NULL;
inst->current_offset = 0;
inst->file_size = 0;
}
out:
return ret;
}
/* EFI_HTTP_PROTOCOL */
/*
* efi_http_get_mode_data() - Gets the current operational status.
*
* This function implements EFI_HTTP_PROTOCOL.GetModeData().
* See the Unified Extensible Firmware Interface
* (UEFI) specification for details.
*
* @this: pointer to the protocol instance
* @data: pointer to the buffer for operational parameters
* of this HTTP instance
* Return: status code
*/
static efi_status_t EFIAPI efi_http_get_mode_data(struct efi_http_protocol *this,
struct efi_http_config_data *data)
{
EFI_ENTRY("%p, %p", this, data);
efi_status_t ret = EFI_UNSUPPORTED;
return EFI_EXIT(ret);
}
/*
* efi_http_configure() - Initializes operational status for this
* EFI HTTP instance.
*
* This function implements EFI_HTTP_PROTOCOL.Configure().
* See the Unified Extensible Firmware Interface
* (UEFI) specification for details.
*
* @this: pointer to the protocol instance
* @data: pointer to the buffer for operational parameters of
* this HTTP instance
* Return: status code
*/
static efi_status_t EFIAPI efi_http_configure(struct efi_http_protocol *this,
struct efi_http_config_data *data)
{
EFI_ENTRY("%p, %p", this, data);
efi_status_t ret = EFI_SUCCESS;
enum efi_http_version http_version;
struct efi_httpv4_access_point *ipv4_node;
struct efi_http_instance *http_instance;
if (!this) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
http_instance = (struct efi_http_instance *)this;
if (!data) {
efi_free_pool(http_instance->http_load_addr);
http_instance->http_load_addr = NULL;
http_instance->current_offset = 0;
http_instance->configured = false;
goto out;
}
if (http_instance->configured) {
ret = EFI_ALREADY_STARTED;
goto out;
}
http_version = data->http_version;
ipv4_node = data->access_point.ipv4_node;
if ((http_version != HTTPVERSION10 &&
http_version != HTTPVERSION11) ||
data->is_ipv6 || !ipv4_node) { /* Only support ipv4 */
ret = EFI_UNSUPPORTED;
goto out;
}
if (!ipv4_node->use_default_address) {
efi_net_set_addr((struct efi_ipv4_address *)&ipv4_node->local_address,
(struct efi_ipv4_address *)&ipv4_node->local_subnet, NULL);
}
http_instance->current_offset = 0;
http_instance->configured = true;
out:
return EFI_EXIT(ret);
}
/*
* efi_http_request() - Queues an HTTP request to this HTTP instance
*
* This function implements EFI_HTTP_PROTOCOL.Request().
* See the Unified Extensible Firmware Interface
* (UEFI) specification for details.
*
* @this: pointer to the protocol instance
* @token: pointer to storage containing HTTP request token
* Return: status code
*/
static efi_status_t EFIAPI efi_http_request(struct efi_http_protocol *this,
struct efi_http_token *token)
{
EFI_ENTRY("%p, %p", this, token);
efi_status_t ret = EFI_SUCCESS;
u8 *tmp;
u8 url_8[1024];
u16 *url_16;
enum efi_http_method current_method;
struct efi_http_instance *http_instance;
if (!token || !this || !token->message ||
!token->message->data.request) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
http_instance = (struct efi_http_instance *)this;
if (!http_instance->configured) {
ret = EFI_NOT_STARTED;
goto out;
}
current_method = token->message->data.request->method;
url_16 = token->message->data.request->url;
/* Parse URL. It comes in UCS-2 encoding and follows RFC3986 */
tmp = url_8;
utf16_utf8_strncpy((char **)&tmp, url_16, 1024);
ret = efi_net_do_request(url_8, current_method, &http_instance->http_load_addr,
&http_instance->status_code, &http_instance->file_size,
http_instance->headers_buffer);
if (ret != EFI_SUCCESS)
goto out;
// We have a successful request
efi_net_parse_headers(&http_instance->num_headers, http_instance->headers);
http_instance->current_offset = 0;
token->status = EFI_SUCCESS;
goto out_signal;
out_signal:
efi_signal_event(token->event);
out:
return EFI_EXIT(ret);
}
/*
* efi_http_cancel() - Abort an asynchronous HTTP request or response token
*
* This function implements EFI_HTTP_PROTOCOL.Cancel().
* See the Unified Extensible Firmware Interface
* (UEFI) specification for details.
*
* @this: pointer to the protocol instance
* @token: pointer to storage containing HTTP request token
* Return: status code
*/
static efi_status_t EFIAPI efi_http_cancel(struct efi_http_protocol *this,
struct efi_http_token *token)
{
EFI_ENTRY("%p, %p", this, token);
efi_status_t ret = EFI_UNSUPPORTED;
return EFI_EXIT(ret);
}
/*
* efi_http_response() - Queues an HTTP response to this HTTP instance
*
* This function implements EFI_HTTP_PROTOCOL.Response().
* See the Unified Extensible Firmware Interface
* (UEFI) specification for details.
*
* @this: pointer to the protocol instance
* @token: pointer to storage containing HTTP request token
* Return: status code
*/
static efi_status_t EFIAPI efi_http_response(struct efi_http_protocol *this,
struct efi_http_token *token)
{
EFI_ENTRY("%p, %p", this, token);
efi_status_t ret = EFI_SUCCESS;
struct efi_http_instance *http_instance;
struct efi_http_header **client_headers;
struct efi_http_response_data *response;
if (!token || !this || !token->message) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
http_instance = (struct efi_http_instance *)this;
// Set HTTP status code
if (token->message->data.response) { // TODO extra check, see spec.
response = token->message->data.response;
response->status_code = efi_u32_to_httpstatus(http_instance->status_code);
}
client_headers = &token->message->headers;
ret = efi_allocate_pool(EFI_BOOT_SERVICES_DATA,
(http_instance->num_headers) * sizeof(struct efi_http_header),
(void **)client_headers); // This is deallocated by the client.
if (ret != EFI_SUCCESS)
goto out_bad_signal;
// Send headers
token->message->header_count = http_instance->num_headers;
for (int i = 0; i < http_instance->num_headers; i++) {
(*client_headers)[i].field_name = http_instance->headers[i].name;
(*client_headers)[i].field_value = http_instance->headers[i].value;
}
ret = efi_http_send_data(token->message->body, &token->message->body_length, http_instance);
if (ret != EFI_SUCCESS)
goto out_bad_signal;
token->status = EFI_SUCCESS;
goto out_signal;
out_bad_signal:
token->status = EFI_ABORTED;
out_signal:
efi_signal_event(token->event);
out:
return EFI_EXIT(ret);
}
/*
* efi_http_poll() - Polls for incoming data packets and processes outgoing data packets
*
* This function implements EFI_HTTP_PROTOCOL.Poll().
* See the Unified Extensible Firmware Interface
* (UEFI) specification for details.
*
* @this: pointer to the protocol instance
* @token: pointer to storage containing HTTP request token
* Return: status code
*/
static efi_status_t EFIAPI efi_http_poll(struct efi_http_protocol *this)
{
EFI_ENTRY("%p", this);
efi_status_t ret = EFI_UNSUPPORTED;
return EFI_EXIT(ret);
}
/* EFI_HTTP_SERVICE_BINDING_PROTOCOL */
/*
* efi_http_service_binding_create_child() - Creates a child handle
* and installs a protocol
*
* This function implements EFI_HTTP_SERVICE_BINDING.CreateChild().
* See the Unified Extensible Firmware Interface
* (UEFI) specification for details.
*
* @this: pointer to the protocol instance
* @child_handle: pointer to child handle
* Return: status code
*/
static efi_status_t EFIAPI efi_http_service_binding_create_child(
struct efi_service_binding_protocol *this,
efi_handle_t *child_handle)
{
EFI_ENTRY("%p, %p", this, child_handle);
efi_status_t ret = EFI_SUCCESS;
struct efi_http_instance *new_instance;
if (!child_handle)
return EFI_EXIT(EFI_INVALID_PARAMETER);
new_instance = calloc(1, sizeof(struct efi_http_instance));
if (!new_instance) {
ret = EFI_OUT_OF_RESOURCES;
goto failure_to_add_protocol;
}
if (*child_handle) {
new_instance->handle = *child_handle;
goto install;
}
new_instance->handle = calloc(1, sizeof(struct efi_object));
if (!new_instance->handle) {
efi_free_pool((void *)new_instance);
ret = EFI_OUT_OF_RESOURCES;
goto failure_to_add_protocol;
}
efi_add_handle(new_instance->handle);
*child_handle = new_instance->handle;
install:
ret = efi_add_protocol(new_instance->handle, &efi_http_guid,
&new_instance->http);
if (ret != EFI_SUCCESS)
goto failure_to_add_protocol;
new_instance->http.get_mode_data = efi_http_get_mode_data;
new_instance->http.configure = efi_http_configure;
new_instance->http.request = efi_http_request;
new_instance->http.cancel = efi_http_cancel;
new_instance->http.response = efi_http_response;
new_instance->http.poll = efi_http_poll;
++num_instances;
return EFI_EXIT(EFI_SUCCESS);
failure_to_add_protocol:
return EFI_EXIT(ret);
}
/*
* efi_http_service_binding_destroy_child() - Destroys a child handle with
* a protocol installed on it
*
* This function implements EFI_HTTP_SERVICE_BINDING.DestroyChild().
* See the Unified Extensible Firmware Interface
* (UEFI) specification for details.
*
* @this: pointer to the protocol instance
* @child_handle: child handle
* Return: status code
*/
static efi_status_t EFIAPI efi_http_service_binding_destroy_child(
struct efi_service_binding_protocol *this,
efi_handle_t child_handle)
{
EFI_ENTRY("%p, %p", this, child_handle);
efi_status_t ret = EFI_SUCCESS;
struct efi_http_instance *http_instance;
struct efi_handler *phandler;
void *protocol_interface;
if (num_instances == 0)
return EFI_EXIT(EFI_NOT_FOUND);
if (!child_handle)
return EFI_EXIT(EFI_INVALID_PARAMETER);
efi_search_protocol(child_handle, &efi_http_guid, &phandler);
if (phandler)
protocol_interface = phandler->protocol_interface;
ret = efi_delete_handle(child_handle);
if (ret != EFI_SUCCESS)
return EFI_EXIT(ret);
http_instance = (struct efi_http_instance *)protocol_interface;
efi_free_pool(http_instance->http_load_addr);
http_instance->http_load_addr = NULL;
free(protocol_interface);
num_instances--;
return EFI_EXIT(EFI_SUCCESS);
}
/**
* efi_http_register() - register the http protocol
*
*/
efi_status_t efi_http_register(const efi_handle_t handle,
struct efi_service_binding_protocol *http_service_binding)
{
efi_status_t r = EFI_SUCCESS;
r = efi_add_protocol(handle, &efi_http_service_binding_guid,
http_service_binding);
if (r != EFI_SUCCESS)
goto failure_to_add_protocol;
http_service_binding->create_child = efi_http_service_binding_create_child;
http_service_binding->destroy_child = efi_http_service_binding_destroy_child;
return EFI_SUCCESS;
failure_to_add_protocol:
return r;
}
enum efi_http_status_code efi_u32_to_httpstatus(u32 status)
{
switch (status) {
case 100: return HTTP_STATUS_100_CONTINUE;
case 101: return HTTP_STATUS_101_SWITCHING_PROTOCOLS;
case 200: return HTTP_STATUS_200_OK;
case 201: return HTTP_STATUS_201_CREATED;
case 202: return HTTP_STATUS_202_ACCEPTED;
case 203: return HTTP_STATUS_203_NON_AUTHORITATIVE_INFORMATION;
case 204: return HTTP_STATUS_204_NO_CONTENT;
case 205: return HTTP_STATUS_205_RESET_CONTENT;
case 206: return HTTP_STATUS_206_PARTIAL_CONTENT;
case 300: return HTTP_STATUS_300_MULTIPLE_CHOICES;
case 301: return HTTP_STATUS_301_MOVED_PERMANENTLY;
case 302: return HTTP_STATUS_302_FOUND;
case 303: return HTTP_STATUS_303_SEE_OTHER;
case 304: return HTTP_STATUS_304_NOT_MODIFIED;
case 305: return HTTP_STATUS_305_USE_PROXY;
case 307: return HTTP_STATUS_307_TEMPORARY_REDIRECT;
case 400: return HTTP_STATUS_400_BAD_REQUEST;
case 401: return HTTP_STATUS_401_UNAUTHORIZED;
case 402: return HTTP_STATUS_402_PAYMENT_REQUIRED;
case 403: return HTTP_STATUS_403_FORBIDDEN;
case 404: return HTTP_STATUS_404_NOT_FOUND;
case 405: return HTTP_STATUS_405_METHOD_NOT_ALLOWED;
case 406: return HTTP_STATUS_406_NOT_ACCEPTABLE;
case 407: return HTTP_STATUS_407_PROXY_AUTHENTICATION_REQUIRED;
case 408: return HTTP_STATUS_408_REQUEST_TIME_OUT;
case 409: return HTTP_STATUS_409_CONFLICT;
case 410: return HTTP_STATUS_410_GONE;
case 411: return HTTP_STATUS_411_LENGTH_REQUIRED;
case 412: return HTTP_STATUS_412_PRECONDITION_FAILED;
case 413: return HTTP_STATUS_413_REQUEST_ENTITY_TOO_LARGE;
case 414: return HTTP_STATUS_414_REQUEST_URI_TOO_LARGE;
case 415: return HTTP_STATUS_415_UNSUPPORTED_MEDIA_TYPE;
case 416: return HTTP_STATUS_416_REQUESTED_RANGE_NOT_SATISFIED;
case 417: return HTTP_STATUS_417_EXPECTATION_FAILED;
case 500: return HTTP_STATUS_500_INTERNAL_SERVER_ERROR;
case 501: return HTTP_STATUS_501_NOT_IMPLEMENTED;
case 502: return HTTP_STATUS_502_BAD_GATEWAY;
case 503: return HTTP_STATUS_503_SERVICE_UNAVAILABLE;
case 504: return HTTP_STATUS_504_GATEWAY_TIME_OUT;
case 505: return HTTP_STATUS_505_HTTP_VERSION_NOT_SUPPORTED;
case 308: return HTTP_STATUS_308_PERMANENT_REDIRECT;
default: return HTTP_STATUS_UNSUPPORTED_STATUS;
}
}