REF: https://bugzilla.tianocore.org/show_bug.cgi?id=3507 HTTP boot has a default set forced timeout value of 5 seconds for getting the recovery image from a remote source. This change allows the HTTP boot flow to get the IO timeout value from the PcdHttpIoTimeout. PcdHttpIoTimeout value is set in platform code. Signed-off-by: Zachary Clark-Williams <zachary.clark-williams@intel.com> Cc: Maciej Rabeda <maciej.rabeda@linux.intel.com> Cc: Jiaxin Wu <jiaxin.wu@intel.com> Cc: Siyuan Fu <siyuan.fu@intel.com> Reviewed-by: Maciej Rabeda <maciej.rabeda@linux.intel.com> Reviewed-by: Jiaxin Wu <jiaxin.wu@intel.com>
		
			
				
	
	
		
			1313 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1313 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /** @file
 | |
|   Implementation of the boot file download function.
 | |
| 
 | |
| Copyright (c) 2015 - 2021, Intel Corporation. All rights reserved.<BR>
 | |
| (C) Copyright 2016 Hewlett Packard Enterprise Development LP<BR>
 | |
| SPDX-License-Identifier: BSD-2-Clause-Patent
 | |
| 
 | |
| **/
 | |
| 
 | |
| #include "HttpBootDxe.h"
 | |
| 
 | |
| /**
 | |
|   Update the device path node to include the boot resource information.
 | |
| 
 | |
|   @param[in]    Private            The pointer to the driver's private data.
 | |
| 
 | |
|   @retval EFI_SUCCESS              Device patch successfully updated.
 | |
|   @retval EFI_OUT_OF_RESOURCES     Could not allocate needed resources.
 | |
|   @retval Others                   Unexpected error happened.
 | |
| 
 | |
| **/
 | |
| EFI_STATUS
 | |
| HttpBootUpdateDevicePath (
 | |
|   IN   HTTP_BOOT_PRIVATE_DATA   *Private
 | |
|   )
 | |
