summaryrefslogtreecommitdiffstats
path: root/ShellPkg/DynamicCommand/HttpDynamicCommand/Http.c
diff options
context:
space:
mode:
Diffstat (limited to 'ShellPkg/DynamicCommand/HttpDynamicCommand/Http.c')
-rw-r--r--ShellPkg/DynamicCommand/HttpDynamicCommand/Http.c1904
1 files changed, 1904 insertions, 0 deletions
diff --git a/ShellPkg/DynamicCommand/HttpDynamicCommand/Http.c b/ShellPkg/DynamicCommand/HttpDynamicCommand/Http.c
new file mode 100644
index 0000000000..3735a4a7e6
--- /dev/null
+++ b/ShellPkg/DynamicCommand/HttpDynamicCommand/Http.c
@@ -0,0 +1,1904 @@
+/** @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;
+ 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;
+}