diff options
Diffstat (limited to 'lib/efi_loader/efi_http.c')
| -rw-r--r-- | lib/efi_loader/efi_http.c | 548 | 
1 files changed, 548 insertions, 0 deletions
| diff --git a/lib/efi_loader/efi_http.c b/lib/efi_loader/efi_http.c new file mode 100644 index 00000000000..694e1993418 --- /dev/null +++ b/lib/efi_loader/efi_http.c @@ -0,0 +1,548 @@ +// 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; +	} +} | 
