diff options
Diffstat (limited to '')
-rw-r--r-- | src/VBox/Devices/EFI/Firmware/ShellPkg/DynamicCommand/HttpDynamicCommand/Http.c | 1905 |
1 files changed, 1905 insertions, 0 deletions
diff --git a/src/VBox/Devices/EFI/Firmware/ShellPkg/DynamicCommand/HttpDynamicCommand/Http.c b/src/VBox/Devices/EFI/Firmware/ShellPkg/DynamicCommand/HttpDynamicCommand/Http.c new file mode 100644 index 00000000..5173ac1e --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/ShellPkg/DynamicCommand/HttpDynamicCommand/Http.c @@ -0,0 +1,1905 @@ +/** @file + The implementation for the 'http' Shell command. + + Copyright (c) 2015, ARM Ltd. All rights reserved.<BR> + Copyright (c) 2015 - 2018, Intel Corporation. All rights reserved. <BR> + (C) Copyright 2015 Hewlett Packard Enterprise Development LP<BR> + Copyright (c) 2020, Broadcom. All rights reserved. <BR> + + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include "Http.h" + +#define IP4_CONFIG2_INTERFACE_INFO_NAME_LENGTH 32 + +// +// Constant strings and definitions related to the message +// indicating the amount of progress in the dowloading of a HTTP file. +// + +// +// Number of steps in the progression slider. +// +#define HTTP_PROGRESS_SLIDER_STEPS \ + ((sizeof (HTTP_PROGR_FRAME) / sizeof (CHAR16)) - 3) + +// +// Size in number of characters plus one (final zero) of the message to +// indicate the progress of an HTTP download. The format is "[(progress slider: +// 40 characters)] (nb of KBytes downloaded so far: 7 characters) Kb". There +// are thus the number of characters in HTTP_PROGR_FRAME[] plus 11 characters +// (2 // spaces, "Kb" and seven characters for the number of KBytes). +// +#define HTTP_PROGRESS_MESSAGE_SIZE \ + ((sizeof (HTTP_PROGR_FRAME) / sizeof (CHAR16)) + 12) + +// +// Buffer size. Note that larger buffer does not mean better speed. +// +#define DEFAULT_BUF_SIZE SIZE_32KB +#define MAX_BUF_SIZE SIZE_4MB + +#define MIN_PARAM_COUNT 2 +#define MAX_PARAM_COUNT 4 +#define NEED_REDIRECTION(Code) \ + (((Code >= HTTP_STATUS_300_MULTIPLE_CHOICES) \ + && (Code <= HTTP_STATUS_307_TEMPORARY_REDIRECT)) \ + || (Code == HTTP_STATUS_308_PERMANENT_REDIRECT)) + +#define CLOSE_HTTP_HANDLE(ControllerHandle,HttpChildHandle) \ + do { \ + if (HttpChildHandle) { \ + CloseProtocolAndDestroyServiceChild ( \ + ControllerHandle, \ + &gEfiHttpServiceBindingProtocolGuid, \ + &gEfiHttpProtocolGuid, \ + HttpChildHandle \ + ); \ + HttpChildHandle = NULL; \ + } \ + } while (0) + +typedef enum { + HdrHost, + HdrConn, + HdrAgent, + HdrMax +} HDR_TYPE; + +#define USER_AGENT_HDR "Mozilla/5.0 (EDK2; Linux) Gecko/20100101 Firefox/79.0" + +#define TIMER_MAX_TIMEOUT_S 10 + +// +// File name to use when Uri ends with "/". +// +#define DEFAULT_HTML_FILE L"index.html" +#define DEFAULT_HTTP_PROTO L"http" + +// +// String to delete the HTTP progress message to be able to update it : +// (HTTP_PROGRESS_MESSAGE_SIZE-1) '\b'. +// +#define HTTP_PROGRESS_DEL \ + L"\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\ +\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" + +#define HTTP_KB L"\b\b\b\b\b\b\b\b\b\b" +// +// Frame for the progression slider. +// +#define HTTP_PROGR_FRAME L"[ ]" + +// +// Improve readability by using these macros. +// +#define PRINT_HII(token,...) \ + ShellPrintHiiEx (\ + -1, -1, NULL, token, mHttpHiiHandle, __VA_ARGS__) + +#define PRINT_HII_APP(token,value) \ + PRINT_HII (token, HTTP_APP_NAME, value) + +// +// TimeBaseLib.h constants. +// These will be removed once the library gets fixed. +// + +// +// Define EPOCH (1970-JANUARY-01) in the Julian Date representation. +// +#define EPOCH_JULIAN_DATE 2440588 + +// +// Seconds per unit. +// +#define SEC_PER_MIN ((UINTN) 60) +#define SEC_PER_HOUR ((UINTN) 3600) +#define SEC_PER_DAY ((UINTN) 86400) + +// +// String descriptions for server errors. +// +STATIC CONST CHAR16 *ErrStatusDesc[] = +{ + L"400 Bad Request", + L"401 Unauthorized", + L"402 Payment required", + L"403 Forbidden", + L"404 Not Found", + L"405 Method not allowed", + L"406 Not acceptable", + L"407 Proxy authentication required", + L"408 Request time out", + L"409 Conflict", + L"410 Gone", + L"411 Length required", + L"412 Precondition failed", + L"413 Request entity too large", + L"414 Request URI to large", + L"415 Unsupported media type", + L"416 Requested range not satisfied", + L"417 Expectation failed", + L"500 Internal server error", + L"501 Not implemented", + L"502 Bad gateway", + L"503 Service unavailable", + L"504 Gateway timeout", + L"505 HTTP version not supported" +}; + +STATIC CONST SHELL_PARAM_ITEM ParamList[] = { + {L"-i", TypeValue}, + {L"-k", TypeFlag}, + {L"-l", TypeValue}, + {L"-m", TypeFlag}, + {L"-s", TypeValue}, + {L"-t", TypeValue}, + {NULL , TypeMax} +}; + +// +// Local File Handle. +// +STATIC SHELL_FILE_HANDLE mFileHandle = NULL; + +// +// Path of the local file, Unicode encoded. +// +STATIC CONST CHAR16 *mLocalFilePath; + +STATIC BOOLEAN gRequestCallbackComplete = FALSE; +STATIC BOOLEAN gResponseCallbackComplete = FALSE; + +STATIC BOOLEAN gHttpError; + +EFI_HII_HANDLE mHttpHiiHandle; + +// +// Functions declarations. +// + +/** + Check and convert the UINT16 option values of the 'http' command. + + @param[in] ValueStr Value as an Unicode encoded string. + @param[out] Value UINT16 value. + + @retval TRUE The value was returned. + @retval FALSE A parsing error occured. +**/ +STATIC +BOOLEAN +StringToUint16 ( + IN CONST CHAR16 *ValueStr, + OUT UINT16 *Value + ); + +/** + Get the name of the NIC. + + @param[in] ControllerHandle The network physical device handle. + @param[in] NicNumber The network physical device number. + @param[out] NicName Address where to store the NIC name. + The memory area has to be at least + IP4_CONFIG2_INTERFACE_INFO_NAME_LENGTH + double byte wide. + + @retval EFI_SUCCESS The name of the NIC was returned. + @retval Others The creation of the child for the Managed + Network Service failed or the opening of + the Managed Network Protocol failed or + the operational parameters for the + Managed Network Protocol could not be + read. +**/ +STATIC +EFI_STATUS +GetNicName ( + IN EFI_HANDLE ControllerHandle, + IN UINTN NicNumber, + OUT CHAR16 *NicName + ); + +/** + Create a child for the service identified by its service binding protocol GUID + and get from the child the interface of the protocol identified by its GUID. + + @param[in] ControllerHandle Controller handle. + @param[in] ServiceBindingProtocolGuid Service binding protocol GUID of the + service to be created. + @param[in] ProtocolGuid GUID of the protocol to be open. + @param[out] ChildHandle Address where the handler of the + created child is returned. NULL is + returned in case of error. + @param[out] Interface Address where a pointer to the + protocol interface is returned in + case of success. + + @retval EFI_SUCCESS The child was created and the protocol opened. + @retval Others Either the creation of the child or the opening + of the protocol failed. +**/ +STATIC +EFI_STATUS +CreateServiceChildAndOpenProtocol ( + IN EFI_HANDLE ControllerHandle, + IN EFI_GUID *ServiceBindingProtocolGuid, + IN EFI_GUID *ProtocolGuid, + OUT EFI_HANDLE *ChildHandle, + OUT VOID **Interface + ); + +/** + Close the protocol identified by its GUID on the child handle of the service + identified by its service binding protocol GUID, then destroy the child + handle. + + @param[in] ControllerHandle Controller handle. + @param[in] ServiceBindingProtocolGuid Service binding protocol GUID of the + service to be destroyed. + @param[in] ProtocolGuid GUID of the protocol to be closed. + @param[in] ChildHandle Handle of the child to be destroyed. + +**/ +STATIC +VOID +CloseProtocolAndDestroyServiceChild ( + IN EFI_HANDLE ControllerHandle, + IN EFI_GUID *ServiceBindingProtocolGuid, + IN EFI_GUID *ProtocolGuid, + IN EFI_HANDLE ChildHandle + ); + +/** + Worker function that download the data of a file from an HTTP server given + the path of the file and its size. + + @param[in] Context A pointer to the download context. + + @retval EFI_SUCCESS The file was downloaded. + @retval EFI_OUT_OF_RESOURCES A memory allocation failed. + @retval Others The downloading of the file + from the server failed. +**/ +STATIC +EFI_STATUS +DownloadFile ( + IN HTTP_DOWNLOAD_CONTEXT *Context, + IN EFI_HANDLE ControllerHandle, + IN CHAR16 *NicName + ); + +/** + Cleans off leading and trailing spaces and tabs. + + @param[in] String pointer to the string to trim them off. + + @retval EFI_SUCCESS No errors. + @retval EFI_INVALID_PARAMETER String pointer is NULL. +**/ +STATIC +EFI_STATUS +TrimSpaces ( + IN CHAR16 *String + ) +{ + CHAR16 *Str; + UINTN Len; + + ASSERT (String != NULL); + + if (String == NULL) { + return EFI_INVALID_PARAMETER; + } + + Str = String; + + // + // Remove any whitespace at the beginning of the Str. + // + while (*Str == L' ' || *Str == L'\t') { + Str++; + } + + // + // Remove any whitespace at the end of the Str. + // + do { + Len = StrLen (Str); + if (!Len || (Str[Len - 1] != L' ' && Str[Len - 1] != '\t')) { + break; + } + + Str[Len - 1] = CHAR_NULL; + } while (Len); + + CopyMem (String, Str, StrSize (Str)); + + return EFI_SUCCESS; +} + +// +// Callbacks for request and response. +// We just acknowledge that operation has completed here. +// + +/** + Callback to set the request completion flag. + + @param[in] Event: The event. + @param[in] Context: pointer to Notification Context. + **/ +STATIC +VOID +EFIAPI +RequestCallback ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + gRequestCallbackComplete = TRUE; +} + +/** + Callback to set the response completion flag. + @param[in] Event: The event. + @param[in] Context: pointer to Notification Context. + **/ +STATIC +VOID +EFIAPI +ResponseCallback ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + gResponseCallbackComplete = TRUE; +} + +// +// Set of functions from TimeBaseLib. +// This will be removed once TimeBaseLib is enabled for ShellPkg. +// + +/** + Calculate Epoch days. + + @param[in] Time - a pointer to the EFI_TIME abstraction. + + @retval Number of days elapsed since EPOCH_JULIAN_DAY. + **/ +STATIC +UINTN +EfiGetEpochDays ( + IN EFI_TIME *Time + ) +{ + UINTN a; + UINTN y; + UINTN m; + // + // Absolute Julian Date representation of the supplied Time. + // + UINTN JulianDate; + // + // Number of days elapsed since EPOCH_JULIAN_DAY. + // + UINTN EpochDays; + + a = (14 - Time->Month) / 12 ; + y = Time->Year + 4800 - a; + m = Time->Month + (12 * a) - 3; + + JulianDate = Time->Day + ((153 * m + 2) / 5) + (365 * y) + (y / 4) - + (y / 100) + (y / 400) - 32045; + + ASSERT (JulianDate >= EPOCH_JULIAN_DATE); + EpochDays = JulianDate - EPOCH_JULIAN_DATE; + + return EpochDays; +} + +/** + Converts EFI_TIME to Epoch seconds + (elapsed since 1970 JANUARY 01, 00:00:00 UTC). + + @param[in] Time: a pointer to EFI_TIME abstraction. + **/ +STATIC +UINTN +EFIAPI +EfiTimeToEpoch ( + IN EFI_TIME *Time + ) +{ + // + // Number of days elapsed since EPOCH_JULIAN_DAY. + // + UINTN EpochDays; + UINTN EpochSeconds; + + EpochDays = EfiGetEpochDays (Time); + + EpochSeconds = (EpochDays * SEC_PER_DAY) + + ((UINTN)Time->Hour * SEC_PER_HOUR) + + (Time->Minute * SEC_PER_MIN) + Time->Second; + + return EpochSeconds; +} + +/** + Function for 'http' command. + + @param[in] ImageHandle Handle to the Image (NULL if Internal). + @param[in] SystemTable Pointer to the System Table (NULL if Internal). + + @retval SHELL_SUCCESS The 'http' command completed successfully. + @retval SHELL_ABORTED The Shell Library initialization failed. + @retval SHELL_INVALID_PARAMETER At least one of the command's arguments is + not valid. + @retval SHELL_OUT_OF_RESOURCES A memory allocation failed. + @retval SHELL_NOT_FOUND Network Interface Card not found. + @retval SHELL_UNSUPPORTED Command was valid, but the server returned + a status code indicating some error. + Examine the file requested for error body. +**/ +SHELL_STATUS +RunHttp ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + EFI_STATUS Status; + LIST_ENTRY *CheckPackage; + UINTN ParamCount; + UINTN HandleCount; + UINTN NicNumber; + UINTN InitialSize; + UINTN ParamOffset; + UINTN StartSize; + CHAR16 *ProblemParam; + CHAR16 NicName[IP4_CONFIG2_INTERFACE_INFO_NAME_LENGTH]; + CHAR16 *Walker1; + CHAR16 *VStr; + CONST CHAR16 *UserNicName; + CONST CHAR16 *ValueStr; + CONST CHAR16 *RemoteFilePath; + CONST CHAR16 *Walker; + EFI_HTTPv4_ACCESS_POINT IPv4Node; + EFI_HANDLE *Handles; + EFI_HANDLE ControllerHandle; + HTTP_DOWNLOAD_CONTEXT Context; + BOOLEAN NicFound; + + ProblemParam = NULL; + RemoteFilePath = NULL; + NicFound = FALSE; + Handles = NULL; + + // + // Initialize the Shell library (we must be in non-auto-init...). + // + ParamOffset = 0; + gHttpError = FALSE; + + Status = ShellInitialize (); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return SHELL_ABORTED; + } + + ZeroMem (&Context, sizeof (Context)); + + // + // Parse the command line. + // + Status = ShellCommandLineParse ( + ParamList, + &CheckPackage, + &ProblemParam, + TRUE + ); + if (EFI_ERROR (Status)) { + if ((Status == EFI_VOLUME_CORRUPTED) + && (ProblemParam != NULL)) + { + PRINT_HII_APP (STRING_TOKEN (STR_GEN_PROBLEM), ProblemParam); + SHELL_FREE_NON_NULL (ProblemParam); + } else { + ASSERT (FALSE); + } + + goto Error; + } + + // + // Check the number of parameters. + // + Status = EFI_INVALID_PARAMETER; + + ParamCount = ShellCommandLineGetCount (CheckPackage); + if (ParamCount > MAX_PARAM_COUNT) { + PRINT_HII_APP (STRING_TOKEN (STR_GEN_TOO_MANY), NULL); + goto Error; + } + + if (ParamCount < MIN_PARAM_COUNT) { + PRINT_HII_APP (STRING_TOKEN (STR_GEN_TOO_FEW), NULL); + goto Error; + } + + ZeroMem (&Context.HttpConfigData, sizeof (Context.HttpConfigData)); + ZeroMem (&IPv4Node, sizeof (IPv4Node)); + IPv4Node.UseDefaultAddress = TRUE; + + Context.HttpConfigData.HttpVersion = HttpVersion11; + Context.HttpConfigData.AccessPoint.IPv4Node = &IPv4Node; + + // + // Get the host address (not necessarily IPv4 format). + // + ValueStr = ShellCommandLineGetRawValue (CheckPackage, 1); + if (!ValueStr) { + PRINT_HII_APP (STRING_TOKEN (STR_GEN_PARAM_INV), ValueStr); + goto Error; + } else { + StartSize = 0; + TrimSpaces ((CHAR16 *)ValueStr); + if (!StrStr (ValueStr, L"://")) { + Context.ServerAddrAndProto = StrnCatGrow ( + &Context.ServerAddrAndProto, + &StartSize, + DEFAULT_HTTP_PROTO, + StrLen (DEFAULT_HTTP_PROTO) + ); + Context.ServerAddrAndProto = StrnCatGrow ( + &Context.ServerAddrAndProto, + &StartSize, + L"://", + StrLen (L"://") + ); + VStr = (CHAR16 *)ValueStr; + } else { + VStr = StrStr (ValueStr, L"://") + StrLen (L"://"); + } + + for (Walker1 = VStr; *Walker1; Walker1++) { + if (*Walker1 == L'/') { + break; + } + } + + if (*Walker1 == L'/') { + ParamOffset = 1; + RemoteFilePath = Walker1; + } + + Context.ServerAddrAndProto = StrnCatGrow ( + &Context.ServerAddrAndProto, + &StartSize, + ValueStr, + StrLen (ValueStr) - StrLen (Walker1) + ); + if (!Context.ServerAddrAndProto) { + Status = EFI_OUT_OF_RESOURCES; + goto Error; + } + } + + if (!RemoteFilePath) { + RemoteFilePath = ShellCommandLineGetRawValue (CheckPackage, 2); + if (!RemoteFilePath) { + // + // If no path given, assume just "/". + // + RemoteFilePath = L"/"; + } + } + + TrimSpaces ((CHAR16 *)RemoteFilePath); + + if (ParamCount == MAX_PARAM_COUNT - ParamOffset) { + mLocalFilePath = ShellCommandLineGetRawValue ( + CheckPackage, + MAX_PARAM_COUNT - 1 - ParamOffset + ); + } else { + Walker = RemoteFilePath + StrLen (RemoteFilePath); + while ((--Walker) >= RemoteFilePath) { + if ((*Walker == L'\\') || + (*Walker == L'/' ) ) { + break; + } + } + + mLocalFilePath = Walker + 1; + } + + if (!StrLen (mLocalFilePath)) { + mLocalFilePath = DEFAULT_HTML_FILE; + } + + InitialSize = 0; + Context.Uri = StrnCatGrow ( + &Context.Uri, + &InitialSize, + RemoteFilePath, + StrLen (RemoteFilePath) + ); + if (!Context.Uri) { + Status = EFI_OUT_OF_RESOURCES; + goto Error; + } + + // + // Get the name of the Network Interface Card to be used if any. + // + UserNicName = ShellCommandLineGetValue (CheckPackage, L"-i"); + + ValueStr = ShellCommandLineGetValue (CheckPackage, L"-l"); + if ((ValueStr != NULL) + && (!StringToUint16 ( + ValueStr, + &Context.HttpConfigData.AccessPoint.IPv4Node->LocalPort + ) + )) + { + goto Error; + } + + Context.BufferSize = DEFAULT_BUF_SIZE; + + ValueStr = ShellCommandLineGetValue (CheckPackage, L"-s"); + if (ValueStr != NULL) { + Context.BufferSize = ShellStrToUintn (ValueStr); + if (!Context.BufferSize || Context.BufferSize > MAX_BUF_SIZE) { + PRINT_HII_APP (STRING_TOKEN (STR_GEN_PARAM_INV), ValueStr); + goto Error; + } + } + + ValueStr = ShellCommandLineGetValue (CheckPackage, L"-t"); + if (ValueStr != NULL) { + Context.HttpConfigData.TimeOutMillisec = (UINT32)ShellStrToUintn (ValueStr); + } + + // + // Locate all HTTP Service Binding protocols. + // + Status = gBS->LocateHandleBuffer ( + ByProtocol, + &gEfiManagedNetworkServiceBindingProtocolGuid, + NULL, + &HandleCount, + &Handles + ); + if (EFI_ERROR (Status) || (HandleCount == 0)) { + PRINT_HII (STRING_TOKEN (STR_HTTP_ERR_NO_NIC), NULL); + if (!EFI_ERROR (Status)) { + Status = EFI_NOT_FOUND; + } + + goto Error; + } + + Status = EFI_NOT_FOUND; + + Context.Flags = 0; + if (ShellCommandLineGetFlag (CheckPackage, L"-m")) { + Context.Flags |= DL_FLAG_TIME; + } + + if (ShellCommandLineGetFlag (CheckPackage, L"-k")) { + Context.Flags |= DL_FLAG_KEEP_BAD; + } + + for (NicNumber = 0; + (NicNumber < HandleCount) && (Status != EFI_SUCCESS); + NicNumber++) + { + ControllerHandle = Handles[NicNumber]; + + Status = GetNicName (ControllerHandle, NicNumber, NicName); + if (EFI_ERROR (Status)) { + PRINT_HII (STRING_TOKEN (STR_HTTP_ERR_NIC_NAME), NicNumber, Status); + continue; + } + + if (UserNicName != NULL) { + if (StrCmp (NicName, UserNicName) != 0) { + Status = EFI_NOT_FOUND; + continue; + } + + NicFound = TRUE; + } + + Status = DownloadFile (&Context, ControllerHandle, NicName); + PRINT_HII (STRING_TOKEN (STR_GEN_CRLF), NULL); + + if (EFI_ERROR (Status)) { + PRINT_HII ( + STRING_TOKEN (STR_HTTP_ERR_DOWNLOAD), + RemoteFilePath, + NicName, + Status + ); + // + // If a user aborted the operation, + // do not try another controller. + // + if (Status == EFI_ABORTED) { + goto Error; + } + } + + if (gHttpError) { + // + // This is not related to connection, so no need to repeat with + // another interface. + // + break; + } + } + + if ((UserNicName != NULL) && (!NicFound)) { + PRINT_HII (STRING_TOKEN (STR_HTTP_ERR_NIC_NOT_FOUND), UserNicName); + } + +Error: + ShellCommandLineFreeVarList (CheckPackage); + SHELL_FREE_NON_NULL (Handles); + SHELL_FREE_NON_NULL (Context.ServerAddrAndProto); + SHELL_FREE_NON_NULL (Context.Uri); + + return Status & ~MAX_BIT; +} + +/** + Check and convert the UINT16 option values of the 'http' command + + @param[in] ValueStr Value as an Unicode encoded string + @param[out] Value UINT16 value + + @retval TRUE The value was returned. + @retval FALSE A parsing error occured. +**/ +STATIC +BOOLEAN +StringToUint16 ( + IN CONST CHAR16 *ValueStr, + OUT UINT16 *Value + ) +{ + UINTN Val; + + Val = ShellStrToUintn (ValueStr); + if (Val > MAX_UINT16) { + PRINT_HII_APP (STRING_TOKEN (STR_GEN_PARAM_INV), ValueStr); + return FALSE; + } + + *Value = (UINT16)Val; + return TRUE; +} + +/** + Get the name of the NIC. + + @param[in] ControllerHandle The network physical device handle. + @param[in] NicNumber The network physical device number. + @param[out] NicName Address where to store the NIC name. + The memory area has to be at least + IP4_CONFIG2_INTERFACE_INFO_NAME_LENGTH + double byte wide. + + @retval EFI_SUCCESS The name of the NIC was returned. + @retval Others The creation of the child for the Managed + Network Service failed or the opening of + the Managed Network Protocol failed or + the operational parameters for the + Managed Network Protocol could not be + read. +**/ +STATIC +EFI_STATUS +GetNicName ( + IN EFI_HANDLE ControllerHandle, + IN UINTN NicNumber, + OUT CHAR16 *NicName + ) +{ + EFI_STATUS Status; + EFI_HANDLE MnpHandle; + EFI_MANAGED_NETWORK_PROTOCOL *Mnp; + EFI_SIMPLE_NETWORK_MODE SnpMode; + + Status = CreateServiceChildAndOpenProtocol ( + ControllerHandle, + &gEfiManagedNetworkServiceBindingProtocolGuid, + &gEfiManagedNetworkProtocolGuid, + &MnpHandle, + (VOID**)&Mnp + ); + if (EFI_ERROR (Status)) { + goto Error; + } + + Status = Mnp->GetModeData (Mnp, NULL, &SnpMode); + if (EFI_ERROR (Status) && (Status != EFI_NOT_STARTED)) { + goto Error; + } + + UnicodeSPrint ( + NicName, + IP4_CONFIG2_INTERFACE_INFO_NAME_LENGTH, + SnpMode.IfType == NET_IFTYPE_ETHERNET ? L"eth%d" : L"unk%d", + NicNumber + ); + + Status = EFI_SUCCESS; + +Error: + + if (MnpHandle != NULL) { + CloseProtocolAndDestroyServiceChild ( + ControllerHandle, + &gEfiManagedNetworkServiceBindingProtocolGuid, + &gEfiManagedNetworkProtocolGuid, + MnpHandle + ); + } + + return Status; +} + +/** + Create a child for the service identified by its service binding protocol GUID + and get from the child the interface of the protocol identified by its GUID. + + @param[in] ControllerHandle Controller handle. + @param[in] ServiceBindingProtocolGuid Service binding protocol GUID of the + service to be created. + @param[in] ProtocolGuid GUID of the protocol to be open. + @param[out] ChildHandle Address where the handler of the + created child is returned. NULL is + returned in case of error. + @param[out] Interface Address where a pointer to the + protocol interface is returned in + case of success. + + @retval EFI_SUCCESS The child was created and the protocol opened. + @retval Others Either the creation of the child or the opening + of the protocol failed. +**/ +STATIC +EFI_STATUS +CreateServiceChildAndOpenProtocol ( + IN EFI_HANDLE ControllerHandle, + IN EFI_GUID *ServiceBindingProtocolGuid, + IN EFI_GUID *ProtocolGuid, + OUT EFI_HANDLE *ChildHandle, + OUT VOID **Interface + ) +{ + EFI_STATUS Status; + + *ChildHandle = NULL; + Status = NetLibCreateServiceChild ( + ControllerHandle, + gImageHandle, + ServiceBindingProtocolGuid, + ChildHandle + ); + if (!EFI_ERROR (Status)) { + Status = gBS->OpenProtocol ( + *ChildHandle, + ProtocolGuid, + Interface, + gImageHandle, + ControllerHandle, + EFI_OPEN_PROTOCOL_GET_PROTOCOL + ); + if (EFI_ERROR (Status)) { + NetLibDestroyServiceChild ( + ControllerHandle, + gImageHandle, + ServiceBindingProtocolGuid, + *ChildHandle + ); + *ChildHandle = NULL; + } + } + + return Status; +} + +/** + Close the protocol identified by its GUID on the child handle of the service + identified by its service binding protocol GUID, then destroy the child + handle. + + @param[in] ControllerHandle Controller handle. + @param[in] ServiceBindingProtocolGuid Service binding protocol GUID of the + service to be destroyed. + @param[in] ProtocolGuid GUID of the protocol to be closed. + @param[in] ChildHandle Handle of the child to be destroyed. +**/ +STATIC +VOID +CloseProtocolAndDestroyServiceChild ( + IN EFI_HANDLE ControllerHandle, + IN EFI_GUID *ServiceBindingProtocolGuid, + IN EFI_GUID *ProtocolGuid, + IN EFI_HANDLE ChildHandle + ) +{ + gBS->CloseProtocol ( + ChildHandle, + ProtocolGuid, + gImageHandle, + ControllerHandle + ); + + NetLibDestroyServiceChild ( + ControllerHandle, + gImageHandle, + ServiceBindingProtocolGuid, + ChildHandle + ); +} + +/** + Wait until operation completes. Completion is indicated by + setting of an appropriate variable. + + @param[in] Context A pointer to the HTTP download context. + @param[in, out] CallBackComplete A pointer to the callback completion + variable set by the callback. + + @retval EFI_SUCCESS Callback signalled completion. + @retval EFI_TIMEOUT Timed out waiting for completion. + @retval Others Error waiting for completion. +**/ +STATIC +EFI_STATUS +WaitForCompletion ( + IN HTTP_DOWNLOAD_CONTEXT *Context, + IN OUT BOOLEAN *CallBackComplete + ) +{ + EFI_STATUS Status; + EFI_EVENT WaitEvt; + + Status = EFI_SUCCESS; + + // + // Use a timer to measure timeout. Cannot use Stall here! + // + Status = gBS->CreateEvent ( + EVT_TIMER, + TPL_CALLBACK, + NULL, + NULL, + &WaitEvt + ); + ASSERT_EFI_ERROR (Status); + + if (!EFI_ERROR (Status)) { + Status = gBS->SetTimer ( + WaitEvt, + TimerRelative, + EFI_TIMER_PERIOD_SECONDS (TIMER_MAX_TIMEOUT_S) + ); + + ASSERT_EFI_ERROR (Status); + } + + while (! *CallBackComplete + && (!EFI_ERROR (Status)) + && EFI_ERROR (gBS->CheckEvent (WaitEvt))) + { + Status = Context->Http->Poll (Context->Http); + if (!Context->ContentDownloaded + && CallBackComplete == &gResponseCallbackComplete) + { + // + // An HTTP server may just send a response redirection header. + // In this case, don't wait for the event as + // it might never happen and we waste 10s waiting. + // Note that at this point Response may not has been populated, + // so it needs to be checked first. + // + if (Context->ResponseToken.Message + && Context->ResponseToken.Message->Data.Response + && (NEED_REDIRECTION ( + Context->ResponseToken.Message->Data.Response->StatusCode + ) + )) + { + break; + } + } + } + + gBS->SetTimer (WaitEvt, TimerCancel, 0); + gBS->CloseEvent (WaitEvt); + + if (*CallBackComplete) { + return EFI_SUCCESS; + } + + if (!EFI_ERROR (Status)) { + Status = EFI_TIMEOUT; + } + + return Status; +} + +/** + Generate and send a request to the http server. + + @param[in] Context HTTP download context. + @param[in] DownloadUrl Fully qualified URL to be downloaded. + + @retval EFI_SUCCESS Request has been sent successfully. + @retval EFI_INVALID_PARAMETER Invalid URL. + @retval EFI_OUT_OF_RESOURCES Out of memory. + @retval EFI_DEVICE_ERROR If HTTPS is used, this probably + means that TLS support either was not + installed or not configured. + @retval Others Error sending the request. +**/ +STATIC +EFI_STATUS +SendRequest ( + IN HTTP_DOWNLOAD_CONTEXT *Context, + IN CHAR16 *DownloadUrl + ) +{ + EFI_HTTP_REQUEST_DATA RequestData; + EFI_HTTP_HEADER RequestHeader[HdrMax]; + EFI_HTTP_MESSAGE RequestMessage; + EFI_STATUS Status; + CHAR16 *Host; + UINTN StringSize; + + ZeroMem (&RequestData, sizeof (RequestData)); + ZeroMem (&RequestHeader, sizeof (RequestHeader)); + ZeroMem (&RequestMessage, sizeof (RequestMessage)); + ZeroMem (&Context->RequestToken, sizeof (Context->RequestToken)); + + RequestHeader[HdrHost].FieldName = "Host"; + RequestHeader[HdrConn].FieldName = "Connection"; + RequestHeader[HdrAgent].FieldName = "User-Agent"; + + Host = (CHAR16 *)Context->ServerAddrAndProto; + while (*Host != CHAR_NULL && *Host != L'/') { + Host++; + } + + if (*Host == CHAR_NULL) { + return EFI_INVALID_PARAMETER; + } + + // + // Get the next slash. + // + Host++; + // + // And now the host name. + // + Host++; + + StringSize = StrLen (Host) + 1; + RequestHeader[HdrHost].FieldValue = AllocatePool (StringSize); + if (!RequestHeader[HdrHost].FieldValue) { + return EFI_OUT_OF_RESOURCES; + } + + UnicodeStrToAsciiStrS ( + Host, + RequestHeader[HdrHost].FieldValue, + StringSize + ); + + RequestHeader[HdrConn].FieldValue = "close"; + RequestHeader[HdrAgent].FieldValue = USER_AGENT_HDR; + RequestMessage.HeaderCount = HdrMax; + + RequestData.Method = HttpMethodGet; + RequestData.Url = DownloadUrl; + + RequestMessage.Data.Request = &RequestData; + RequestMessage.Headers = RequestHeader; + RequestMessage.BodyLength = 0; + RequestMessage.Body = NULL; + Context->RequestToken.Event = NULL; + + // + // Completion callback event to be set when Request completes. + // + Status = gBS->CreateEvent ( + EVT_NOTIFY_SIGNAL, + TPL_CALLBACK, + RequestCallback, + Context, + &Context->RequestToken.Event + ); + ASSERT_EFI_ERROR (Status); + + Context->RequestToken.Status = EFI_SUCCESS; + Context->RequestToken.Message = &RequestMessage; + gRequestCallbackComplete = FALSE; + Status = Context->Http->Request (Context->Http, &Context->RequestToken); + if (EFI_ERROR (Status)) { + goto Error; + } + + Status = WaitForCompletion (Context, &gRequestCallbackComplete); + if (EFI_ERROR (Status)) { + Context->Http->Cancel (Context->Http, &Context->RequestToken); + } + +Error: + SHELL_FREE_NON_NULL (RequestHeader[HdrHost].FieldValue); + if (Context->RequestToken.Event) { + gBS->CloseEvent (Context->RequestToken.Event); + ZeroMem (&Context->RequestToken, sizeof (Context->RequestToken)); + } + + return Status; +} + +/** + Update the progress of a file download + This procedure is called each time a new HTTP body portion is received. + + @param[in] Context HTTP download context. + @param[in] DownloadLen Portion size, in bytes. + @param[in] Buffer The pointer to the parsed buffer. + + @retval EFI_SUCCESS Portion saved. + @retval Other Error saving the portion. +**/ +STATIC +EFI_STATUS +EFIAPI +SavePortion ( + IN HTTP_DOWNLOAD_CONTEXT *Context, + IN UINTN DownloadLen, + IN CHAR8 *Buffer + ) +{ + CHAR16 Progress[HTTP_PROGRESS_MESSAGE_SIZE]; + UINTN NbOfKb; + UINTN Index; + UINTN LastStep; + UINTN Step; + EFI_STATUS Status; + + LastStep = 0; + Step = 0; + + ShellSetFilePosition (mFileHandle, Context->LastReportedNbOfBytes); + Status = ShellWriteFile (mFileHandle, &DownloadLen, Buffer); + if (EFI_ERROR (Status)) { + if (Context->ContentDownloaded > 0) { + PRINT_HII (STRING_TOKEN (STR_GEN_CRLF), NULL); + } + + PRINT_HII (STRING_TOKEN (STR_HTTP_ERR_WRITE), mLocalFilePath, Status); + return Status; + } + + if (Context->ContentDownloaded == 0) { + ShellPrintEx (-1, -1, L"%s 0 Kb", HTTP_PROGR_FRAME); + } + + Context->ContentDownloaded += DownloadLen; + NbOfKb = Context->ContentDownloaded >> 10; + + Progress[0] = L'\0'; + if (Context->ContentLength) { + LastStep = (Context->LastReportedNbOfBytes * HTTP_PROGRESS_SLIDER_STEPS) / + Context->ContentLength; + Step = (Context->ContentDownloaded * HTTP_PROGRESS_SLIDER_STEPS) / + Context->ContentLength; + } + + Context->LastReportedNbOfBytes = Context->ContentDownloaded; + + if (Step <= LastStep) { + if (!Context->ContentLength) { + // + // Update downloaded size, there is no length info available. + // + ShellPrintEx (-1, -1, L"%s", HTTP_KB); + ShellPrintEx (-1, -1, L"%7d Kb", NbOfKb); + } + + return EFI_SUCCESS; + } + + ShellPrintEx (-1, -1, L"%s", HTTP_PROGRESS_DEL); + + Status = StrCpyS (Progress, HTTP_PROGRESS_MESSAGE_SIZE, HTTP_PROGR_FRAME); + if (EFI_ERROR (Status)) { + return Status; + } + + for (Index = 1; Index < Step; Index++) { + Progress[Index] = L'='; + } + + if (Step) { + Progress[Step] = L'>'; + } + + UnicodeSPrint ( + Progress + (sizeof (HTTP_PROGR_FRAME) / sizeof (CHAR16)) - 1, + sizeof (Progress) - sizeof (HTTP_PROGR_FRAME), + L" %7d Kb", + NbOfKb + ); + + + ShellPrintEx (-1, -1, L"%s", Progress); + + return EFI_SUCCESS; +} + +/** + Replace the original Host and Uri with Host and Uri returned by the + HTTP server in 'Location' header (redirection). + + @param[in] Location A pointer to the 'Location' string + provided by HTTP server. + @param[in] Context A pointer to HTTP download context. + @param[in] DownloadUrl Fully qualified HTTP URL. + + @retval EFI_SUCCESS Host and Uri were successfully set. + @retval EFI_OUT_OF_RESOURCES Error setting Host or Uri. +**/ +STATIC +EFI_STATUS +SetHostURI ( + IN CHAR8 *Location, + IN HTTP_DOWNLOAD_CONTEXT *Context, + IN CHAR16 *DownloadUrl + ) +{ + EFI_STATUS Status; + UINTN StringSize; + UINTN FirstStep; + UINTN Idx; + UINTN Step; + CHAR8 *Walker; + CHAR16 *Temp; + CHAR8 *Tmp; + CHAR16 *Url; + BOOLEAN IsAbEmptyUrl; + + Tmp = NULL; + Url = NULL; + IsAbEmptyUrl = FALSE; + FirstStep = 0; + + StringSize = (AsciiStrSize (Location) * sizeof (CHAR16)); + Url = AllocateZeroPool (StringSize); + if (!Url) { + return EFI_OUT_OF_RESOURCES; + } + + Status = AsciiStrToUnicodeStrS ( + (CONST CHAR8 *)Location, + Url, + StringSize + ); + + if (EFI_ERROR (Status)) { + goto Error; + } + + // + // If an HTTP server redirects to the same location more than once, + // then stop attempts and tell it is not reachable. + // + if (!StrCmp (Url, DownloadUrl)) { + Status = EFI_NO_MAPPING; + goto Error; + } + + if (AsciiStrLen (Location) > 2) { + // + // Some servers return 'Location: //server/resource' + // + IsAbEmptyUrl = (Location[0] == '/') && (Location[1] == '/'); + if (IsAbEmptyUrl) { + // + // Skip first "//" + // + Location += 2; + FirstStep = 1; + } + } + + if (AsciiStrStr (Location, "://") || IsAbEmptyUrl) { + Idx = 0; + Walker = Location; + + for (Step = FirstStep; Step < 2; Step++) { + for (; *Walker != '/' && *Walker != '\0'; Walker++) { + Idx++; + } + + if (!Step) { + // + // Skip "//" + // + Idx += 2; + Walker += 2; + } + } + + Tmp = AllocateZeroPool (Idx + 1); + if (!Tmp) { + Status = EFI_OUT_OF_RESOURCES; + goto Error; + } + + CopyMem (Tmp, Location, Idx); + + // + // Location now points to Uri + // + Location += Idx; + StringSize = (Idx + 1) * sizeof (CHAR16); + + SHELL_FREE_NON_NULL (Context->ServerAddrAndProto); + + Temp = AllocateZeroPool (StringSize); + if (!Temp) { + Status = EFI_OUT_OF_RESOURCES; + goto Error; + } + + Status = AsciiStrToUnicodeStrS ( + (CONST CHAR8 *)Tmp, + Temp, + StringSize + ); + if (EFI_ERROR (Status)) { + SHELL_FREE_NON_NULL (Temp); + goto Error; + } + + Idx = 0; + if (IsAbEmptyUrl) { + Context->ServerAddrAndProto = StrnCatGrow ( + &Context->ServerAddrAndProto, + &Idx, + L"http://", + StrLen (L"http://") + ); + } + + Context->ServerAddrAndProto = StrnCatGrow ( + &Context->ServerAddrAndProto, + &Idx, + Temp, + StrLen (Temp) + ); + SHELL_FREE_NON_NULL (Temp); + if (!Context->ServerAddrAndProto) { + Status = EFI_OUT_OF_RESOURCES; + goto Error; + } + } + + SHELL_FREE_NON_NULL (Context->Uri); + + StringSize = AsciiStrSize (Location) * sizeof (CHAR16); + Context->Uri = AllocateZeroPool (StringSize); + if (!Context->Uri) { + Status = EFI_OUT_OF_RESOURCES; + goto Error; + } + + // + // Now make changes to the Uri part. + // + Status = AsciiStrToUnicodeStrS ( + (CONST CHAR8 *)Location, + Context->Uri, + StringSize + ); +Error: + SHELL_FREE_NON_NULL (Tmp); + SHELL_FREE_NON_NULL (Url); + + return Status; +} + +/** + Message parser callback. + Save a portion of HTTP body. + + @param[in] EventType Type of event. Can be either + OnComplete or OnData. + @param[in] Data A pointer to the buffer with data. + @param[in] Length Data length of this portion. + @param[in] Context A pointer to the HTTP download context. + + @retval EFI_SUCCESS The portion was processed successfully. + @retval Other Error returned by SavePortion. +**/ +STATIC +EFI_STATUS +EFIAPI +ParseMsg ( + IN HTTP_BODY_PARSE_EVENT EventType, + IN CHAR8 *Data, + IN UINTN Length, + IN VOID *Context + ) +{ + if ((Data == NULL) + || (EventType == BodyParseEventOnComplete) + || (Context == NULL)) + { + return EFI_SUCCESS; + } + + return SavePortion (Context, Length, Data); +} + + +/** + Get HTTP server response and collect the whole body as a file. + Set appropriate status in Context (REQ_OK, REQ_REPEAT, REQ_ERROR). + Note that even if HTTP server returns an error code, it might send + the body as well. This body will be collected in the resultant file. + + @param[in] Context A pointer to the HTTP download context. + @param[in] DownloadUrl A pointer to the fully qualified URL to download. + + @retval EFI_SUCCESS Valid file. Body successfully collected. + @retval EFI_HTTP_ERROR Response is a valid HTTP response, but the + HTTP server + indicated an error (HTTP code >= 400). + Response body MAY contain full + HTTP server response. + @retval Others Error getting the reponse from the HTTP server. + Response body is not collected. +**/ +STATIC +EFI_STATUS +GetResponse ( + IN HTTP_DOWNLOAD_CONTEXT *Context, + IN CHAR16 *DownloadUrl + ) +{ + EFI_HTTP_RESPONSE_DATA ResponseData; + EFI_HTTP_MESSAGE ResponseMessage; + EFI_HTTP_HEADER *Header; + EFI_STATUS Status; + VOID *MsgParser; + EFI_TIME StartTime; + EFI_TIME EndTime; + CONST CHAR16 *Desc; + UINTN ElapsedSeconds; + BOOLEAN IsTrunked; + BOOLEAN CanMeasureTime; + + ZeroMem (&ResponseData, sizeof (ResponseData)); + ZeroMem (&ResponseMessage, sizeof (ResponseMessage)); + ZeroMem (&Context->ResponseToken, sizeof (Context->ResponseToken)); + IsTrunked = FALSE; + + ResponseMessage.Body = Context->Buffer; + Context->ResponseToken.Status = EFI_SUCCESS; + Context->ResponseToken.Message = &ResponseMessage; + Context->ContentLength = 0; + Context->Status = REQ_OK; + Status = EFI_SUCCESS; + MsgParser = NULL; + ResponseData.StatusCode = HTTP_STATUS_UNSUPPORTED_STATUS; + ResponseMessage.Data.Response = &ResponseData; + Context->ResponseToken.Event = NULL; + CanMeasureTime = FALSE; + if (Context->Flags & DL_FLAG_TIME) { + ZeroMem (&StartTime, sizeof (StartTime)); + CanMeasureTime = !EFI_ERROR (gRT->GetTime (&StartTime, NULL)); + } + + do { + SHELL_FREE_NON_NULL (ResponseMessage.Headers); + ResponseMessage.HeaderCount = 0; + gResponseCallbackComplete = FALSE; + ResponseMessage.BodyLength = Context->BufferSize; + + if (ShellGetExecutionBreakFlag ()) { + Status = EFI_ABORTED; + break; + } + + if (!Context->ContentDownloaded && !Context->ResponseToken.Event) { + Status = gBS->CreateEvent ( + EVT_NOTIFY_SIGNAL, + TPL_CALLBACK, + ResponseCallback, + Context, + &Context->ResponseToken.Event + ); + ASSERT_EFI_ERROR (Status); + } else { + ResponseMessage.Data.Response = NULL; + } + + if (EFI_ERROR (Status)) { + break; + } + + Status = Context->Http->Response (Context->Http, &Context->ResponseToken); + if (EFI_ERROR (Status)) { + break; + } + + Status = WaitForCompletion (Context, &gResponseCallbackComplete); + if (EFI_ERROR (Status) && ResponseMessage.HeaderCount) { + Status = EFI_SUCCESS; + } + + if (EFI_ERROR (Status)) { + Context->Http->Cancel (Context->Http, &Context->ResponseToken); + break; + } + + if (!Context->ContentDownloaded) { + if (NEED_REDIRECTION (ResponseData.StatusCode)) { + // + // Need to repeat the request with new Location (server redirected). + // + Context->Status = REQ_NEED_REPEAT; + + Header = HttpFindHeader ( + ResponseMessage.HeaderCount, + ResponseMessage.Headers, + "Location" + ); + if (Header) { + Status = SetHostURI (Header->FieldValue, Context, DownloadUrl); + if (Status == EFI_NO_MAPPING) { + PRINT_HII ( + STRING_TOKEN (STR_HTTP_ERR_STATUSCODE), + Context->ServerAddrAndProto, + L"Recursive HTTP server relocation", + Context->Uri + ); + } + } else { + // + // Bad reply from the server. Server must specify the location. + // Indicate that resource was not found, and no body collected. + // + Status = EFI_NOT_FOUND; + } + + Context->Http->Cancel (Context->Http, &Context->ResponseToken); + break; + } + + // + // Init message-body parser by header information. + // + if (!MsgParser) { + Status = HttpInitMsgParser ( + ResponseMessage.Data.Request->Method, + ResponseData.StatusCode, + ResponseMessage.HeaderCount, + ResponseMessage.Headers, + ParseMsg, + Context, + &MsgParser + ); + if (EFI_ERROR (Status)) { + break; + } + } + + // + // If it is a trunked message, rely on the parser. + // + Header = HttpFindHeader ( + ResponseMessage.HeaderCount, + ResponseMessage.Headers, + "Transfer-Encoding" + ); + IsTrunked = (Header && !AsciiStrCmp (Header->FieldValue, "chunked")); + + HttpGetEntityLength (MsgParser, &Context->ContentLength); + + if (ResponseData.StatusCode >= HTTP_STATUS_400_BAD_REQUEST + && (ResponseData.StatusCode != HTTP_STATUS_308_PERMANENT_REDIRECT)) + { + // + // Server reported an error via Response code. + // Collect the body if any. + // + if (!gHttpError) { + gHttpError = TRUE; + + Desc = ErrStatusDesc[ResponseData.StatusCode - + HTTP_STATUS_400_BAD_REQUEST]; + PRINT_HII ( + STRING_TOKEN (STR_HTTP_ERR_STATUSCODE), + Context->ServerAddrAndProto, + Desc, + Context->Uri + ); + + // + // This gives an RFC HTTP error. + // + Context->Status = ShellStrToUintn (Desc); + Status = ENCODE_ERROR (Context->Status); + } + } + } + + // + // Do NOT try to parse an empty body. + // + if (ResponseMessage.BodyLength || IsTrunked) { + Status = HttpParseMessageBody ( + MsgParser, + ResponseMessage.BodyLength, + ResponseMessage.Body + ); + } + } while (!HttpIsMessageComplete (MsgParser) + && !EFI_ERROR (Status) + && ResponseMessage.BodyLength); + + if (Context->Status != REQ_NEED_REPEAT + && Status == EFI_SUCCESS + && CanMeasureTime) + { + if (!EFI_ERROR (gRT->GetTime (&EndTime, NULL))) { + ElapsedSeconds = EfiTimeToEpoch (&EndTime) - EfiTimeToEpoch (&StartTime); + Print ( + L",%a%Lus\n", + ElapsedSeconds ? " " : " < ", + ElapsedSeconds > 1 ? (UINT64)ElapsedSeconds : 1 + ); + } + } + + SHELL_FREE_NON_NULL (MsgParser); + if (Context->ResponseToken.Event) { + gBS->CloseEvent (Context->ResponseToken.Event); + ZeroMem (&Context->ResponseToken, sizeof (Context->ResponseToken)); + } + + return Status; +} + +/** + Worker function that downloads the data of a file from an HTTP server given + the path of the file and its size. + + @param[in] Context A pointer to the HTTP download context. + @param[in] ControllerHandle The handle of the network interface controller + @param[in] NicName NIC name + + @retval EFI_SUCCESS The file was downloaded. + @retval EFI_OUT_OF_RESOURCES A memory allocation failed. + #return EFI_HTTP_ERROR The server returned a valid HTTP error. + Examine the mLocalFilePath file + to get error body. + @retval Others The downloading of the file from the server + failed. +**/ +STATIC +EFI_STATUS +DownloadFile ( + IN HTTP_DOWNLOAD_CONTEXT *Context, + IN EFI_HANDLE ControllerHandle, + IN CHAR16 *NicName + ) +{ + EFI_STATUS Status; + CHAR16 *DownloadUrl; + UINTN UrlSize; + EFI_HANDLE HttpChildHandle; + + ASSERT (Context); + if (Context == NULL) { + return EFI_INVALID_PARAMETER; + } + + DownloadUrl = NULL; + HttpChildHandle = NULL; + + Context->Buffer = AllocatePool (Context->BufferSize); + if (Context->Buffer == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto ON_EXIT; + } + + // + // Open the file. + // + if (!EFI_ERROR (ShellFileExists (mLocalFilePath))) { + ShellDeleteFileByName (mLocalFilePath); + } + + Status = ShellOpenFileByName ( + mLocalFilePath, + &mFileHandle, + EFI_FILE_MODE_CREATE | + EFI_FILE_MODE_WRITE | + EFI_FILE_MODE_READ, + 0 + ); + if (EFI_ERROR (Status)) { + PRINT_HII_APP (STRING_TOKEN (STR_GEN_FILE_OPEN_FAIL), mLocalFilePath); + goto ON_EXIT; + } + + do { + SHELL_FREE_NON_NULL (DownloadUrl); + + CLOSE_HTTP_HANDLE (ControllerHandle, HttpChildHandle); + + Status = CreateServiceChildAndOpenProtocol ( + ControllerHandle, + &gEfiHttpServiceBindingProtocolGuid, + &gEfiHttpProtocolGuid, + &HttpChildHandle, + (VOID**)&Context->Http + ); + + if (EFI_ERROR (Status)) { + PRINT_HII (STRING_TOKEN (STR_HTTP_ERR_OPEN_PROTOCOL), NicName, Status); + goto ON_EXIT; + } + + Status = Context->Http->Configure (Context->Http, &Context->HttpConfigData); + if (EFI_ERROR (Status)) { + PRINT_HII (STRING_TOKEN (STR_HTTP_ERR_CONFIGURE), NicName, Status); + goto ON_EXIT; + } + + UrlSize = 0; + DownloadUrl = StrnCatGrow ( + &DownloadUrl, + &UrlSize, + Context->ServerAddrAndProto, + StrLen (Context->ServerAddrAndProto) + ); + if (Context->Uri[0] != L'/') { + DownloadUrl = StrnCatGrow ( + &DownloadUrl, + &UrlSize, + L"/", + StrLen (Context->ServerAddrAndProto) + ); + } + + DownloadUrl = StrnCatGrow ( + &DownloadUrl, + &UrlSize, + Context->Uri, + StrLen (Context->Uri)); + + PRINT_HII (STRING_TOKEN (STR_HTTP_DOWNLOADING), DownloadUrl); + + Status = SendRequest (Context, DownloadUrl); + if (Status) { + goto ON_EXIT; + } + + Status = GetResponse (Context, DownloadUrl); + + if (Status) { + goto ON_EXIT; + } + + } while (Context->Status == REQ_NEED_REPEAT); + + if (Context->Status) { + Status = ENCODE_ERROR (Context->Status); + } + +ON_EXIT: + // + // Close the file. + // + if (mFileHandle != NULL) { + if (EFI_ERROR (Status) && !(Context->Flags & DL_FLAG_KEEP_BAD)) { + ShellDeleteFile (&mFileHandle); + } else { + ShellCloseFile (&mFileHandle); + } + } + + SHELL_FREE_NON_NULL (DownloadUrl); + SHELL_FREE_NON_NULL (Context->Buffer); + + CLOSE_HTTP_HANDLE (ControllerHandle, HttpChildHandle); + + return Status; +} + +/** + Retrive HII package list from ImageHandle and publish to HII database. + + @param[in] ImageHandle The image handle of the process. + + @retval HII handle. +**/ +EFI_HII_HANDLE +InitializeHiiPackage ( + IN EFI_HANDLE ImageHandle + ) +{ + EFI_STATUS Status; + EFI_HII_PACKAGE_LIST_HEADER *PackageList; + EFI_HII_HANDLE HiiHandle; + + // + // Retrieve HII package list from ImageHandle. + // + Status = gBS->OpenProtocol ( + ImageHandle, + &gEfiHiiPackageListProtocolGuid, + (VOID **)&PackageList, + ImageHandle, + NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL + ); + ASSERT_EFI_ERROR (Status); + if (EFI_ERROR (Status)) { + return NULL; + } + + // + // Publish HII package list to HII Database. + // + Status = gHiiDatabase->NewPackageList ( + gHiiDatabase, + PackageList, + NULL, + &HiiHandle + ); + ASSERT_EFI_ERROR (Status); + if (EFI_ERROR (Status)) { + return NULL; + } + + return HiiHandle; +} |