| {
 | |
|   EFI_DEV_PATH               *Node;
 | |
|   EFI_DEVICE_PATH_PROTOCOL   *TmpIpDevicePath;
 | |
|   EFI_DEVICE_PATH_PROTOCOL   *TmpDnsDevicePath;
 | |
|   EFI_DEVICE_PATH_PROTOCOL   *NewDevicePath;
 | |
|   UINTN                      Length;
 | |
|   EFI_STATUS                 Status;
 | |
| 
 | |
|   TmpIpDevicePath  = NULL;
 | |
|   TmpDnsDevicePath = NULL;
 | |
| 
 | |
|   //
 | |
|   // Update the IP node with DHCP assigned information.
 | |
|   //
 | |
|   if (!Private->UsingIpv6) {
 | |
|     Node = AllocateZeroPool (sizeof (IPv4_DEVICE_PATH));
 | |
|     if (Node == NULL) {
 | |
|       return EFI_OUT_OF_RESOURCES;
 | |
|     }
 | |
|     Node->Ipv4.Header.Type    = MESSAGING_DEVICE_PATH;
 | |
|     Node->Ipv4.Header.SubType = MSG_IPv4_DP;
 | |
|     SetDevicePathNodeLength (Node, sizeof (IPv4_DEVICE_PATH));
 | |
|     CopyMem (&Node->Ipv4.LocalIpAddress, &Private->StationIp, sizeof (EFI_IPv4_ADDRESS));
 | |
|     Node->Ipv4.RemotePort      = Private->Port;
 | |
|     Node->Ipv4.Protocol        = EFI_IP_PROTO_TCP;
 | |
|     Node->Ipv4.StaticIpAddress = FALSE;
 | |
|     CopyMem (&Node->Ipv4.GatewayIpAddress, &Private->GatewayIp, sizeof (EFI_IPv4_ADDRESS));
 | |
|     CopyMem (&Node->Ipv4.SubnetMask, &Private->SubnetMask, sizeof (EFI_IPv4_ADDRESS));
 | |
|   } else {
 | |
|     Node = AllocateZeroPool (sizeof (IPv6_DEVICE_PATH));
 | |
|     if (Node == NULL) {
 | |
|       return EFI_OUT_OF_RESOURCES;
 | |
|     }
 | |
|     Node->Ipv6.Header.Type     = MESSAGING_DEVICE_PATH;
 | |
|     Node->Ipv6.Header.SubType  = MSG_IPv6_DP;
 | |
|     SetDevicePathNodeLength (Node, sizeof (IPv6_DEVICE_PATH));
 | |
|     Node->Ipv6.PrefixLength    = IP6_PREFIX_LENGTH;
 | |
|     Node->Ipv6.RemotePort      = Private->Port;
 | |
|     Node->Ipv6.Protocol        = EFI_IP_PROTO_TCP;
 | |
|     Node->Ipv6.IpAddressOrigin = 0;
 | |
|     CopyMem (&Node->Ipv6.LocalIpAddress, &Private->StationIp.v6, sizeof (EFI_IPv6_ADDRESS));
 | |
|     CopyMem (&Node->Ipv6.RemoteIpAddress, &Private->ServerIp.v6, sizeof (EFI_IPv6_ADDRESS));
 | |
|     CopyMem (&Node->Ipv6.GatewayIpAddress, &Private->GatewayIp.v6, sizeof (EFI_IPv6_ADDRESS));
 | |
|   }
 | |
| 
 | |
|   TmpIpDevicePath = AppendDevicePathNode (Private->ParentDevicePath, (EFI_DEVICE_PATH_PROTOCOL*) Node);
 | |
|   FreePool (Node);
 | |
|   if (TmpIpDevicePath == NULL) {
 | |
|     return EFI_OUT_OF_RESOURCES;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Update the DNS node with DNS server IP list if existed.
 | |
|   //
 | |
|   if (Private->DnsServerIp != NULL) {
 | |
|     Length = sizeof (EFI_DEVICE_PATH_PROTOCOL) + sizeof (Node->Dns.IsIPv6) + Private->DnsServerCount * sizeof (EFI_IP_ADDRESS);
 | |
|     Node = AllocatePool (Length);
 | |
|     if (Node == NULL) {
 | |
|       FreePool (TmpIpDevicePath);
 | |
|       return EFI_OUT_OF_RESOURCES;
 | |
|     }
 | |
|     Node->DevPath.Type    = MESSAGING_DEVICE_PATH;
 | |
|     Node->DevPath.SubType = MSG_DNS_DP;
 | |
|     SetDevicePathNodeLength (Node, Length);
 | |
|     Node->Dns.IsIPv6 = Private->UsingIpv6 ? 0x01 : 0x00;
 | |
|     CopyMem ((UINT8*) Node + sizeof (EFI_DEVICE_PATH_PROTOCOL) + sizeof (Node->Dns.IsIPv6), Private->DnsServerIp, Private->DnsServerCount * sizeof (EFI_IP_ADDRESS));
 | |
| 
 | |
|     TmpDnsDevicePath = AppendDevicePathNode (TmpIpDevicePath, (EFI_DEVICE_PATH_PROTOCOL*) Node);
 | |
|     FreePool (Node);
 | |
|     FreePool (TmpIpDevicePath);
 | |
|     TmpIpDevicePath = NULL;
 | |
|     if (TmpDnsDevicePath == NULL) {
 | |
|       return EFI_OUT_OF_RESOURCES;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Update the URI node with the boot file URI.
 | |
|   //
 | |
|   Length = sizeof (EFI_DEVICE_PATH_PROTOCOL) + AsciiStrSize (Private->BootFileUri);
 | |
|   Node = AllocatePool (Length);
 | |
|   if (Node == NULL) {
 | |
|     if (TmpIpDevicePath != NULL) {
 | |
|       FreePool (TmpIpDevicePath);
 | |
|     }
 | |
|     if (TmpDnsDevicePath != NULL) {
 | |
|       FreePool (TmpDnsDevicePath);
 | |
|     }
 | |
|     return EFI_OUT_OF_RESOURCES;
 | |
|   }
 | |
|   Node->DevPath.Type    = MESSAGING_DEVICE_PATH;
 | |
|   Node->DevPath.SubType = MSG_URI_DP;
 | |
|   SetDevicePathNodeLength (Node, Length);
 | |
|   CopyMem ((UINT8*) Node + sizeof (EFI_DEVICE_PATH_PROTOCOL), Private->BootFileUri, AsciiStrSize (Private->BootFileUri));
 | |
| 
 | |
|   if (TmpDnsDevicePath != NULL) {
 | |
|     NewDevicePath = AppendDevicePathNode (TmpDnsDevicePath, (EFI_DEVICE_PATH_PROTOCOL*) Node);
 | |
|     FreePool (TmpDnsDevicePath);
 | |
|   } else {
 | |
|     ASSERT (TmpIpDevicePath != NULL);
 | |
|     NewDevicePath = AppendDevicePathNode (TmpIpDevicePath, (EFI_DEVICE_PATH_PROTOCOL*) Node);
 | |
|     FreePool (TmpIpDevicePath);
 | |
|   }
 | |
|   FreePool (Node);
 | |
|   if (NewDevicePath == NULL) {
 | |
|     return EFI_OUT_OF_RESOURCES;
 | |
|   }
 | |
| 
 | |
|   if (!Private->UsingIpv6) {
 | |
|     //
 | |
|     // Reinstall the device path protocol of the child handle.
 | |
|     //
 | |
|     Status = gBS->ReinstallProtocolInterface (
 | |
|                     Private->Ip4Nic->Controller,
 | |
|                     &gEfiDevicePathProtocolGuid,
 | |
|                     Private->Ip4Nic->DevicePath,
 | |
|                     NewDevicePath
 | |
|                     );
 | |
|     if (EFI_ERROR (Status)) {
 | |
|       return Status;
 | |
|     }
 | |
| 
 | |
|     FreePool (Private->Ip4Nic->DevicePath);
 | |
|     Private->Ip4Nic->DevicePath = NewDevicePath;
 | |
|   } else {
 | |
|     //
 | |
|     // Reinstall the device path protocol of the child handle.
 | |
|     //
 | |
|     Status = gBS->ReinstallProtocolInterface (
 | |
|                     Private->Ip6Nic->Controller,
 | |
|                     &gEfiDevicePathProtocolGuid,
 | |
|                     Private->Ip6Nic->DevicePath,
 | |
|                     NewDevicePath
 | |
|                     );
 | |
|     if (EFI_ERROR (Status)) {
 | |
|       return Status;
 | |
|     }
 | |
|     FreePool (Private->Ip6Nic->DevicePath);
 | |
|     Private->Ip6Nic->DevicePath = NewDevicePath;
 | |
|   }
 | |
| 
 | |
|   return EFI_SUCCESS;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Parse the boot file URI information from the selected Dhcp4 offer packet.
 | |
| 
 | |
|   @param[in]    Private        The pointer to the driver's private data.
 | |
| 
 | |
|   @retval EFI_SUCCESS          Successfully parsed out all the boot information.
 | |
|   @retval Others               Failed to parse out the boot information.
 | |
| 
 | |
| **/
 | |
| EFI_STATUS
 | |
| HttpBootDhcp4ExtractUriInfo (
 | |
|   IN     HTTP_BOOT_PRIVATE_DATA   *Private
 | |
|   )
 | |
| {
 | |
|   HTTP_BOOT_DHCP4_PACKET_CACHE    *SelectOffer;
 | |
|   HTTP_BOOT_DHCP4_PACKET_CACHE    *HttpOffer;
 | |
|   UINT32                          SelectIndex;
 | |
|   UINT32                          ProxyIndex;
 | |
|   UINT32                          DnsServerIndex;
 | |
|   EFI_DHCP4_PACKET_OPTION         *Option;
 | |
|   EFI_STATUS                      Status;
 | |
| 
 | |
|   ASSERT (Private != NULL);
 | |
|   ASSERT (Private->SelectIndex != 0);
 | |
|   SelectIndex = Private->SelectIndex - 1;
 | |
|   ASSERT (SelectIndex < HTTP_BOOT_OFFER_MAX_NUM);
 | |
| 
 | |
|   DnsServerIndex = 0;
 | |
| 
 | |
|   Status = EFI_SUCCESS;
 | |
| 
 | |
|   //
 | |
|   // SelectOffer contains the IP address configuration and name server configuration.
 | |
|   // HttpOffer contains the boot file URL.
 | |
|   //
 | |
|   SelectOffer = &Private->OfferBuffer[SelectIndex].Dhcp4;
 | |
|   if (Private->FilePathUri == NULL) {
 | |
|     //
 | |
|     // In Corporate environment, we need a HttpOffer.
 | |
|     //
 | |
|     if ((SelectOffer->OfferType == HttpOfferTypeDhcpIpUri) ||
 | |
|         (SelectOffer->OfferType == HttpOfferTypeDhcpIpUriDns) ||
 | |
|         (SelectOffer->OfferType == HttpOfferTypeDhcpNameUriDns)) {
 | |
|       HttpOffer = SelectOffer;
 | |
|     } else {
 | |
|       ASSERT (Private->SelectProxyType != HttpOfferTypeMax);
 | |
|       ProxyIndex = Private->OfferIndex[Private->SelectProxyType][0];
 | |
|       HttpOffer = &Private->OfferBuffer[ProxyIndex].Dhcp4;
 | |
|     }
 | |
|     Private->BootFileUriParser = HttpOffer->UriParser;
 | |
|     Private->BootFileUri = (CHAR8*) HttpOffer->OptList[HTTP_BOOT_DHCP4_TAG_INDEX_BOOTFILE]->Data;
 | |
|   } else {
 | |
|     //
 | |
|     // In Home environment the BootFileUri comes from the FilePath.
 | |
|     //
 | |
|     Private->BootFileUriParser = Private->FilePathUriParser;
 | |
|     Private->BootFileUri = Private->FilePathUri;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Check the URI scheme.
 | |
|   //
 | |
|   Status = HttpBootCheckUriScheme (Private->BootFileUri);
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     DEBUG ((EFI_D_ERROR, "HttpBootDhcp4ExtractUriInfo: %r.\n", Status));
 | |
|     if (Status == EFI_INVALID_PARAMETER) {
 | |
|       AsciiPrint ("\n  Error: Invalid URI address.\n");
 | |
|     } else if (Status == EFI_ACCESS_DENIED) {
 | |
|       AsciiPrint ("\n  Error: Access forbidden, only HTTPS connection is allowed.\n");
 | |
|     }
 | |
|     return Status;
 | |
|   }
 | |
| 
 | |
|   if ((SelectOffer->OfferType == HttpOfferTypeDhcpNameUriDns) ||
 | |
|       (SelectOffer->OfferType == HttpOfferTypeDhcpDns) ||
 | |
|       (SelectOffer->OfferType == HttpOfferTypeDhcpIpUriDns)) {
 | |
|     Option = SelectOffer->OptList[HTTP_BOOT_DHCP4_TAG_INDEX_DNS_SERVER];
 | |
|     ASSERT (Option != NULL);
 | |
| 
 | |
|     //
 | |
|     // Record the Dns Server address list.
 | |
|     //
 | |
|     Private->DnsServerCount = (Option->Length) / sizeof (EFI_IPv4_ADDRESS);
 | |
| 
 | |
|     Private->DnsServerIp = AllocateZeroPool (Private->DnsServerCount * sizeof (EFI_IP_ADDRESS));
 | |
|     if (Private->DnsServerIp == NULL) {
 | |
|       return EFI_OUT_OF_RESOURCES;
 | |
|     }
 | |
| 
 | |
|     for (DnsServerIndex = 0; DnsServerIndex < Private->DnsServerCount; DnsServerIndex++) {
 | |
|       CopyMem (&(Private->DnsServerIp[DnsServerIndex].v4), &(((EFI_IPv4_ADDRESS *) Option->Data)[DnsServerIndex]), sizeof (EFI_IPv4_ADDRESS));
 | |
|     }
 | |
| 
 | |
|     //
 | |
|     // Configure the default DNS server if server assigned.
 | |
|     //
 | |
|     Status = HttpBootRegisterIp4Dns (
 | |
|                Private,
 | |
|                Option->Length,
 | |
|                Option->Data
 | |
|                );
 | |
|     if (EFI_ERROR (Status)) {
 | |
|       FreePool (Private->DnsServerIp);
 | |
|       Private->DnsServerIp = NULL;
 | |
|       return Status;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Extract the port from URL, and use default HTTP port 80 if not provided.
 | |
|   //
 | |
|   Status = HttpUrlGetPort (
 | |
|              Private->BootFileUri,
 | |
|              Private->BootFileUriParser,
 | |
|              &Private->Port
 | |
|              );
 | |
|   if (EFI_ERROR (Status) || Private->Port == 0) {
 | |
|     Private->Port = 80;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // All boot informations are valid here.
 | |
|   //
 | |
| 
 | |
|   //
 | |
|   // Update the device path to include the boot resource information.
 | |
|   //
 | |
|   Status = HttpBootUpdateDevicePath (Private);
 | |
|   if (EFI_ERROR (Status) && Private->DnsServerIp != NULL) {
 | |
|     FreePool (Private->DnsServerIp);
 | |
|     Private->DnsServerIp = NULL;
 | |
|   }
 | |
| 
 | |
|   return Status;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Parse the boot file URI information from the selected Dhcp6 offer packet.
 | |
| 
 | |
|   @param[in]    Private        The pointer to the driver's private data.
 | |
| 
 | |
|   @retval EFI_SUCCESS          Successfully parsed out all the boot information.
 | |
|   @retval Others               Failed to parse out the boot information.
 | |
| 
 | |
| **/
 | |
| EFI_STATUS
 | |
| HttpBootDhcp6ExtractUriInfo (
 | |
|   IN     HTTP_BOOT_PRIVATE_DATA   *Private
 | |
|   )
 | |
| {
 | |
|   HTTP_BOOT_DHCP6_PACKET_CACHE    *SelectOffer;
 | |
|   HTTP_BOOT_DHCP6_PACKET_CACHE    *HttpOffer;
 | |
|   UINT32                          SelectIndex;
 | |
|   UINT32                          ProxyIndex;
 | |
|   UINT32                          DnsServerIndex;
 | |
|   EFI_DHCP6_PACKET_OPTION         *Option;
 | |
|   EFI_IPv6_ADDRESS                IpAddr;
 | |
|   CHAR8                           *HostName;
 | |
|   UINTN                           HostNameSize;
 | |
|   CHAR16                          *HostNameStr;
 | |
|   EFI_STATUS                      Status;
 | |
| 
 | |
|   ASSERT (Private != NULL);
 | |
|   ASSERT (Private->SelectIndex != 0);
 | |
|   SelectIndex = Private->SelectIndex - 1;
 | |
|   ASSERT (SelectIndex < HTTP_BOOT_OFFER_MAX_NUM);
 | |
| 
 | |
|   DnsServerIndex = 0;
 | |
| 
 | |
|   Status   = EFI_SUCCESS;
 | |
|   HostName = NULL;
 | |
|   //
 | |
|   // SelectOffer contains the IP address configuration and name server configuration.
 | |
|   // HttpOffer contains the boot file URL.
 | |
|   //
 | |
|   SelectOffer = &Private->OfferBuffer[SelectIndex].Dhcp6;
 | |
|   if (Private->FilePathUri == NULL) {
 | |
|     //
 | |
|     // In Corporate environment, we need a HttpOffer.
 | |
|     //
 | |
|     if ((SelectOffer->OfferType == HttpOfferTypeDhcpIpUri) ||
 | |
|         (SelectOffer->OfferType == HttpOfferTypeDhcpIpUriDns) ||
 | |
|         (SelectOffer->OfferType == HttpOfferTypeDhcpNameUriDns)) {
 | |
|       HttpOffer = SelectOffer;
 | |
|     } else {
 | |
|       ASSERT (Private->SelectProxyType != HttpOfferTypeMax);
 | |
|       ProxyIndex = Private->OfferIndex[Private->SelectProxyType][0];
 | |
|       HttpOffer = &Private->OfferBuffer[ProxyIndex].Dhcp6;
 | |
|     }
 | |
|     Private->BootFileUriParser = HttpOffer->UriParser;
 | |
|     Private->BootFileUri = (CHAR8*) HttpOffer->OptList[HTTP_BOOT_DHCP6_IDX_BOOT_FILE_URL]->Data;
 | |
|   } else {
 | |
|     //
 | |
|     // In Home environment the BootFileUri comes from the FilePath.
 | |
|     //
 | |
|     Private->BootFileUriParser = Private->FilePathUriParser;
 | |
|     Private->BootFileUri = Private->FilePathUri;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Check the URI scheme.
 | |
|   //
 | |
|   Status = HttpBootCheckUriScheme (Private->BootFileUri);
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     DEBUG ((EFI_D_ERROR, "HttpBootDhcp6ExtractUriInfo: %r.\n", Status));
 | |
|     if (Status == EFI_INVALID_PARAMETER) {
 | |
|       AsciiPrint ("\n  Error: Invalid URI address.\n");
 | |
|     } else if (Status == EFI_ACCESS_DENIED) {
 | |
|       AsciiPrint ("\n  Error: Access forbidden, only HTTPS connection is allowed.\n");
 | |
|     }
 | |
|     return Status;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   //  Set the Local station address to IP layer.
 | |
|   //
 | |
|   Status = HttpBootSetIp6Address (Private);
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     return Status;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Register the IPv6 gateway address to the network device.
 | |
|   //
 | |
|   Status = HttpBootSetIp6Gateway (Private);
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     return Status;
 | |
|   }
 | |
| 
 | |
|   if ((SelectOffer->OfferType == HttpOfferTypeDhcpNameUriDns) ||
 | |
|       (SelectOffer->OfferType == HttpOfferTypeDhcpDns) ||
 | |
|       (SelectOffer->OfferType == HttpOfferTypeDhcpIpUriDns)) {
 | |
|     Option = SelectOffer->OptList[HTTP_BOOT_DHCP6_IDX_DNS_SERVER];
 | |
|     ASSERT (Option != NULL);
 | |
| 
 | |
|     //
 | |
|     // Record the Dns Server address list.
 | |
|     //
 | |
|     Private->DnsServerCount = HTONS (Option->OpLen) / sizeof (EFI_IPv6_ADDRESS);
 | |
| 
 | |
|     Private->DnsServerIp = AllocateZeroPool (Private->DnsServerCount * sizeof (EFI_IP_ADDRESS));
 | |
|     if (Private->DnsServerIp == NULL) {
 | |
|       return EFI_OUT_OF_RESOURCES;
 | |
|     }
 | |
| 
 | |
|     for (DnsServerIndex = 0; DnsServerIndex < Private->DnsServerCount; DnsServerIndex++) {
 | |
|       CopyMem (&(Private->DnsServerIp[DnsServerIndex].v6), &(((EFI_IPv6_ADDRESS *) Option->Data)[DnsServerIndex]), sizeof (EFI_IPv6_ADDRESS));
 | |
|     }
 | |
| 
 | |
|     //
 | |
|     // Configure the default DNS server if server assigned.
 | |
|     //
 | |
|     Status = HttpBootSetIp6Dns (
 | |
|                Private,
 | |
|                HTONS (Option->OpLen),
 | |
|                Option->Data
 | |
|                );
 | |
|     if (EFI_ERROR (Status)) {
 | |
|       goto Error;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Extract the HTTP server Ip from URL. This is used to Check route table
 | |
|   // whether can send message to HTTP Server Ip through the GateWay.
 | |
|   //
 | |
|   Status = HttpUrlGetIp6 (
 | |
|              Private->BootFileUri,
 | |
|              Private->BootFileUriParser,
 | |
|              &IpAddr
 | |
|              );
 | |
| 
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     //
 | |
|     // The Http server address is expressed by Name Ip, so perform DNS resolution
 | |
|     //
 | |
|     Status = HttpUrlGetHostName (
 | |
|                Private->BootFileUri,
 | |
|                Private->BootFileUriParser,
 | |
|                &HostName
 | |
|                );
 | |
|     if (EFI_ERROR (Status)) {
 | |
|       goto Error;
 | |
|     }
 | |
| 
 | |
|     HostNameSize = AsciiStrSize (HostName);
 | |
|     HostNameStr = AllocateZeroPool (HostNameSize * sizeof (CHAR16));
 | |
|     if (HostNameStr == NULL) {
 | |
|       Status = EFI_OUT_OF_RESOURCES;
 | |
|       goto Error;
 | |
|     }
 | |
| 
 | |
|     AsciiStrToUnicodeStrS (HostName, HostNameStr, HostNameSize);
 | |
| 
 | |
|     if (HostName != NULL) {
 | |
|       FreePool (HostName);
 | |
|     }
 | |
| 
 | |
|     Status = HttpBootDns (Private, HostNameStr, &IpAddr);
 | |
|     FreePool (HostNameStr);
 | |
|     if (EFI_ERROR (Status)) {
 | |
|       AsciiPrint ("\n  Error: Could not retrieve the host address from DNS server.\n");
 | |
|       goto Error;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   CopyMem (&Private->ServerIp.v6, &IpAddr, sizeof (EFI_IPv6_ADDRESS));
 | |
| 
 | |
|   //
 | |
|   // Extract the port from URL, and use default HTTP port 80 if not provided.
 | |
|   //
 | |
|   Status = HttpUrlGetPort (
 | |
|              Private->BootFileUri,
 | |
|              Private->BootFileUriParser,
 | |
|              &Private->Port
 | |
|              );
 | |
|   if (EFI_ERROR (Status) || Private->Port == 0) {
 | |
|     Private->Port = 80;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // All boot informations are valid here.
 | |
|   //
 | |
| 
 | |
|   //
 | |
|   // Update the device path to include the boot resource information.
 | |
|   //
 | |
|   Status = HttpBootUpdateDevicePath (Private);
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     goto Error;
 | |
|   }
 | |
| 
 | |
|   return Status;
 | |
| 
 | |
| Error:
 | |
|   if (Private->DnsServerIp != NULL) {
 | |
|     FreePool (Private->DnsServerIp);
 | |
|     Private->DnsServerIp = NULL;
 | |
|   }
 | |
| 
 | |
|   return Status;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Discover all the boot information for boot file.
 | |
| 
 | |
|   @param[in, out]    Private        The pointer to the driver's private data.
 | |
| 
 | |
|   @retval EFI_SUCCESS          Successfully obtained all the boot information .
 | |
|   @retval Others               Failed to retrieve the boot information.
 | |
| 
 | |
| **/
 | |
| EFI_STATUS
 | |
| HttpBootDiscoverBootInfo (
 | |
|   IN OUT HTTP_BOOT_PRIVATE_DATA   *Private
 | |
|   )
 | |
| {
 | |
|   EFI_STATUS              Status;
 | |
| 
 | |
|   //
 | |
|   // Start D.O.R.A/S.A.R.R exchange to acquire station ip address and
 | |
|   // other Http boot information.
 | |
|   //
 | |
|   Status = HttpBootDhcp (Private);
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     return Status;
 | |
|   }
 | |
| 
 | |
|   if (!Private->UsingIpv6) {
 | |
|     Status = HttpBootDhcp4ExtractUriInfo (Private);
 | |
|   } else {
 | |
|     Status = HttpBootDhcp6ExtractUriInfo (Private);
 | |
|   }
 | |
| 
 | |
|   return Status;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   HttpIo Callback function which will be invoked when specified HTTP_IO_CALLBACK_EVENT happened.
 | |
| 
 | |
|   @param[in]    EventType      Indicate the Event type that occurs in the current callback.
 | |
|   @param[in]    Message        HTTP message which will be send to, or just received from HTTP server.
 | |
|   @param[in]    Context        The Callback Context pointer.
 | |
| 
 | |
|   @retval EFI_SUCCESS          Tells the HttpIo to continue the HTTP process.
 | |
|   @retval Others               Tells the HttpIo to abort the current HTTP process.
 | |
| **/
 | |
| EFI_STATUS
 | |
| EFIAPI
 | |
| HttpBootHttpIoCallback (
 | |
|   IN  HTTP_IO_CALLBACK_EVENT    EventType,
 | |
|   IN  EFI_HTTP_MESSAGE          *Message,
 | |
|   IN  VOID                      *Context
 | |
|   )
 | |
| {
 | |
|   HTTP_BOOT_PRIVATE_DATA       *Private;
 | |
|   EFI_STATUS                   Status;
 | |
|   Private = (HTTP_BOOT_PRIVATE_DATA *) Context;
 | |
|   if (Private->HttpBootCallback != NULL) {
 | |
|     Status = Private->HttpBootCallback->Callback (
 | |
|                Private->HttpBootCallback,
 | |
|                EventType == HttpIoRequest ? HttpBootHttpRequest : HttpBootHttpResponse,
 | |
|                EventType == HttpIoRequest ? FALSE : TRUE,
 | |
|                sizeof (EFI_HTTP_MESSAGE),
 | |
|                (VOID *) Message
 | |
|                );
 | |
|     return Status;
 | |
|   }
 | |
|   return EFI_SUCCESS;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Create a HttpIo instance for the file download.
 | |
| 
 | |
|   @param[in]    Private        The pointer to the driver's private data.
 | |
| 
 | |
|   @retval EFI_SUCCESS          Successfully created.
 | |
|   @retval Others               Failed to create HttpIo.
 | |
| 
 | |
| **/
 | |
| EFI_STATUS
 | |
| HttpBootCreateHttpIo (
 | |
|   IN     HTTP_BOOT_PRIVATE_DATA       *Private
 | |
|   )
 | |
| {
 | |
|   HTTP_IO_CONFIG_DATA          ConfigData;
 | |
|   EFI_STATUS                   Status;
 | |
|   EFI_HANDLE                   ImageHandle;
 | |
|   UINT32                       TimeoutValue;
 | |
| 
 | |
|   ASSERT (Private != NULL);
 | |
| 
 | |
|   //
 | |
|   // Get HTTP timeout value
 | |
|   //
 | |
|   TimeoutValue = PcdGet32 (PcdHttpIoTimeout);
 | |
| 
 | |
|   ZeroMem (&ConfigData, sizeof (HTTP_IO_CONFIG_DATA));
 | |
|   if (!Private->UsingIpv6) {
 | |
|     ConfigData.Config4.HttpVersion    = HttpVersion11;
 | |
|     ConfigData.Config4.RequestTimeOut = TimeoutValue;
 | |
|     IP4_COPY_ADDRESS (&ConfigData.Config4.LocalIp, &Private->StationIp.v4);
 | |
|     IP4_COPY_ADDRESS (&ConfigData.Config4.SubnetMask, &Private->SubnetMask.v4);
 | |
|     ImageHandle = Private->Ip4Nic->ImageHandle;
 | |
|   } else {
 | |
|     ConfigData.Config6.HttpVersion    = HttpVersion11;
 | |
|     ConfigData.Config6.RequestTimeOut = TimeoutValue;
 | |
|     IP6_COPY_ADDRESS (&ConfigData.Config6.LocalIp, &Private->StationIp.v6);
 | |
|     ImageHandle = Private->Ip6Nic->ImageHandle;
 | |
|   }
 | |
| 
 | |
|   Status = HttpIoCreateIo (
 | |
|              ImageHandle,
 | |
|              Private->Controller,
 | |
|              Private->UsingIpv6 ? IP_VERSION_6 : IP_VERSION_4,
 | |
|              &ConfigData,
 | |
|              HttpBootHttpIoCallback,
 | |
|              (VOID *) Private,
 | |
|              &Private->HttpIo
 | |
|              );
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     return Status;
 | |
|   }
 | |
| 
 | |
|   Private->HttpCreated = TRUE;
 | |
|   return EFI_SUCCESS;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Release all the resource of a cache item.
 | |
| 
 | |
|   @param[in]          Cache         The pointer to the cache item.
 | |
| 
 | |
| **/
 | |
| VOID
 | |
| HttpBootFreeCache (
 | |
|   IN  HTTP_BOOT_CACHE_CONTENT    *Cache
 | |
|   )
 | |
| {
 | |
|   UINTN                       Index;
 | |
|   LIST_ENTRY                  *Entry;
 | |
|   LIST_ENTRY                  *NextEntry;
 | |
|   HTTP_BOOT_ENTITY_DATA       *EntityData;
 | |
| 
 | |
|   if (Cache != NULL) {
 | |
|     //
 | |
|     // Free the request data
 | |
|     //
 | |
|     if (Cache->RequestData != NULL) {
 | |
|       if (Cache->RequestData->Url != NULL) {
 | |
|         FreePool (Cache->RequestData->Url);
 | |
|       }
 | |
|       FreePool (Cache->RequestData);
 | |
|     }
 | |
| 
 | |
|     //
 | |
|     // Free the response header
 | |
|     //
 | |
|     if (Cache->ResponseData != NULL) {
 | |
|       if (Cache->ResponseData->Headers != NULL) {
 | |
|         for (Index = 0; Index < Cache->ResponseData->HeaderCount; Index++) {
 | |
|           FreePool (Cache->ResponseData->Headers[Index].FieldName);
 | |
|           FreePool (Cache->ResponseData->Headers[Index].FieldValue);
 | |
|         }
 | |
|         FreePool (Cache->ResponseData->Headers);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     //
 | |
|     // Free the response body
 | |
|     //
 | |
|     NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Cache->EntityDataList) {
 | |
|       EntityData = NET_LIST_USER_STRUCT (Entry, HTTP_BOOT_ENTITY_DATA, Link);
 | |
|       if (EntityData->Block != NULL) {
 | |
|         FreePool (EntityData->Block);
 | |
|       }
 | |
|       RemoveEntryList (&EntityData->Link);
 | |
|       FreePool (EntityData);
 | |
|     }
 | |
| 
 | |
|     FreePool (Cache);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Clean up all cached data.
 | |
| 
 | |
|   @param[in]          Private         The pointer to the driver's private data.
 | |
| 
 | |
| **/
 | |
| VOID
 | |
| HttpBootFreeCacheList (
 | |
|   IN     HTTP_BOOT_PRIVATE_DATA   *Private
 | |
|   )
 | |
| {
 | |
|   LIST_ENTRY                  *Entry;
 | |
|   LIST_ENTRY                  *NextEntry;
 | |
|   HTTP_BOOT_CACHE_CONTENT     *Cache;
 | |
| 
 | |
|   NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Private->CacheList) {
 | |
|     Cache = NET_LIST_USER_STRUCT (Entry, HTTP_BOOT_CACHE_CONTENT, Link);
 | |
|     RemoveEntryList (&Cache->Link);
 | |
|     HttpBootFreeCache (Cache);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Get the file content from cached data.
 | |
| 
 | |
|   @param[in]          Private         The pointer to the driver's private data.
 | |
|   @param[in]          Uri             Uri of the file to be retrieved from cache.
 | |
|   @param[in, out]     BufferSize      On input the size of Buffer in bytes. On output with a return
 | |
|                                       code of EFI_SUCCESS, the amount of data transferred to
 | |
|                                       Buffer. On output with a return code of EFI_BUFFER_TOO_SMALL,
 | |
|                                       the size of Buffer required to retrieve the requested file.
 | |
|   @param[out]         Buffer          The memory buffer to transfer the file to. IF Buffer is NULL,
 | |
|                                       then the size of the requested file is returned in
 | |
|                                       BufferSize.
 | |
|   @param[out]         ImageType       The image type of the downloaded file.
 | |
| 
 | |
|   @retval EFI_SUCCESS          Successfully created.
 | |
|   @retval Others               Failed to create HttpIo.
 | |
| 
 | |
| **/
 | |
| EFI_STATUS
 | |
| HttpBootGetFileFromCache (
 | |
|   IN     HTTP_BOOT_PRIVATE_DATA   *Private,
 | |
|   IN     CHAR16                   *Uri,
 | |
|   IN OUT UINTN                    *BufferSize,
 | |
|      OUT UINT8                    *Buffer,
 | |
|      OUT HTTP_BOOT_IMAGE_TYPE     *ImageType
 | |
|   )
 | |
| {
 | |
|   LIST_ENTRY                  *Entry;
 | |
|   LIST_ENTRY                  *Entry2;
 | |
|   HTTP_BOOT_CACHE_CONTENT     *Cache;
 | |
|   HTTP_BOOT_ENTITY_DATA       *EntityData;
 | |
|   UINTN                       CopyedSize;
 | |
| 
 | |
|   if (Uri == NULL || BufferSize == NULL || Buffer == NULL || ImageType == NULL) {
 | |
|     return EFI_INVALID_PARAMETER;
 | |
|   }
 | |
| 
 | |
|   NET_LIST_FOR_EACH (Entry, &Private->CacheList) {
 | |
|     Cache = NET_LIST_USER_STRUCT (Entry, HTTP_BOOT_CACHE_CONTENT, Link);
 | |
|     //
 | |
|     // Compare the URI to see whether we already have a cache for this file.
 | |
|     //
 | |
|     if ((Cache->RequestData != NULL) &&
 | |
|         (Cache->RequestData->Url != NULL) &&
 | |
|         (StrCmp (Uri, Cache->RequestData->Url) == 0)) {
 | |
|       //
 | |
|       // Hit in cache, record image type.
 | |
|       //
 | |
|       *ImageType  = Cache->ImageType;
 | |
| 
 | |
|       //
 | |
|       // Check buffer size.
 | |
|       //
 | |
|       if (*BufferSize < Cache->EntityLength) {
 | |
|         *BufferSize = Cache->EntityLength;
 | |
|         return EFI_BUFFER_TOO_SMALL;
 | |
|       }
 | |
| 
 | |
|       //
 | |
|       // Fill data to buffer.
 | |
|       //
 | |
|       CopyedSize = 0;
 | |
|       NET_LIST_FOR_EACH (Entry2, &Cache->EntityDataList) {
 | |
|         EntityData = NET_LIST_USER_STRUCT (Entry2, HTTP_BOOT_ENTITY_DATA, Link);
 | |
|         if (*BufferSize > CopyedSize) {
 | |
|           CopyMem (
 | |
|             Buffer + CopyedSize,
 | |
|             EntityData->DataStart,
 | |
|             MIN (EntityData->DataLength, *BufferSize - CopyedSize)
 | |
|             );
 | |
|           CopyedSize += MIN (EntityData->DataLength, *BufferSize - CopyedSize);
 | |
|         }
 | |
|       }
 | |
|       *BufferSize = CopyedSize;
 | |
|       return EFI_SUCCESS;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return EFI_NOT_FOUND;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   A callback function to intercept events during message parser.
 | |
| 
 | |
|   This function will be invoked during HttpParseMessageBody() with various events type. An error
 | |
|   return status of the callback function will cause the HttpParseMessageBody() aborted.
 | |
| 
 | |
|   @param[in]    EventType          Event type of this callback call.
 | |
|   @param[in]    Data               A pointer to data buffer.
 | |
|   @param[in]    Length             Length in bytes of the Data.
 | |
|   @param[in]    Context            Callback context set by HttpInitMsgParser().
 | |
| 
 | |
|   @retval EFI_SUCCESS              Continue to parser the message body.
 | |
|   @retval Others                   Abort the parse.
 | |
| 
 | |
| **/
 | |
| EFI_STATUS
 | |
| EFIAPI
 | |
| HttpBootGetBootFileCallback (
 | |
|   IN HTTP_BODY_PARSE_EVENT      EventType,
 | |
|   IN CHAR8                      *Data,
 | |
|   IN UINTN                      Length,
 | |
|   IN VOID                       *Context
 | |
|   )
 | |
| {
 | |
|   HTTP_BOOT_CALLBACK_DATA      *CallbackData;
 | |
|   HTTP_BOOT_ENTITY_DATA        *NewEntityData;
 | |
|   EFI_STATUS                   Status;
 | |
|   EFI_HTTP_BOOT_CALLBACK_PROTOCOL   *HttpBootCallback;
 | |
| 
 | |
|   //
 | |
|   // We only care about the entity data.
 | |
|   //
 | |
|   if (EventType != BodyParseEventOnData) {
 | |
|     return EFI_SUCCESS;
 | |
|   }
 | |
| 
 | |
|   CallbackData = (HTTP_BOOT_CALLBACK_DATA *) Context;
 | |
|   HttpBootCallback = CallbackData->Private->HttpBootCallback;
 | |
|   if (HttpBootCallback != NULL) {
 | |
|     Status = HttpBootCallback->Callback (
 | |
|                HttpBootCallback,
 | |
|                HttpBootHttpEntityBody,
 | |
|                TRUE,
 | |
|                (UINT32)Length,
 | |
|                Data
 | |
|                );
 | |
|     if (EFI_ERROR (Status)) {
 | |
|       return Status;
 | |
|     }
 | |
|   }
 | |
|   //
 | |
|   // Copy data if caller has provided a buffer.
 | |
|   //
 | |
|   if (CallbackData->BufferSize > CallbackData->CopyedSize) {
 | |
|     CopyMem (
 | |
|       CallbackData->Buffer + CallbackData->CopyedSize,
 | |
|       Data,
 | |
|       MIN (Length, CallbackData->BufferSize - CallbackData->CopyedSize)
 | |
|       );
 | |
|     CallbackData->CopyedSize += MIN (Length, CallbackData->BufferSize - CallbackData->CopyedSize);
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // The caller doesn't provide a buffer, save the block into cache list.
 | |
|   //
 | |
|   if (CallbackData->Cache != NULL) {
 | |
|     NewEntityData = AllocatePool (sizeof (HTTP_BOOT_ENTITY_DATA));
 | |
|     if (NewEntityData == NULL) {
 | |
|       return EFI_OUT_OF_RESOURCES;
 | |
|     }
 | |
|     if (CallbackData->NewBlock) {
 | |
|       NewEntityData->Block = CallbackData->Block;
 | |
|       CallbackData->Block = NULL;
 | |
|     }
 | |
|     NewEntityData->DataLength = Length;
 | |
|     NewEntityData->DataStart  = (UINT8*) Data;
 | |
|     InsertTailList (&CallbackData->Cache->EntityDataList, &NewEntityData->Link);
 | |
|   }
 | |
|   return EFI_SUCCESS;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   This function download the boot file by using UEFI HTTP protocol.
 | |
| 
 | |
|   @param[in]       Private         The pointer to the driver's private data.
 | |
|   @param[in]       HeaderOnly      Only request the response header, it could save a lot of time if
 | |
|                                    the caller only want to know the size of the requested file.
 | |
|   @param[in, out]  BufferSize      On input the size of Buffer in bytes. On output with a return
 | |
|                                    code of EFI_SUCCESS, the amount of data transferred to
 | |
|                                    Buffer. On output with a return code of EFI_BUFFER_TOO_SMALL,
 | |
|                                    the size of Buffer required to retrieve the requested file.
 | |
|   @param[out]      Buffer          The memory buffer to transfer the file to. IF Buffer is NULL,
 | |
|                                    then the size of the requested file is returned in
 | |
|                                    BufferSize.
 | |
|   @param[out]      ImageType       The image type of the downloaded file.
 | |
| 
 | |
|   @retval EFI_SUCCESS              The file was loaded.
 | |
|   @retval EFI_INVALID_PARAMETER    BufferSize is NULL or Buffer Size is not NULL but Buffer is NULL.
 | |
|   @retval EFI_OUT_OF_RESOURCES     Could not allocate needed resources
 | |
|   @retval EFI_BUFFER_TOO_SMALL     The BufferSize is too small to read the current directory entry.
 | |
|                                    BufferSize has been updated with the size needed to complete
 | |
|                                    the request.
 | |
|   @retval Others                   Unexpected error happened.
 | |
| 
 | |
| **/
 | |
| EFI_STATUS
 | |
| HttpBootGetBootFile (
 | |
|   IN     HTTP_BOOT_PRIVATE_DATA   *Private,
 | |
|   IN     BOOLEAN                  HeaderOnly,
 | |
|   IN OUT UINTN                    *BufferSize,
 | |
|      OUT UINT8                    *Buffer,
 | |
|      OUT HTTP_BOOT_IMAGE_TYPE     *ImageType
 | |
|   )
 | |
| {
 | |
|   EFI_STATUS                 Status;
 | |
|   EFI_HTTP_STATUS_CODE       StatusCode;
 | |
|   CHAR8                      *HostName;
 | |
|   EFI_HTTP_REQUEST_DATA      *RequestData;
 | |
|   HTTP_IO_RESPONSE_DATA      *ResponseData;
 | |
|   HTTP_IO_RESPONSE_DATA      ResponseBody;
 | |
|   HTTP_IO                    *HttpIo;
 | |
|   HTTP_IO_HEADER             *HttpIoHeader;
 | |
|   VOID                       *Parser;
 | |
|   HTTP_BOOT_CALLBACK_DATA    Context;
 | |
|   UINTN                      ContentLength;
 | |
|   HTTP_BOOT_CACHE_CONTENT    *Cache;
 | |
|   UINT8                      *Block;
 | |
|   UINTN                      UrlSize;
 | |
|   CHAR16                     *Url;
 | |
|   BOOLEAN                    IdentityMode;
 | |
|   UINTN                      ReceivedSize;
 | |
| 
 | |
|   ASSERT (Private != NULL);
 | |
|   ASSERT (Private->HttpCreated);
 | |
| 
 | |
|   if (BufferSize == NULL || ImageType == NULL) {
 | |
|     return EFI_INVALID_PARAMETER;
 | |
|   }
 | |
| 
 | |
|   if (*BufferSize != 0 && Buffer == NULL) {
 | |
|     return EFI_INVALID_PARAMETER;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // First, check whether we already cached the requested Uri.
 | |
|   //
 | |
|   UrlSize = AsciiStrSize (Private->BootFileUri);
 | |
|   Url = AllocatePool (UrlSize * sizeof (CHAR16));
 | |
|   if (Url == NULL) {
 | |
|     return EFI_OUT_OF_RESOURCES;
 | |
|   }
 | |
|   AsciiStrToUnicodeStrS (Private->BootFileUri, Url, UrlSize);
 | |
|   if (!HeaderOnly && Buffer != NULL) {
 | |
|     Status = HttpBootGetFileFromCache (Private, Url, BufferSize, Buffer, ImageType);
 | |
|     if (Status != EFI_NOT_FOUND) {
 | |
|       FreePool (Url);
 | |
|       return Status;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Not found in cache, try to download it through HTTP.
 | |
|   //
 | |
| 
 | |
|   //
 | |
|   // 1. Create a temp cache item for the requested URI if caller doesn't provide buffer.
 | |
|   //
 | |
|   Cache = NULL;
 | |
|   if ((!HeaderOnly) && (*BufferSize == 0)) {
 | |
|     Cache = AllocateZeroPool (sizeof (HTTP_BOOT_CACHE_CONTENT));
 | |
|     if (Cache == NULL) {
 | |
|       Status = EFI_OUT_OF_RESOURCES;
 | |
|       goto ERROR_1;
 | |
|     }
 | |
|     Cache->ImageType = ImageTypeMax;
 | |
|     InitializeListHead (&Cache->EntityDataList);
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // 2. Send HTTP request message.
 | |
|   //
 | |
| 
 | |
|   //
 | |
|   // 2.1 Build HTTP header for the request, 3 header is needed to download a boot file:
 | |
|   //       Host
 | |
|   //       Accept
 | |
|   //       User-Agent
 | |
|   //
 | |
|   HttpIoHeader = HttpIoCreateHeader (3);
 | |
|   if (HttpIoHeader == NULL) {
 | |
|     Status = EFI_OUT_OF_RESOURCES;
 | |
|     goto ERROR_2;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Add HTTP header field 1: Host
 | |
|   //
 | |
|   HostName = NULL;
 | |
|   Status = HttpUrlGetHostName (
 | |
|              Private->BootFileUri,
 | |
|              Private->BootFileUriParser,
 | |
|              &HostName
 | |
|              );
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     goto ERROR_3;
 | |
|   }
 | |
|   Status = HttpIoSetHeader (
 | |
|              HttpIoHeader,
 | |
|              HTTP_HEADER_HOST,
 | |
|              HostName
 | |
|              );
 | |
|   FreePool (HostName);
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     goto ERROR_3;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Add HTTP header field 2: Accept
 | |
|   //
 | |
|   Status = HttpIoSetHeader (
 | |
|              HttpIoHeader,
 | |
|              HTTP_HEADER_ACCEPT,
 | |
|              "*/*"
 | |
|              );
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     goto ERROR_3;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Add HTTP header field 3: User-Agent
 | |
|   //
 | |
|   Status = HttpIoSetHeader (
 | |
|              HttpIoHeader,
 | |
|              HTTP_HEADER_USER_AGENT,
 | |
|              HTTP_USER_AGENT_EFI_HTTP_BOOT
 | |
|              );
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     goto ERROR_3;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // 2.2 Build the rest of HTTP request info.
 | |
|   //
 | |
|   RequestData = AllocatePool (sizeof (EFI_HTTP_REQUEST_DATA));
 | |
|   if (RequestData == NULL) {
 | |
|     Status = EFI_OUT_OF_RESOURCES;
 | |
|     goto ERROR_3;
 | |
|   }
 | |
|   RequestData->Method = HeaderOnly ? HttpMethodHead : HttpMethodGet;
 | |
|   RequestData->Url = Url;
 | |
| 
 | |
|   //
 | |
|   // 2.3 Record the request info in a temp cache item.
 | |
|   //
 | |
|   if (Cache != NULL) {
 | |
|     Cache->RequestData = RequestData;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // 2.4 Send out the request to HTTP server.
 | |
|   //
 | |
|   HttpIo = &Private->HttpIo;
 | |
|   Status = HttpIoSendRequest (
 | |
|              HttpIo,
 | |
|              RequestData,
 | |
|              HttpIoHeader->HeaderCount,
 | |
|              HttpIoHeader->Headers,
 | |
|              0,
 | |
|              NULL
 | |
|             );
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     goto ERROR_4;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // 3. Receive HTTP response message.
 | |
|   //
 | |
| 
 | |
|   //
 | |
|   // 3.1 First step, use zero BodyLength to only receive the response headers.
 | |
|   //
 | |
|   ResponseData = AllocateZeroPool (sizeof(HTTP_IO_RESPONSE_DATA));
 | |
|   if (ResponseData == NULL) {
 | |
|     Status = EFI_OUT_OF_RESOURCES;
 | |
|     goto ERROR_4;
 | |
|   }
 | |
|   Status = HttpIoRecvResponse (
 | |
|              &Private->HttpIo,
 | |
|              TRUE,
 | |
|              ResponseData
 | |
|              );
 | |
|   if (EFI_ERROR (Status) || EFI_ERROR (ResponseData->Status)) {
 | |
|     if (EFI_ERROR (ResponseData->Status)) {
 | |
|       StatusCode = HttpIo->RspToken.Message->Data.Response->StatusCode;
 | |
|       HttpBootPrintErrorMessage (StatusCode);
 | |
|       Status = ResponseData->Status;
 | |
|     }
 | |
|     goto ERROR_5;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Check the image type according to server's response.
 | |
|   //
 | |
|   Status = HttpBootCheckImageType (
 | |
|              Private->BootFileUri,
 | |
|              Private->BootFileUriParser,
 | |
|              ResponseData->HeaderCount,
 | |
|              ResponseData->Headers,
 | |
|              ImageType
 | |
|              );
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     goto ERROR_5;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // 3.2 Cache the response header.
 | |
|   //
 | |
|   if (Cache != NULL) {
 | |
|     Cache->ResponseData = ResponseData;
 | |
|     Cache->ImageType = *ImageType;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // 3.3 Init a message-body parser from the header information.
 | |
|   //
 | |
|   Parser = NULL;
 | |
|   Context.NewBlock   = FALSE;
 | |
|   Context.Block      = NULL;
 | |
|   Context.CopyedSize = 0;
 | |
|   Context.Buffer     = Buffer;
 | |
|   Context.BufferSize = *BufferSize;
 | |
|   Context.Cache      = Cache;
 | |
|   Context.Private    = Private;
 | |
|   Status = HttpInitMsgParser (
 | |
|              HeaderOnly ? HttpMethodHead : HttpMethodGet,
 | |
|              ResponseData->Response.StatusCode,
 | |
|              ResponseData->HeaderCount,
 | |
|              ResponseData->Headers,
 | |
|              HttpBootGetBootFileCallback,
 | |
|              (VOID*) &Context,
 | |
|              &Parser
 | |
|              );
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     goto ERROR_6;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // 3.4 Continue to receive and parse message-body if needed.
 | |
|   //
 | |
|   Block = NULL;
 | |
|   if (!HeaderOnly) {
 | |
|     //
 | |
|     // 3.4.1, check whether we are in identity transfer-coding.
 | |
|     //
 | |
|     ContentLength = 0;
 | |
|     Status = HttpGetEntityLength (Parser, &ContentLength);
 | |
|     if (!EFI_ERROR (Status)) {
 | |
|       IdentityMode = TRUE;
 | |
|     } else {
 | |
|       IdentityMode = FALSE;
 | |
|     }
 | |
| 
 | |
|     //
 | |
|     // 3.4.2, start the message-body download, the identity and chunked transfer-coding
 | |
|     // is handled in different path here.
 | |
|     //
 | |
|     ZeroMem (&ResponseBody, sizeof (HTTP_IO_RESPONSE_DATA));
 | |
|     if (IdentityMode) {
 | |
|       //
 | |
|       // In identity transfer-coding there is no need to parse the message body,
 | |
|       // just download the message body to the user provided buffer directly.
 | |
|       //
 | |
|       ReceivedSize = 0;
 | |
|       while (ReceivedSize < ContentLength) {
 | |
|         ResponseBody.Body       = (CHAR8*) Buffer + ReceivedSize;
 | |
|         ResponseBody.BodyLength = *BufferSize - ReceivedSize;
 | |
|         Status = HttpIoRecvResponse (
 | |
|                    &Private->HttpIo,
 | |
|                    FALSE,
 | |
|                    &ResponseBody
 | |
|                    );
 | |
|         if (EFI_ERROR (Status) || EFI_ERROR (ResponseBody.Status)) {
 | |
|           if (EFI_ERROR (ResponseBody.Status)) {
 | |
|             Status = ResponseBody.Status;
 | |
|           }
 | |
|           goto ERROR_6;
 | |
|         }
 | |
|         ReceivedSize += ResponseBody.BodyLength;
 | |
|         if (Private->HttpBootCallback != NULL) {
 | |
|           Status = Private->HttpBootCallback->Callback (
 | |
|                      Private->HttpBootCallback,
 | |
|                      HttpBootHttpEntityBody,
 | |
|                      TRUE,
 | |
|                      (UINT32)ResponseBody.BodyLength,
 | |
|                      ResponseBody.Body
 | |
|                      );
 | |
|           if (EFI_ERROR (Status)) {
 | |
|             goto ERROR_6;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     } else {
 | |
|       //
 | |
|       // In "chunked" transfer-coding mode, so we need to parse the received
 | |
|       // data to get the real entity content.
 | |
|       //
 | |
|       Block = NULL;
 | |
|       while (!HttpIsMessageComplete (Parser)) {
 | |
|         //
 | |
|         // Allocate a buffer in Block to hold the message-body.
 | |
|         // If caller provides a buffer, this Block will be reused in every HttpIoRecvResponse().
 | |
|         // Otherwise a buffer, the buffer in Block will be cached and we should allocate a new before
 | |
|         // every HttpIoRecvResponse().
 | |
|         //
 | |
|         if (Block == NULL || Context.BufferSize == 0) {
 | |
|           Block = AllocatePool (HTTP_BOOT_BLOCK_SIZE);
 | |
|           if (Block == NULL) {
 | |
|             Status = EFI_OUT_OF_RESOURCES;
 | |
|             goto ERROR_6;
 | |
|           }
 | |
|           Context.NewBlock = TRUE;
 | |
|           Context.Block = Block;
 | |
|         } else {
 | |
|           Context.NewBlock = FALSE;
 | |
|         }
 | |
| 
 | |
|         ResponseBody.Body       = (CHAR8*) Block;
 | |
|         ResponseBody.BodyLength = HTTP_BOOT_BLOCK_SIZE;
 | |
|         Status = HttpIoRecvResponse (
 | |
|                    &Private->HttpIo,
 | |
|                    FALSE,
 | |
|                    &ResponseBody
 | |
|                    );
 | |
|         if (EFI_ERROR (Status) || EFI_ERROR (ResponseBody.Status)) {
 | |
|           if (EFI_ERROR (ResponseBody.Status)) {
 | |
|             Status = ResponseBody.Status;
 | |
|           }
 | |
|           goto ERROR_6;
 | |
|         }
 | |
| 
 | |
|         //
 | |
|         // Parse the new received block of the message-body, the block will be saved in cache.
 | |
|         //
 | |
|         Status = HttpParseMessageBody (
 | |
|                    Parser,
 | |
|                    ResponseBody.BodyLength,
 | |
|                    ResponseBody.Body
 | |
|                    );
 | |
|         if (EFI_ERROR (Status)) {
 | |
|           goto ERROR_6;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // 3.5 Message-body receive & parse is completed, we should be able to get the file size now.
 | |
|   //
 | |
|   Status = HttpGetEntityLength (Parser, &ContentLength);
 | |
|   if (EFI_ERROR (Status)) {
 | |
|     goto ERROR_6;
 | |
|   }
 | |
| 
 | |
|   if (*BufferSize < ContentLength) {
 | |
|     Status = EFI_BUFFER_TOO_SMALL;
 | |
|   } else {
 | |
|     Status = EFI_SUCCESS;
 | |
|   }
 | |
|   *BufferSize = ContentLength;
 | |
| 
 | |
|   //
 | |
|   // 4. Save the cache item to driver's cache list and return.
 | |
|   //
 | |
|   if (Cache != NULL) {
 | |
|     Cache->EntityLength = ContentLength;
 | |
|     InsertTailList (&Private->CacheList, &Cache->Link);
 | |
|   }
 | |
| 
 | |
|   if (Parser != NULL) {
 | |
|     HttpFreeMsgParser (Parser);
 | |
|   }
 | |
| 
 | |
|   return Status;
 | |
| 
 | |
| ERROR_6:
 | |
|   if (Parser != NULL) {
 | |
|     HttpFreeMsgParser (Parser);
 | |
|   }
 | |
|   if (Context.Block != NULL) {
 | |
|     FreePool (Context.Block);
 | |
|   }
 | |
|   HttpBootFreeCache (Cache);
 | |
| 
 | |
| ERROR_5:
 | |
|   if (ResponseData != NULL) {
 | |
|     FreePool (ResponseData);
 | |
|   }
 | |
| ERROR_4:
 | |
|   if (RequestData != NULL) {
 | |
|     FreePool (RequestData);
 | |
|   }
 | |
| ERROR_3:
 | |
|   HttpIoFreeHeader (HttpIoHeader);
 | |
| ERROR_2:
 | |
|   if (Cache != NULL) {
 | |
|     FreePool (Cache);
 | |
|   }
 | |
| ERROR_1:
 | |
|   if (Url != NULL) {
 | |
|     FreePool (Url);
 | |
|   }
 | |
| 
 | |
|   return Status;
 | |
| }
 | |
| 
 |