| /** @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; | |
| } |