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