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