diff options
Diffstat (limited to 'ShellPkg/DynamicCommand/HttpDynamicCommand/Http.c')
| -rw-r--r-- | ShellPkg/DynamicCommand/HttpDynamicCommand/Http.c | 3808 |
1 files changed, 1904 insertions, 1904 deletions
diff --git a/ShellPkg/DynamicCommand/HttpDynamicCommand/Http.c b/ShellPkg/DynamicCommand/HttpDynamicCommand/Http.c index b4e88e7635..6c058198cb 100644 --- a/ShellPkg/DynamicCommand/HttpDynamicCommand/Http.c +++ b/ShellPkg/DynamicCommand/HttpDynamicCommand/Http.c @@ -1,1904 +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;
- 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;
-}
+/** @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; +} |
