1 // SPDX-License-Identifier: BSD-2-Clause-Patent
4 * Copyright 2015 SUSE LINUX GmbH <glin@suse.com>
6 * Significant portions of this code are derived from Tianocore
7 * (http://tianocore.sf.net) and are Copyright 2009-2012 Intel
13 ascii_to_int (CONST CHAR8
*str
)
18 // skip preceeding white space
19 while (*str
&& *str
== ' ') {
25 while ((c
= *(str
++))) {
26 if (c
>= '0' && c
<= '9') {
27 u
= (u
* 10) + c
- '0';
37 convert_http_status_code (EFI_HTTP_STATUS_CODE status_code
)
39 if (status_code
>= HTTP_STATUS_100_CONTINUE
&&
40 status_code
< HTTP_STATUS_200_OK
) {
41 return (status_code
- HTTP_STATUS_100_CONTINUE
+ 100);
42 } else if (status_code
>= HTTP_STATUS_200_OK
&&
43 status_code
< HTTP_STATUS_300_MULTIPLE_CHIOCES
) {
44 return (status_code
- HTTP_STATUS_200_OK
+ 200);
45 } else if (status_code
>= HTTP_STATUS_300_MULTIPLE_CHIOCES
&&
46 status_code
< HTTP_STATUS_400_BAD_REQUEST
) {
47 return (status_code
- HTTP_STATUS_300_MULTIPLE_CHIOCES
+ 300);
48 } else if (status_code
>= HTTP_STATUS_400_BAD_REQUEST
&&
49 status_code
< HTTP_STATUS_500_INTERNAL_SERVER_ERROR
) {
50 return (status_code
- HTTP_STATUS_400_BAD_REQUEST
+ 400);
51 } else if (status_code
>= HTTP_STATUS_500_INTERNAL_SERVER_ERROR
) {
52 return (status_code
- HTTP_STATUS_500_INTERNAL_SERVER_ERROR
+ 500);
58 static EFI_DEVICE_PATH
*devpath
;
59 static EFI_MAC_ADDRESS mac_addr
;
60 static IPv4_DEVICE_PATH ip4_node
;
61 static IPv6_DEVICE_PATH ip6_node
;
62 static BOOLEAN is_ip6
;
66 find_httpboot (EFI_HANDLE device
)
68 EFI_DEVICE_PATH
*unpacked
;
69 EFI_DEVICE_PATH
*Node
;
70 MAC_ADDR_DEVICE_PATH
*MacNode
;
71 URI_DEVICE_PATH
*UriNode
;
73 BOOLEAN ip_found
= FALSE
;
81 devpath
= DevicePathFromHandle(device
);
83 perror(L
"Failed to get device path\n");
87 unpacked
= UnpackDevicePath(devpath
);
89 perror(L
"Failed to unpack device path\n");
94 /* Traverse the device path to find IPv4()/.../Uri() or
96 while (!IsDevicePathEnd(Node
)) {
97 /* Save the MAC node so we can match the net card later */
98 if (DevicePathType(Node
) == MESSAGING_DEVICE_PATH
&&
99 DevicePathSubType(Node
) == MSG_MAC_ADDR_DP
) {
100 MacNode
= (MAC_ADDR_DEVICE_PATH
*)Node
;
101 CopyMem(&mac_addr
, &MacNode
->MacAddress
,
102 sizeof(EFI_MAC_ADDRESS
));
103 } else if (DevicePathType(Node
) == MESSAGING_DEVICE_PATH
&&
104 (DevicePathSubType(Node
) == MSG_IPv4_DP
||
105 DevicePathSubType(Node
) == MSG_IPv6_DP
)) {
106 /* Save the IP node so we can set up the connection */
108 if (DevicePathSubType(Node
) == MSG_IPv6_DP
) {
109 CopyMem(&ip6_node
, Node
,
110 sizeof(IPv6_DEVICE_PATH
));
113 CopyMem(&ip4_node
, Node
,
114 sizeof(IPv4_DEVICE_PATH
));
119 } else if (ip_found
== TRUE
&&
120 (DevicePathType(Node
) == MESSAGING_DEVICE_PATH
&&
121 DevicePathSubType(Node
) == MSG_URI_DP
)) {
122 EFI_DEVICE_PATH
*NextNode
;
124 /* Check if the URI node is the last node since the */
125 /* RAMDISK node could be appended, and we don't need */
126 /* to download the second stage loader in that case. */
127 NextNode
= NextDevicePathNode(Node
);
128 if (!IsDevicePathEnd(NextNode
))
131 /* Save the current URI */
132 UriNode
= (URI_DEVICE_PATH
*)Node
;
133 uri_size
= strlen(UriNode
->Uri
);
134 uri
= AllocatePool(uri_size
+ 1);
136 perror(L
"Failed to allocate uri\n");
139 CopyMem(uri
, UriNode
->Uri
, uri_size
+ 1);
143 Node
= NextDevicePathNode(Node
);
151 generate_next_uri (CONST CHAR8
*current_uri
, CONST CHAR8
*next_loader
,
159 if (strncmp(current_uri
, (CHAR8
*)"http://", 7) == 0) {
160 ptr
= current_uri
+ 7;
162 } else if (strncmp(current_uri
, (CHAR8
*)"https://", 8) == 0) {
163 ptr
= current_uri
+ 8;
166 return EFI_INVALID_PARAMETER
;
169 /* Extract the path */
170 next_len
= strlen(next_loader
);
171 while (*ptr
!= '\0') {
178 *uri
= AllocatePool(sizeof(CHAR8
) * (path_len
+ next_len
+ 1));
180 return EFI_OUT_OF_RESOURCES
;
182 CopyMem(*uri
, (void *)current_uri
, path_len
);
183 CopyMem(*uri
+ path_len
, (void *)next_loader
, next_len
);
184 (*uri
)[path_len
+ next_len
] = '\0';
190 extract_hostname (CONST CHAR8
*url
, CHAR8
**hostname
)
192 CONST CHAR8
*ptr
, *start
;
195 if (strncmp(url
, (CHAR8
*)"http://", 7) == 0)
197 else if (strncmp(url
, (CHAR8
*)"https://", 8) == 0)
200 return EFI_INVALID_PARAMETER
;
203 while (*ptr
!= '/' && *ptr
!= '\0') {
208 *hostname
= AllocatePool(sizeof(CHAR8
) * (host_len
+ 1));
210 return EFI_OUT_OF_RESOURCES
;
212 CopyMem(*hostname
, (void *)start
, host_len
);
213 (*hostname
)[host_len
] = '\0';
218 #define SAME_MAC_ADDR(a, b) (!CompareMem(a, b, sizeof(EFI_MAC_ADDRESS)))
221 get_nic_handle (EFI_MAC_ADDRESS
*mac
)
223 EFI_DEVICE_PATH
*unpacked
= NULL
;
224 EFI_DEVICE_PATH
*Node
;
225 EFI_DEVICE_PATH
*temp_path
= NULL
;
226 MAC_ADDR_DEVICE_PATH
*MacNode
;
227 EFI_HANDLE handle
= NULL
;
231 EFI_STATUS efi_status
;
233 /* Get the list of handles that support the HTTP service binding
235 efi_status
= BS
->LocateHandleBuffer(ByProtocol
,
236 &EFI_HTTP_BINDING_GUID
,
237 NULL
, &NoHandles
, &buffer
);
238 if (EFI_ERROR(efi_status
))
241 for (i
= 0; i
< NoHandles
; i
++) {
242 temp_path
= DevicePathFromHandle(buffer
[i
]);
244 /* Match the MAC address */
245 unpacked
= UnpackDevicePath(temp_path
);
247 perror(L
"Failed to unpack device path\n");
251 while (!IsDevicePathEnd(Node
)) {
252 if (DevicePathType(Node
) == MESSAGING_DEVICE_PATH
&&
253 DevicePathSubType(Node
) == MSG_MAC_ADDR_DP
) {
254 MacNode
= (MAC_ADDR_DEVICE_PATH
*)Node
;
255 if (SAME_MAC_ADDR(mac
, &MacNode
->MacAddress
)) {
260 Node
= NextDevicePathNode(Node
);
275 is_unspecified_ip6addr (EFI_IPv6_ADDRESS ip6
)
279 for (i
= 0; i
<16; i
++) {
280 if (ip6
.Addr
[i
] != 0)
288 print_ip6_addr(EFI_IPv6_ADDRESS ip6addr
)
290 perror(L
"%x:%x:%x:%x:%x:%x:%x:%x\n",
291 ip6addr
.Addr
[0] << 8 | ip6addr
.Addr
[1],
292 ip6addr
.Addr
[2] << 8 | ip6addr
.Addr
[3],
293 ip6addr
.Addr
[4] << 8 | ip6addr
.Addr
[5],
294 ip6addr
.Addr
[6] << 8 | ip6addr
.Addr
[7],
295 ip6addr
.Addr
[8] << 8 | ip6addr
.Addr
[9],
296 ip6addr
.Addr
[10] << 8 | ip6addr
.Addr
[11],
297 ip6addr
.Addr
[12] << 8 | ip6addr
.Addr
[13],
298 ip6addr
.Addr
[14] << 8 | ip6addr
.Addr
[15]);
302 set_ip6(EFI_HANDLE
*nic
, IPv6_DEVICE_PATH
*ip6node
)
304 EFI_IP6_CONFIG_PROTOCOL
*ip6cfg
;
305 EFI_IP6_CONFIG_MANUAL_ADDRESS ip6
;
306 EFI_IPv6_ADDRESS gateway
;
307 EFI_STATUS efi_status
;
309 efi_status
= BS
->HandleProtocol(nic
, &EFI_IP6_CONFIG_GUID
,
311 if (EFI_ERROR(efi_status
))
314 ip6
.Address
= ip6node
->LocalIpAddress
;
315 ip6
.PrefixLength
= ip6node
->PrefixLength
;
316 ip6
.IsAnycast
= FALSE
;
317 efi_status
= ip6cfg
->SetData(ip6cfg
, Ip6ConfigDataTypeManualAddress
,
319 if (EFI_ERROR(efi_status
)) {
320 perror(L
"Failed to set IPv6 Address:\nIP: ");
321 print_ip6_addr(ip6
.Address
);
322 perror(L
"Prefix Length: %u\n", ip6
.PrefixLength
);
326 gateway
= ip6node
->GatewayIpAddress
;
327 if (is_unspecified_ip6addr(gateway
))
330 efi_status
= ip6cfg
->SetData(ip6cfg
, Ip6ConfigDataTypeGateway
,
331 sizeof(gateway
), &gateway
);
332 if (EFI_ERROR(efi_status
)) {
333 perror(L
"Failed to set IPv6 Gateway:\nIP: ");
334 print_ip6_addr(gateway
);
342 is_unspecified_ip4addr (EFI_IPv4_ADDRESS ip4
)
346 for (i
= 0; i
<4; i
++) {
347 if (ip4
.Addr
[i
] != 0)
355 print_ip4_addr(EFI_IPv4_ADDRESS ip4addr
)
357 perror(L
"%u.%u.%u.%u\n",
358 ip4addr
.Addr
[0], ip4addr
.Addr
[1],
359 ip4addr
.Addr
[2], ip4addr
.Addr
[3]);
363 set_ip4(EFI_HANDLE
*nic
, IPv4_DEVICE_PATH
*ip4node
)
365 EFI_IP4_CONFIG2_PROTOCOL
*ip4cfg2
;
366 EFI_IP4_CONFIG2_MANUAL_ADDRESS ip4
;
367 EFI_IPv4_ADDRESS gateway
;
368 EFI_STATUS efi_status
;
370 efi_status
= BS
->HandleProtocol(nic
, &EFI_IP4_CONFIG2_GUID
,
372 if (EFI_ERROR(efi_status
))
375 ip4
.Address
= ip4node
->LocalIpAddress
;
376 ip4
.SubnetMask
= ip4node
->SubnetMask
;
377 efi_status
= ip4cfg2
->SetData(ip4cfg2
, Ip4Config2DataTypeManualAddress
,
379 if (EFI_ERROR(efi_status
)) {
380 perror(L
"Failed to Set IPv4 Address:\nIP: ");
381 print_ip4_addr(ip4
.Address
);
383 print_ip4_addr(ip4
.SubnetMask
);
387 gateway
= ip4node
->GatewayIpAddress
;
388 if (is_unspecified_ip4addr(gateway
))
391 efi_status
= ip4cfg2
->SetData(ip4cfg2
, Ip4Config2DataTypeGateway
,
392 sizeof(gateway
), &gateway
);
393 if (EFI_ERROR(efi_status
)) {
394 perror(L
"Failed to Set IPv4 Gateway:\nGateway: ");
395 print_ip4_addr(gateway
);
403 httpnotify (EFI_EVENT Event UNUSED
, VOID
*Context
)
405 *((BOOLEAN
*) Context
) = TRUE
;
409 configure_http (EFI_HTTP_PROTOCOL
*http
, BOOLEAN is_ip6
)
411 EFI_HTTP_CONFIG_DATA http_mode
;
412 EFI_HTTPv4_ACCESS_POINT ip4node
;
413 EFI_HTTPv6_ACCESS_POINT ip6node
;
416 ZeroMem(&http_mode
, sizeof(http_mode
));
417 http_mode
.HttpVersion
= HttpVersion11
;
418 /* use the default time out */
419 http_mode
.TimeOutMillisec
= 0;
422 http_mode
.LocalAddressIsIPv6
= FALSE
;
423 ZeroMem(&ip4node
, sizeof(ip4node
));
424 ip4node
.UseDefaultAddress
= TRUE
;
425 http_mode
.AccessPoint
.IPv4Node
= &ip4node
;
427 http_mode
.LocalAddressIsIPv6
= TRUE
;
428 ZeroMem(&ip6node
, sizeof(ip6node
));
429 http_mode
.AccessPoint
.IPv6Node
= &ip6node
;
432 return http
->Configure(http
, &http_mode
);
436 send_http_request (EFI_HTTP_PROTOCOL
*http
, CHAR8
*hostname
, CHAR8
*uri
)
438 EFI_HTTP_TOKEN tx_token
;
439 EFI_HTTP_MESSAGE tx_message
;
440 EFI_HTTP_REQUEST_DATA request
;
441 EFI_HTTP_HEADER headers
[3];
442 BOOLEAN request_done
;
444 EFI_STATUS efi_status
;
445 EFI_STATUS event_status
;
447 /* Convert the ascii string to the UCS2 string */
448 Url
= PoolPrint(L
"%a", uri
);
450 return EFI_OUT_OF_RESOURCES
;
452 request
.Method
= HttpMethodGet
;
455 /* Prepare the HTTP headers */
456 headers
[0].FieldName
= (CHAR8
*)"Host";
457 headers
[0].FieldValue
= hostname
;
458 headers
[1].FieldName
= (CHAR8
*)"Accept";
459 headers
[1].FieldValue
= (CHAR8
*)"*/*";
460 headers
[2].FieldName
= (CHAR8
*)"User-Agent";
461 headers
[2].FieldValue
= (CHAR8
*)"UefiHttpBoot/1.0";
463 tx_message
.Data
.Request
= &request
;
464 tx_message
.HeaderCount
= 3;
465 tx_message
.Headers
= headers
;
466 tx_message
.BodyLength
= 0;
467 tx_message
.Body
= NULL
;
469 tx_token
.Status
= EFI_NOT_READY
;
470 tx_token
.Message
= &tx_message
;
471 tx_token
.Event
= NULL
;
472 request_done
= FALSE
;
473 efi_status
= BS
->CreateEvent(EVT_NOTIFY_SIGNAL
, TPL_NOTIFY
,
474 httpnotify
, &request_done
,
476 if (EFI_ERROR(efi_status
)) {
477 perror(L
"Failed to Create Event for HTTP request: %r\n",
482 /* Send out the request */
483 efi_status
= http
->Request(http
, &tx_token
);
484 if (EFI_ERROR(efi_status
)) {
485 perror(L
"HTTP request failed: %r\n", efi_status
);
489 /* Wait for the response */
490 while (!request_done
)
493 if (EFI_ERROR(tx_token
.Status
)) {
494 perror(L
"HTTP request: %r\n", tx_token
.Status
);
495 efi_status
= tx_token
.Status
;
499 event_status
= BS
->CloseEvent(tx_token
.Event
);
500 if (EFI_ERROR(event_status
)) {
501 perror(L
"Failed to close Event for HTTP request: %r\n",
513 receive_http_response(EFI_HTTP_PROTOCOL
*http
, VOID
**buffer
, UINT64
*buf_size
)
515 EFI_HTTP_TOKEN rx_token
;
516 EFI_HTTP_MESSAGE rx_message
;
517 EFI_HTTP_RESPONSE_DATA response
;
518 EFI_HTTP_STATUS_CODE http_status
;
519 BOOLEAN response_done
;
521 CHAR8 rx_buffer
[9216];
522 EFI_STATUS efi_status
;
523 EFI_STATUS event_status
;
525 /* Initialize the rx message and buffer */
526 response
.StatusCode
= HTTP_STATUS_UNSUPPORTED_STATUS
;
527 rx_message
.Data
.Response
= &response
;
528 rx_message
.HeaderCount
= 0;
529 rx_message
.Headers
= 0;
530 rx_message
.BodyLength
= sizeof(rx_buffer
);
531 rx_message
.Body
= rx_buffer
;
533 rx_token
.Status
= EFI_NOT_READY
;
534 rx_token
.Message
= &rx_message
;
535 rx_token
.Event
= NULL
;
536 response_done
= FALSE
;
537 efi_status
= BS
->CreateEvent(EVT_NOTIFY_SIGNAL
, TPL_NOTIFY
,
538 httpnotify
, &response_done
,
540 if (EFI_ERROR(efi_status
)) {
541 perror(L
"Failed to Create Event for HTTP response: %r\n",
546 /* Notify the firmware to receive the HTTP messages */
547 efi_status
= http
->Response(http
, &rx_token
);
548 if (EFI_ERROR(efi_status
)) {
549 perror(L
"HTTP response failed: %r\n", efi_status
);
553 /* Wait for the response */
554 while (!response_done
)
557 if (EFI_ERROR(rx_token
.Status
)) {
558 perror(L
"HTTP response: %r\n", rx_token
.Status
);
559 efi_status
= rx_token
.Status
;
563 /* Check the HTTP status code */
564 http_status
= rx_token
.Message
->Data
.Response
->StatusCode
;
565 if (http_status
!= HTTP_STATUS_200_OK
) {
566 perror(L
"HTTP Status Code: %d\n",
567 convert_http_status_code(http_status
));
568 efi_status
= EFI_ABORTED
;
572 /* Check the length of the file */
573 for (i
= 0; i
< rx_message
.HeaderCount
; i
++) {
574 if (!strcasecmp(rx_message
.Headers
[i
].FieldName
,
575 (CHAR8
*)"Content-Length")) {
576 *buf_size
= ascii_to_int(rx_message
.Headers
[i
].FieldValue
);
580 if (*buf_size
== 0) {
581 perror(L
"Failed to get Content-Lenght\n");
585 *buffer
= AllocatePool(*buf_size
);
587 perror(L
"Failed to allocate new rx buffer\n");
591 downloaded
= rx_message
.BodyLength
;
593 CopyMem(*buffer
, rx_buffer
, downloaded
);
595 /* Retreive the rest of the message */
596 while (downloaded
< *buf_size
) {
597 if (rx_message
.Headers
) {
598 FreePool(rx_message
.Headers
);
600 rx_message
.Headers
= NULL
;
601 rx_message
.HeaderCount
= 0;
602 rx_message
.Data
.Response
= NULL
;
603 rx_message
.BodyLength
= sizeof(rx_buffer
);
604 rx_message
.Body
= rx_buffer
;
606 rx_token
.Status
= EFI_NOT_READY
;
607 response_done
= FALSE
;
609 efi_status
= http
->Response(http
, &rx_token
);
610 if (EFI_ERROR(efi_status
)) {
611 perror(L
"HTTP response failed: %r\n", efi_status
);
615 while (!response_done
)
618 if (EFI_ERROR(rx_token
.Status
)) {
619 perror(L
"HTTP response: %r\n", rx_token
.Status
);
620 efi_status
= rx_token
.Status
;
624 if (rx_message
.BodyLength
+ downloaded
> *buf_size
) {
625 efi_status
= EFI_BAD_BUFFER_SIZE
;
629 CopyMem(*buffer
+ downloaded
, rx_buffer
, rx_message
.BodyLength
);
631 downloaded
+= rx_message
.BodyLength
;
635 event_status
= BS
->CloseEvent(rx_token
.Event
);
636 if (EFI_ERROR(event_status
)) {
637 perror(L
"Failed to close Event for HTTP response: %r\n",
642 if (EFI_ERROR(efi_status
) && *buffer
)
649 http_fetch (EFI_HANDLE image
, EFI_HANDLE device
,
650 CHAR8
*hostname
, CHAR8
*uri
, BOOLEAN is_ip6
,
651 VOID
**buffer
, UINT64
*buf_size
)
653 EFI_SERVICE_BINDING
*service
;
654 EFI_HANDLE http_handle
;
655 EFI_HTTP_PROTOCOL
*http
;
656 EFI_STATUS efi_status
;
657 EFI_STATUS child_status
;
662 /* Open HTTP Service Binding Protocol */
663 efi_status
= BS
->OpenProtocol(device
, &EFI_HTTP_BINDING_GUID
,
664 (VOID
**) &service
, image
, NULL
,
665 EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
666 if (EFI_ERROR(efi_status
))
669 /* Create the ChildHandle from the Service Binding */
670 /* Set the handle to NULL to request a new handle */
672 efi_status
= service
->CreateChild(service
, &http_handle
);
673 if (EFI_ERROR(efi_status
)) {
674 perror(L
"Failed to create the ChildHandle\n");
678 /* Get the http protocol */
679 efi_status
= BS
->HandleProtocol(http_handle
, &EFI_HTTP_PROTOCOL_GUID
,
681 if (EFI_ERROR(efi_status
)) {
682 perror(L
"Failed to get http\n");
686 efi_status
= configure_http(http
, is_ip6
);
687 if (EFI_ERROR(efi_status
)) {
688 perror(L
"Failed to configure http: %r\n", efi_status
);
692 efi_status
= send_http_request(http
, hostname
, uri
);
693 if (EFI_ERROR(efi_status
)) {
694 perror(L
"Failed to send HTTP request: %r\n", efi_status
);
698 efi_status
= receive_http_response(http
, buffer
, buf_size
);
699 if (EFI_ERROR(efi_status
)) {
700 perror(L
"Failed to receive HTTP response: %r\n", efi_status
);
705 child_status
= service
->DestroyChild(service
, http_handle
);
706 if (EFI_ERROR(efi_status
)) {
708 } else if (EFI_ERROR(child_status
)) {
716 httpboot_fetch_buffer (EFI_HANDLE image
, VOID
**buffer
, UINT64
*buf_size
)
718 EFI_STATUS efi_status
;
720 CHAR8 next_loader
[sizeof DEFAULT_LOADER_CHAR
];
721 CHAR8
*next_uri
= NULL
;
722 CHAR8
*hostname
= NULL
;
725 return EFI_NOT_READY
;
727 translate_slashes(next_loader
, DEFAULT_LOADER_CHAR
);
729 /* Create the URI for the next loader based on the original URI */
730 efi_status
= generate_next_uri(uri
, next_loader
, &next_uri
);
731 if (EFI_ERROR(efi_status
)) {
732 perror(L
"Next URI: %a, %r\n", next_uri
, efi_status
);
736 /* Extract the hostname (or IP) from URI */
737 efi_status
= extract_hostname(uri
, &hostname
);
738 if (EFI_ERROR(efi_status
)) {
739 perror(L
"hostname: %a, %r\n", hostname
, efi_status
);
743 /* Get the handle that associates with the NIC we are using and
744 also supports the HTTP service binding protocol */
745 nic
= get_nic_handle(&mac_addr
);
747 efi_status
= EFI_NOT_FOUND
;
751 /* UEFI stops DHCP after fetching the image and stores the related
752 information in the device path node. We have to set up the
753 connection on our own for the further operations. */
755 efi_status
= set_ip4(nic
, &ip4_node
);
757 efi_status
= set_ip6(nic
, &ip6_node
);
758 if (EFI_ERROR(efi_status
)) {
759 perror(L
"Failed to set IP for HTTPBoot: %r\n", efi_status
);
763 /* Use HTTP protocl to fetch the remote file */
764 efi_status
= http_fetch (image
, nic
, hostname
, next_uri
, is_ip6
,
766 if (EFI_ERROR(efi_status
)) {
767 perror(L
"Failed to fetch image: %r\n", efi_status
);