/** @file | |
This library is used to share code between UEFI network stack modules. | |
It provides the helper routines to parse the HTTP message byte stream. | |
Copyright (c) 2015 - 2019, Intel Corporation. All rights reserved.<BR> | |
(C) Copyright 2016 - 2020 Hewlett Packard Enterprise Development LP<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include "DxeHttpLib.h" | |
/** | |
Decode a percent-encoded URI component to the ASCII character. | |
Decode the input component in Buffer according to RFC 3986. The caller is responsible to make | |
sure ResultBuffer points to a buffer with size equal or greater than ((AsciiStrSize (Buffer)) | |
in bytes. | |
@param[in] Buffer The pointer to a percent-encoded URI component. | |
@param[in] BufferLength Length of Buffer in bytes. | |
@param[out] ResultBuffer Point to the buffer to store the decode result. | |
@param[out] ResultLength Length of decoded string in ResultBuffer in bytes. | |
@retval EFI_SUCCESS Successfully decoded the URI. | |
@retval EFI_INVALID_PARAMETER Buffer is not a valid percent-encoded string. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
UriPercentDecode ( | |
IN CHAR8 *Buffer, | |
IN UINT32 BufferLength, | |
OUT CHAR8 *ResultBuffer, | |
OUT UINT32 *ResultLength | |
) | |
{ | |
UINTN Index; | |
UINTN Offset; | |
CHAR8 HexStr[3]; | |
if (Buffer == NULL || BufferLength == 0 || ResultBuffer == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
Index = 0; | |
Offset = 0; | |
HexStr[2] = '\0'; | |
while (Index < BufferLength) { | |
if (Buffer[Index] == '%') { | |
if (Index + 1 >= BufferLength || Index + 2 >= BufferLength || | |
!NET_IS_HEX_CHAR (Buffer[Index+1]) || !NET_IS_HEX_CHAR (Buffer[Index+2])) { | |
return EFI_INVALID_PARAMETER; | |
} | |
HexStr[0] = Buffer[Index+1]; | |
HexStr[1] = Buffer[Index+2]; | |
ResultBuffer[Offset] = (CHAR8) AsciiStrHexToUintn (HexStr); | |
Index += 3; | |
} else { | |
ResultBuffer[Offset] = Buffer[Index]; | |
Index++; | |
} | |
Offset++; | |
} | |
*ResultLength = (UINT32) Offset; | |
return EFI_SUCCESS; | |
} | |
/** | |
This function return the updated state according to the input state and next character of | |
the authority. | |
@param[in] Char Next character. | |
@param[in] State Current value of the parser state machine. | |
@param[in] IsRightBracket TRUE if there is an sign ']' in the authority component and | |
indicates the next part is ':' before Port. | |
@return Updated state value. | |
**/ | |
HTTP_URL_PARSE_STATE | |
NetHttpParseAuthorityChar ( | |
IN CHAR8 Char, | |
IN HTTP_URL_PARSE_STATE State, | |
IN BOOLEAN *IsRightBracket | |
) | |
{ | |
// | |
// RFC 3986: | |
// The authority component is preceded by a double slash ("//") and is | |
// terminated by the next slash ("/"), question mark ("?"), or number | |
// sign ("#") character, or by the end of the URI. | |
// | |
if (Char == ' ' || Char == '\r' || Char == '\n') { | |
return UrlParserStateMax; | |
} | |
// | |
// authority = [ userinfo "@" ] host [ ":" port ] | |
// | |
switch (State) { | |
case UrlParserUserInfo: | |
if (Char == '@') { | |
return UrlParserHostStart; | |
} | |
break; | |
case UrlParserHost: | |
case UrlParserHostStart: | |
if (Char == '[') { | |
return UrlParserHostIpv6; | |
} | |
if (Char == ':') { | |
return UrlParserPortStart; | |
} | |
return UrlParserHost; | |
case UrlParserHostIpv6: | |
if (Char == ']') { | |
*IsRightBracket = TRUE; | |
} | |
if (Char == ':' && *IsRightBracket) { | |
return UrlParserPortStart; | |
} | |
return UrlParserHostIpv6; | |
case UrlParserPort: | |
case UrlParserPortStart: | |
return UrlParserPort; | |
default: | |
break; | |
} | |
return State; | |
} | |
/** | |
This function parse the authority component of the input URL and update the parser. | |
@param[in] Url The pointer to a HTTP URL string. | |
@param[in] FoundAt TRUE if there is an at sign ('@') in the authority component. | |
@param[in, out] UrlParser Pointer to the buffer of the parse result. | |
@retval EFI_SUCCESS Successfully parse the authority. | |
@retval EFI_INVALID_PARAMETER The Url is invalid to parse the authority component. | |
**/ | |
EFI_STATUS | |
NetHttpParseAuthority ( | |
IN CHAR8 *Url, | |
IN BOOLEAN FoundAt, | |
IN OUT HTTP_URL_PARSER *UrlParser | |
) | |
{ | |
CHAR8 *Char; | |
CHAR8 *Authority; | |
UINT32 Length; | |
HTTP_URL_PARSE_STATE State; | |
UINT32 Field; | |
UINT32 OldField; | |
BOOLEAN IsrightBracket; | |
ASSERT ((UrlParser->FieldBitMap & BIT (HTTP_URI_FIELD_AUTHORITY)) != 0); | |
// | |
// authority = [ userinfo "@" ] host [ ":" port ] | |
// | |
if (FoundAt) { | |
State = UrlParserUserInfo; | |
} else { | |
State = UrlParserHost; | |
} | |
IsrightBracket = FALSE; | |
Field = HTTP_URI_FIELD_MAX; | |
OldField = Field; | |
Authority = Url + UrlParser->FieldData[HTTP_URI_FIELD_AUTHORITY].Offset; | |
Length = UrlParser->FieldData[HTTP_URI_FIELD_AUTHORITY].Length; | |
for (Char = Authority; Char < Authority + Length; Char++) { | |
State = NetHttpParseAuthorityChar (*Char, State, &IsrightBracket); | |
switch (State) { | |
case UrlParserStateMax: | |
return EFI_INVALID_PARAMETER; | |
case UrlParserHostStart: | |
case UrlParserPortStart: | |
continue; | |
case UrlParserUserInfo: | |
Field = HTTP_URI_FIELD_USERINFO; | |
break; | |
case UrlParserHost: | |
Field = HTTP_URI_FIELD_HOST; | |
break; | |
case UrlParserHostIpv6: | |
Field = HTTP_URI_FIELD_HOST; | |
break; | |
case UrlParserPort: | |
Field = HTTP_URI_FIELD_PORT; | |
break; | |
default: | |
ASSERT (FALSE); | |
} | |
// | |
// Field not changed, count the length. | |
// | |
ASSERT (Field < HTTP_URI_FIELD_MAX); | |
if (Field == OldField) { | |
UrlParser->FieldData[Field].Length++; | |
continue; | |
} | |
// | |
// New field start | |
// | |
UrlParser->FieldBitMap |= BIT (Field); | |
UrlParser->FieldData[Field].Offset = (UINT32) (Char - Url); | |
UrlParser->FieldData[Field].Length = 1; | |
OldField = Field; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
This function return the updated state according to the input state and next character of a URL. | |
@param[in] Char Next character. | |
@param[in] State Current value of the parser state machine. | |
@return Updated state value. | |
**/ | |
HTTP_URL_PARSE_STATE | |
NetHttpParseUrlChar ( | |
IN CHAR8 Char, | |
IN HTTP_URL_PARSE_STATE State | |
) | |
{ | |
if (Char == ' ' || Char == '\r' || Char == '\n') { | |
return UrlParserStateMax; | |
} | |
// | |
// http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]] | |
// | |
// Request-URI = "*" | absolute-URI | path-absolute | authority | |
// | |
// absolute-URI = scheme ":" hier-part [ "?" query ] | |
// path-absolute = "/" [ segment-nz *( "/" segment ) ] | |
// authority = [ userinfo "@" ] host [ ":" port ] | |
// | |
switch (State) { | |
case UrlParserUrlStart: | |
if (Char == '*' || Char == '/') { | |
return UrlParserPath; | |
} | |
return UrlParserScheme; | |
case UrlParserScheme: | |
if (Char == ':') { | |
return UrlParserSchemeColon; | |
} | |
break; | |
case UrlParserSchemeColon: | |
if (Char == '/') { | |
return UrlParserSchemeColonSlash; | |
} | |
break; | |
case UrlParserSchemeColonSlash: | |
if (Char == '/') { | |
return UrlParserSchemeColonSlashSlash; | |
} | |
break; | |
case UrlParserAtInAuthority: | |
if (Char == '@') { | |
return UrlParserStateMax; | |
} | |
case UrlParserAuthority: | |
case UrlParserSchemeColonSlashSlash: | |
if (Char == '@') { | |
return UrlParserAtInAuthority; | |
} | |
if (Char == '/') { | |
return UrlParserPath; | |
} | |
if (Char == '?') { | |
return UrlParserQueryStart; | |
} | |
if (Char == '#') { | |
return UrlParserFragmentStart; | |
} | |
return UrlParserAuthority; | |
case UrlParserPath: | |
if (Char == '?') { | |
return UrlParserQueryStart; | |
} | |
if (Char == '#') { | |
return UrlParserFragmentStart; | |
} | |
break; | |
case UrlParserQuery: | |
case UrlParserQueryStart: | |
if (Char == '#') { | |
return UrlParserFragmentStart; | |
} | |
return UrlParserQuery; | |
case UrlParserFragmentStart: | |
return UrlParserFragment; | |
default: | |
break; | |
} | |
return State; | |
} | |
/** | |
Create a URL parser for the input URL string. | |
This function will parse and dereference the input HTTP URL into it components. The original | |
content of the URL won't be modified and the result will be returned in UrlParser, which can | |
be used in other functions like NetHttpUrlGetHostName(). | |
@param[in] Url The pointer to a HTTP URL string. | |
@param[in] Length Length of Url in bytes. | |
@param[in] IsConnectMethod Whether the Url is used in HTTP CONNECT method or not. | |
@param[out] UrlParser Pointer to the returned buffer to store the parse result. | |
@retval EFI_SUCCESS Successfully dereferenced the HTTP URL. | |
@retval EFI_INVALID_PARAMETER UrlParser is NULL or Url is not a valid HTTP URL. | |
@retval EFI_OUT_OF_RESOURCES Could not allocate needed resources. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
HttpParseUrl ( | |
IN CHAR8 *Url, | |
IN UINT32 Length, | |
IN BOOLEAN IsConnectMethod, | |
OUT VOID **UrlParser | |
) | |
{ | |
HTTP_URL_PARSE_STATE State; | |
CHAR8 *Char; | |
UINT32 Field; | |
UINT32 OldField; | |
BOOLEAN FoundAt; | |
EFI_STATUS Status; | |
HTTP_URL_PARSER *Parser; | |
Parser = NULL; | |
if (Url == NULL || Length == 0 || UrlParser == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
Parser = AllocateZeroPool (sizeof (HTTP_URL_PARSER)); | |
if (Parser == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
if (IsConnectMethod) { | |
// | |
// According to RFC 2616, the authority form is only used by the CONNECT method. | |
// | |
State = UrlParserAuthority; | |
} else { | |
State = UrlParserUrlStart; | |
} | |
Field = HTTP_URI_FIELD_MAX; | |
OldField = Field; | |
FoundAt = FALSE; | |
for (Char = Url; Char < Url + Length; Char++) { | |
// | |
// Update state machine according to next char. | |
// | |
State = NetHttpParseUrlChar (*Char, State); | |
switch (State) { | |
case UrlParserStateMax: | |
FreePool (Parser); | |
return EFI_INVALID_PARAMETER; | |
case UrlParserSchemeColon: | |
case UrlParserSchemeColonSlash: | |
case UrlParserSchemeColonSlashSlash: | |
case UrlParserQueryStart: | |
case UrlParserFragmentStart: | |
// | |
// Skip all the delimiting char: "://" "?" "@" | |
// | |
continue; | |
case UrlParserScheme: | |
Field = HTTP_URI_FIELD_SCHEME; | |
break; | |
case UrlParserAtInAuthority: | |
FoundAt = TRUE; | |
case UrlParserAuthority: | |
Field = HTTP_URI_FIELD_AUTHORITY; | |
break; | |
case UrlParserPath: | |
Field = HTTP_URI_FIELD_PATH; | |
break; | |
case UrlParserQuery: | |
Field = HTTP_URI_FIELD_QUERY; | |
break; | |
case UrlParserFragment: | |
Field = HTTP_URI_FIELD_FRAGMENT; | |
break; | |
default: | |
ASSERT (FALSE); | |
} | |
// | |
// Field not changed, count the length. | |
// | |
ASSERT (Field < HTTP_URI_FIELD_MAX); | |
if (Field == OldField) { | |
Parser->FieldData[Field].Length++; | |
continue; | |
} | |
// | |
// New field start | |
// | |
Parser->FieldBitMap |= BIT (Field); | |
Parser->FieldData[Field].Offset = (UINT32) (Char - Url); | |
Parser->FieldData[Field].Length = 1; | |
OldField = Field; | |
} | |
// | |
// If has authority component, continue to parse the username, host and port. | |
// | |
if ((Parser->FieldBitMap & BIT (HTTP_URI_FIELD_AUTHORITY)) != 0) { | |
Status = NetHttpParseAuthority (Url, FoundAt, Parser); | |
if (EFI_ERROR (Status)) { | |
FreePool (Parser); | |
return Status; | |
} | |
} | |
*UrlParser = Parser; | |
return EFI_SUCCESS; | |
} | |
/** | |
Get the Hostname from a HTTP URL. | |
This function will return the HostName according to the Url and previous parse result ,and | |
it is the caller's responsibility to free the buffer returned in *HostName. | |
@param[in] Url The pointer to a HTTP URL string. | |
@param[in] UrlParser URL Parse result returned by NetHttpParseUrl(). | |
@param[out] HostName Pointer to a buffer to store the HostName. | |
@retval EFI_SUCCESS Successfully get the required component. | |
@retval EFI_INVALID_PARAMETER Uri is NULL or HostName is NULL or UrlParser is invalid. | |
@retval EFI_NOT_FOUND No hostName component in the URL. | |
@retval EFI_OUT_OF_RESOURCES Could not allocate needed resources. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
HttpUrlGetHostName ( | |
IN CHAR8 *Url, | |
IN VOID *UrlParser, | |
OUT CHAR8 **HostName | |
) | |
{ | |
CHAR8 *Name; | |
EFI_STATUS Status; | |
UINT32 ResultLength; | |
HTTP_URL_PARSER *Parser; | |
if (Url == NULL || UrlParser == NULL || HostName == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
Parser = (HTTP_URL_PARSER *) UrlParser; | |
if ((Parser->FieldBitMap & BIT (HTTP_URI_FIELD_HOST)) == 0) { | |
return EFI_NOT_FOUND; | |
} | |
Name = AllocatePool (Parser->FieldData[HTTP_URI_FIELD_HOST].Length + 1); | |
if (Name == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Status = UriPercentDecode ( | |
Url + Parser->FieldData[HTTP_URI_FIELD_HOST].Offset, | |
Parser->FieldData[HTTP_URI_FIELD_HOST].Length, | |
Name, | |
&ResultLength | |
); | |
if (EFI_ERROR (Status)) { | |
FreePool (Name); | |
return Status; | |
} | |
Name[ResultLength] = '\0'; | |
*HostName = Name; | |
return EFI_SUCCESS; | |
} | |
/** | |
Get the IPv4 address from a HTTP URL. | |
This function will return the IPv4 address according to the Url and previous parse result. | |
@param[in] Url The pointer to a HTTP URL string. | |
@param[in] UrlParser URL Parse result returned by NetHttpParseUrl(). | |
@param[out] Ip4Address Pointer to a buffer to store the IP address. | |
@retval EFI_SUCCESS Successfully get the required component. | |
@retval EFI_INVALID_PARAMETER Uri is NULL or Ip4Address is NULL or UrlParser is invalid. | |
@retval EFI_NOT_FOUND No IPv4 address component in the URL. | |
@retval EFI_OUT_OF_RESOURCES Could not allocate needed resources. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
HttpUrlGetIp4 ( | |
IN CHAR8 *Url, | |
IN VOID *UrlParser, | |
OUT EFI_IPv4_ADDRESS *Ip4Address | |
) | |
{ | |
CHAR8 *Ip4String; | |
EFI_STATUS Status; | |
UINT32 ResultLength; | |
HTTP_URL_PARSER *Parser; | |
if (Url == NULL || UrlParser == NULL || Ip4Address == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
Parser = (HTTP_URL_PARSER *) UrlParser; | |
if ((Parser->FieldBitMap & BIT (HTTP_URI_FIELD_HOST)) == 0) { | |
return EFI_NOT_FOUND; | |
} | |
Ip4String = AllocatePool (Parser->FieldData[HTTP_URI_FIELD_HOST].Length + 1); | |
if (Ip4String == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Status = UriPercentDecode ( | |
Url + Parser->FieldData[HTTP_URI_FIELD_HOST].Offset, | |
Parser->FieldData[HTTP_URI_FIELD_HOST].Length, | |
Ip4String, | |
&ResultLength | |
); | |
if (EFI_ERROR (Status)) { | |
FreePool (Ip4String); | |
return Status; | |
} | |
Ip4String[ResultLength] = '\0'; | |
Status = NetLibAsciiStrToIp4 (Ip4String, Ip4Address); | |
FreePool (Ip4String); | |
return Status; | |
} | |
/** | |
Get the IPv6 address from a HTTP URL. | |
This function will return the IPv6 address according to the Url and previous parse result. | |
@param[in] Url The pointer to a HTTP URL string. | |
@param[in] UrlParser URL Parse result returned by NetHttpParseUrl(). | |
@param[out] Ip6Address Pointer to a buffer to store the IP address. | |
@retval EFI_SUCCESS Successfully get the required component. | |
@retval EFI_INVALID_PARAMETER Uri is NULL or Ip6Address is NULL or UrlParser is invalid. | |
@retval EFI_NOT_FOUND No IPv6 address component in the URL. | |
@retval EFI_OUT_OF_RESOURCES Could not allocate needed resources. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
HttpUrlGetIp6 ( | |
IN CHAR8 *Url, | |
IN VOID *UrlParser, | |
OUT EFI_IPv6_ADDRESS *Ip6Address | |
) | |
{ | |
CHAR8 *Ip6String; | |
CHAR8 *Ptr; | |
UINT32 Length; | |
EFI_STATUS Status; | |
UINT32 ResultLength; | |
HTTP_URL_PARSER *Parser; | |
if (Url == NULL || UrlParser == NULL || Ip6Address == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
Parser = (HTTP_URL_PARSER *) UrlParser; | |
if ((Parser->FieldBitMap & BIT (HTTP_URI_FIELD_HOST)) == 0) { | |
return EFI_NOT_FOUND; | |
} | |
// | |
// IP-literal = "[" ( IPv6address / IPvFuture ) "]" | |
// | |
Length = Parser->FieldData[HTTP_URI_FIELD_HOST].Length; | |
if (Length < 2) { | |
return EFI_INVALID_PARAMETER; | |
} | |
Ptr = Url + Parser->FieldData[HTTP_URI_FIELD_HOST].Offset; | |
if ((Ptr[0] != '[') || (Ptr[Length - 1] != ']')) { | |
return EFI_INVALID_PARAMETER; | |
} | |
Ip6String = AllocatePool (Length); | |
if (Ip6String == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Status = UriPercentDecode ( | |
Ptr + 1, | |
Length - 2, | |
Ip6String, | |
&ResultLength | |
); | |
if (EFI_ERROR (Status)) { | |
FreePool (Ip6String); | |
return Status; | |
} | |
Ip6String[ResultLength] = '\0'; | |
Status = NetLibAsciiStrToIp6 (Ip6String, Ip6Address); | |
FreePool (Ip6String); | |
return Status; | |
} | |
/** | |
Get the port number from a HTTP URL. | |
This function will return the port number according to the Url and previous parse result. | |
@param[in] Url The pointer to a HTTP URL string. | |
@param[in] UrlParser URL Parse result returned by NetHttpParseUrl(). | |
@param[out] Port Pointer to a buffer to store the port number. | |
@retval EFI_SUCCESS Successfully get the required component. | |
@retval EFI_INVALID_PARAMETER Uri is NULL or Port is NULL or UrlParser is invalid. | |
@retval EFI_NOT_FOUND No port number in the URL. | |
@retval EFI_OUT_OF_RESOURCES Could not allocate needed resources. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
HttpUrlGetPort ( | |
IN CHAR8 *Url, | |
IN VOID *UrlParser, | |
OUT UINT16 *Port | |
) | |
{ | |
CHAR8 *PortString; | |
EFI_STATUS Status; | |
UINTN Index; | |
UINTN Data; | |
UINT32 ResultLength; | |
HTTP_URL_PARSER *Parser; | |
if (Url == NULL || UrlParser == NULL || Port == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
*Port = 0; | |
Index = 0; | |
Parser = (HTTP_URL_PARSER *) UrlParser; | |
if ((Parser->FieldBitMap & BIT (HTTP_URI_FIELD_PORT)) == 0) { | |
return EFI_NOT_FOUND; | |
} | |
PortString = AllocatePool (Parser->FieldData[HTTP_URI_FIELD_PORT].Length + 1); | |
if (PortString == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Status = UriPercentDecode ( | |
Url + Parser->FieldData[HTTP_URI_FIELD_PORT].Offset, | |
Parser->FieldData[HTTP_URI_FIELD_PORT].Length, | |
PortString, | |
&ResultLength | |
); | |
if (EFI_ERROR (Status)) { | |
goto ON_EXIT; | |
} | |
PortString[ResultLength] = '\0'; | |
while (Index < ResultLength) { | |
if (!NET_IS_DIGIT (PortString[Index])) { | |
Status = EFI_INVALID_PARAMETER; | |
goto ON_EXIT; | |
} | |
Index ++; | |
} | |
Status = AsciiStrDecimalToUintnS (Url + Parser->FieldData[HTTP_URI_FIELD_PORT].Offset, (CHAR8 **) NULL, &Data); | |
if (Data > HTTP_URI_PORT_MAX_NUM) { | |
Status = EFI_INVALID_PARAMETER; | |
goto ON_EXIT; | |
} | |
*Port = (UINT16) Data; | |
ON_EXIT: | |
FreePool (PortString); | |
return Status; | |
} | |
/** | |
Get the Path from a HTTP URL. | |
This function will return the Path according to the Url and previous parse result,and | |
it is the caller's responsibility to free the buffer returned in *Path. | |
@param[in] Url The pointer to a HTTP URL string. | |
@param[in] UrlParser URL Parse result returned by NetHttpParseUrl(). | |
@param[out] Path Pointer to a buffer to store the Path. | |
@retval EFI_SUCCESS Successfully get the required component. | |
@retval EFI_INVALID_PARAMETER Uri is NULL or HostName is NULL or UrlParser is invalid. | |
@retval EFI_NOT_FOUND No hostName component in the URL. | |
@retval EFI_OUT_OF_RESOURCES Could not allocate needed resources. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
HttpUrlGetPath ( | |
IN CHAR8 *Url, | |
IN VOID *UrlParser, | |
OUT CHAR8 **Path | |
) | |
{ | |
CHAR8 *PathStr; | |
EFI_STATUS Status; | |
UINT32 ResultLength; | |
HTTP_URL_PARSER *Parser; | |
if (Url == NULL || UrlParser == NULL || Path == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
Parser = (HTTP_URL_PARSER *) UrlParser; | |
if ((Parser->FieldBitMap & BIT (HTTP_URI_FIELD_PATH)) == 0) { | |
return EFI_NOT_FOUND; | |
} | |
PathStr = AllocatePool (Parser->FieldData[HTTP_URI_FIELD_PATH].Length + 1); | |
if (PathStr == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Status = UriPercentDecode ( | |
Url + Parser->FieldData[HTTP_URI_FIELD_PATH].Offset, | |
Parser->FieldData[HTTP_URI_FIELD_PATH].Length, | |
PathStr, | |
&ResultLength | |
); | |
if (EFI_ERROR (Status)) { | |
FreePool (PathStr); | |
return Status; | |
} | |
PathStr[ResultLength] = '\0'; | |
*Path = PathStr; | |
return EFI_SUCCESS; | |
} | |
/** | |
Release the resource of the URL parser. | |
@param[in] UrlParser Pointer to the parser. | |
**/ | |
VOID | |
EFIAPI | |
HttpUrlFreeParser ( | |
IN VOID *UrlParser | |
) | |
{ | |
FreePool (UrlParser); | |
} | |
/** | |
Find a specified header field according to the field name. | |
@param[in] HeaderCount Number of HTTP header structures in Headers list. | |
@param[in] Headers Array containing list of HTTP headers. | |
@param[in] FieldName Null terminated string which describes a field name. | |
@return Pointer to the found header or NULL. | |
**/ | |
EFI_HTTP_HEADER * | |
EFIAPI | |
HttpFindHeader ( | |
IN UINTN HeaderCount, | |
IN EFI_HTTP_HEADER *Headers, | |
IN CHAR8 *FieldName | |
) | |
{ | |
UINTN Index; | |
if (HeaderCount == 0 || Headers == NULL || FieldName == NULL) { | |
return NULL; | |
} | |
for (Index = 0; Index < HeaderCount; Index++){ | |
// | |
// Field names are case-insensitive (RFC 2616). | |
// | |
if (AsciiStriCmp (Headers[Index].FieldName, FieldName) == 0) { | |
return &Headers[Index]; | |
} | |
} | |
return NULL; | |
} | |
typedef enum { | |
BodyParserBodyStart, | |
BodyParserBodyIdentity, | |
BodyParserChunkSizeStart, | |
BodyParserChunkSize, | |
BodyParserChunkSizeEndCR, | |
BodyParserChunkExtStart, | |
BodyParserChunkDataStart, | |
BodyParserChunkDataEnd, | |
BodyParserChunkDataEndCR, | |
BodyParserTrailer, | |
BodyParserLastCRLF, | |
BodyParserLastCRLFEnd, | |
BodyParserComplete, | |
BodyParserStateMax | |
} HTTP_BODY_PARSE_STATE; | |
typedef struct { | |
BOOLEAN IgnoreBody; // "MUST NOT" include a message-body | |
BOOLEAN IsChunked; // "chunked" transfer-coding. | |
BOOLEAN ContentLengthIsValid; | |
UINTN ContentLength; // Entity length (not the message-body length), invalid until ContentLengthIsValid is TRUE | |
HTTP_BODY_PARSER_CALLBACK Callback; | |
VOID *Context; | |
UINTN ParsedBodyLength; | |
HTTP_BODY_PARSE_STATE State; | |
UINTN CurrentChunkSize; | |
UINTN CurrentChunkParsedSize; | |
} HTTP_BODY_PARSER; | |
/** | |
Convert an hexadecimal char to a value of type UINTN. | |
@param[in] Char Ascii character. | |
@return Value translated from Char. | |
**/ | |
UINTN | |
HttpIoHexCharToUintn ( | |
IN CHAR8 Char | |
) | |
{ | |
if (Char >= '0' && Char <= '9') { | |
return Char - '0'; | |
} | |
return (10 + AsciiCharToUpper (Char) - 'A'); | |
} | |
/** | |
Get the value of the content length if there is a "Content-Length" header. | |
@param[in] HeaderCount Number of HTTP header structures in Headers. | |
@param[in] Headers Array containing list of HTTP headers. | |
@param[out] ContentLength Pointer to save the value of the content length. | |
@retval EFI_SUCCESS Successfully get the content length. | |
@retval EFI_NOT_FOUND No "Content-Length" header in the Headers. | |
**/ | |
EFI_STATUS | |
HttpIoParseContentLengthHeader ( | |
IN UINTN HeaderCount, | |
IN EFI_HTTP_HEADER *Headers, | |
OUT UINTN *ContentLength | |
) | |
{ | |
EFI_HTTP_HEADER *Header; | |
Header = HttpFindHeader (HeaderCount, Headers, HTTP_HEADER_CONTENT_LENGTH); | |
if (Header == NULL) { | |
return EFI_NOT_FOUND; | |
} | |
return AsciiStrDecimalToUintnS (Header->FieldValue, (CHAR8 **) NULL, ContentLength); | |
} | |
/** | |
Check whether the HTTP message is using the "chunked" transfer-coding. | |
@param[in] HeaderCount Number of HTTP header structures in Headers. | |
@param[in] Headers Array containing list of HTTP headers. | |
@return The message is "chunked" transfer-coding (TRUE) or not (FALSE). | |
**/ | |
BOOLEAN | |
HttpIoIsChunked ( | |
IN UINTN HeaderCount, | |
IN EFI_HTTP_HEADER *Headers | |
) | |
{ | |
EFI_HTTP_HEADER *Header; | |
Header = HttpFindHeader (HeaderCount, Headers, HTTP_HEADER_TRANSFER_ENCODING); | |
if (Header == NULL) { | |
return FALSE; | |
} | |
if (AsciiStriCmp (Header->FieldValue, "identity") != 0) { | |
return TRUE; | |
} | |
return FALSE; | |
} | |
/** | |
Check whether the HTTP message should have a message-body. | |
@param[in] Method The HTTP method (e.g. GET, POST) for this HTTP message. | |
@param[in] StatusCode Response status code returned by the remote host. | |
@return The message should have a message-body (FALSE) or not (TRUE). | |
**/ | |
BOOLEAN | |
HttpIoNoMessageBody ( | |
IN EFI_HTTP_METHOD Method, | |
IN EFI_HTTP_STATUS_CODE StatusCode | |
) | |
{ | |
// | |
// RFC 2616: | |
// All responses to the HEAD request method | |
// MUST NOT include a message-body, even though the presence of entity- | |
// header fields might lead one to believe they do. All 1xx | |
// (informational), 204 (no content), and 304 (not modified) responses | |
// MUST NOT include a message-body. All other responses do include a | |
// message-body, although it MAY be of zero length. | |
// | |
if (Method == HttpMethodHead) { | |
return TRUE; | |
} | |
if ((StatusCode == HTTP_STATUS_100_CONTINUE) || | |
(StatusCode == HTTP_STATUS_101_SWITCHING_PROTOCOLS) || | |
(StatusCode == HTTP_STATUS_204_NO_CONTENT) || | |
(StatusCode == HTTP_STATUS_304_NOT_MODIFIED)) | |
{ | |
return TRUE; | |
} | |
return FALSE; | |
} | |
/** | |
Initialize a HTTP message-body parser. | |
This function will create and initialize a HTTP message parser according to caller provided HTTP message | |
header information. It is the caller's responsibility to free the buffer returned in *UrlParser by HttpFreeMsgParser(). | |
@param[in] Method The HTTP method (e.g. GET, POST) for this HTTP message. | |
@param[in] StatusCode Response status code returned by the remote host. | |
@param[in] HeaderCount Number of HTTP header structures in Headers. | |
@param[in] Headers Array containing list of HTTP headers. | |
@param[in] Callback Callback function that is invoked when parsing the HTTP message-body, | |
set to NULL to ignore all events. | |
@param[in] Context Pointer to the context that will be passed to Callback. | |
@param[out] MsgParser Pointer to the returned buffer to store the message parser. | |
@retval EFI_SUCCESS Successfully initialized the parser. | |
@retval EFI_OUT_OF_RESOURCES Could not allocate needed resources. | |
@retval EFI_INVALID_PARAMETER MsgParser is NULL or HeaderCount is not NULL but Headers is NULL. | |
@retval Others Failed to initialize the parser. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
HttpInitMsgParser ( | |
IN EFI_HTTP_METHOD Method, | |
IN EFI_HTTP_STATUS_CODE StatusCode, | |
IN UINTN HeaderCount, | |
IN EFI_HTTP_HEADER *Headers, | |
IN HTTP_BODY_PARSER_CALLBACK Callback, | |
IN VOID *Context, | |
OUT VOID **MsgParser | |
) | |
{ | |
EFI_STATUS Status; | |
HTTP_BODY_PARSER *Parser; | |
if (HeaderCount != 0 && Headers == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if (MsgParser == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
Parser = AllocateZeroPool (sizeof (HTTP_BODY_PARSER)); | |
if (Parser == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Parser->State = BodyParserBodyStart; | |
// | |
// Determine the message length according to RFC 2616. | |
// 1. Check whether the message "MUST NOT" have a message-body. | |
// | |
Parser->IgnoreBody = HttpIoNoMessageBody (Method, StatusCode); | |
// | |
// 2. Check whether the message using "chunked" transfer-coding. | |
// | |
Parser->IsChunked = HttpIoIsChunked (HeaderCount, Headers); | |
// | |
// 3. Check whether the message has a Content-Length header field. | |
// | |
Status = HttpIoParseContentLengthHeader (HeaderCount, Headers, &Parser->ContentLength); | |
if (!EFI_ERROR (Status)) { | |
Parser->ContentLengthIsValid = TRUE; | |
} | |
// | |
// 4. Range header is not supported now, so we won't meet media type "multipart/byteranges". | |
// 5. By server closing the connection | |
// | |
// | |
// Set state to skip body parser if the message shouldn't have a message body. | |
// | |
if (Parser->IgnoreBody) { | |
Parser->State = BodyParserComplete; | |
} else { | |
Parser->Callback = Callback; | |
Parser->Context = Context; | |
} | |
*MsgParser = Parser; | |
return EFI_SUCCESS; | |
} | |
/** | |
Parse message body. | |
Parse BodyLength of message-body. This function can be called repeatedly to parse the message-body partially. | |
@param[in, out] MsgParser Pointer to the message parser. | |
@param[in] BodyLength Length in bytes of the Body. | |
@param[in] Body Pointer to the buffer of the message-body to be parsed. | |
@retval EFI_SUCCESS Successfully parse the message-body. | |
@retval EFI_INVALID_PARAMETER MsgParser is NULL or Body is NULL or BodyLength is 0. | |
@retval EFI_ABORTED Operation aborted. | |
@retval Other Error happened while parsing message body. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
HttpParseMessageBody ( | |
IN OUT VOID *MsgParser, | |
IN UINTN BodyLength, | |
IN CHAR8 *Body | |
) | |
{ | |
CHAR8 *Char; | |
UINTN RemainderLengthInThis; | |
UINTN LengthForCallback; | |
UINTN PortionLength; | |
EFI_STATUS Status; | |
HTTP_BODY_PARSER *Parser; | |
if (BodyLength == 0 || Body == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if (MsgParser == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
Parser = (HTTP_BODY_PARSER *) MsgParser; | |
if (Parser->IgnoreBody) { | |
Parser->State = BodyParserComplete; | |
if (Parser->Callback != NULL) { | |
Status = Parser->Callback ( | |
BodyParseEventOnComplete, | |
Body, | |
0, | |
Parser->Context | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
} | |
return EFI_SUCCESS; | |
} | |
if (Parser->State == BodyParserBodyStart) { | |
Parser->ParsedBodyLength = 0; | |
if (Parser->IsChunked) { | |
Parser->State = BodyParserChunkSizeStart; | |
} else { | |
Parser->State = BodyParserBodyIdentity; | |
} | |
} | |
// | |
// The message body might be truncated in anywhere, so we need to parse is byte-by-byte. | |
// | |
for (Char = Body; Char < Body + BodyLength; ) { | |
switch (Parser->State) { | |
case BodyParserStateMax: | |
return EFI_ABORTED; | |
case BodyParserBodyIdentity: | |
// | |
// Identity transfer-coding, just notify user to save the body data. | |
// | |
PortionLength = MIN ( | |
BodyLength, | |
Parser->ContentLength - Parser->ParsedBodyLength | |
); | |
if (PortionLength == 0) { | |
// | |
// Got BodyLength, but no ContentLength. Use BodyLength. | |
// | |
PortionLength = BodyLength; | |
Parser->ContentLength = PortionLength; | |
} | |
if (Parser->Callback != NULL) { | |
Status = Parser->Callback ( | |
BodyParseEventOnData, | |
Char, | |
PortionLength, | |
Parser->Context | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
} | |
Char += PortionLength; | |
Parser->ParsedBodyLength += PortionLength; | |
if (Parser->ParsedBodyLength == Parser->ContentLength) { | |
Parser->State = BodyParserComplete; | |
if (Parser->Callback != NULL) { | |
Status = Parser->Callback ( | |
BodyParseEventOnComplete, | |
Char, | |
0, | |
Parser->Context | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
} | |
} | |
break; | |
case BodyParserChunkSizeStart: | |
// | |
// First byte of chunk-size, the chunk-size might be truncated. | |
// | |
Parser->CurrentChunkSize = 0; | |
Parser->State = BodyParserChunkSize; | |
case BodyParserChunkSize: | |
if (!NET_IS_HEX_CHAR (*Char)) { | |
if (*Char == ';') { | |
Parser->State = BodyParserChunkExtStart; | |
Char++; | |
} else if (*Char == '\r') { | |
Parser->State = BodyParserChunkSizeEndCR; | |
Char++; | |
} else { | |
Parser->State = BodyParserStateMax; | |
} | |
break; | |
} | |
if (Parser->CurrentChunkSize > (((~((UINTN) 0)) - 16) / 16)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
Parser->CurrentChunkSize = Parser->CurrentChunkSize * 16 + HttpIoHexCharToUintn (*Char); | |
Char++; | |
break; | |
case BodyParserChunkExtStart: | |
// | |
// Ignore all the chunk extensions. | |
// | |
if (*Char == '\r') { | |
Parser->State = BodyParserChunkSizeEndCR; | |
} | |
Char++; | |
break; | |
case BodyParserChunkSizeEndCR: | |
if (*Char != '\n') { | |
Parser->State = BodyParserStateMax; | |
break; | |
} | |
Char++; | |
if (Parser->CurrentChunkSize == 0) { | |
// | |
// The last chunk has been parsed and now assumed the state | |
// of HttpBodyParse is ParserLastCRLF. So it need to decide | |
// whether the rest message is trailer or last CRLF in the next round. | |
// | |
Parser->ContentLengthIsValid = TRUE; | |
Parser->State = BodyParserLastCRLF; | |
break; | |
} | |
Parser->State = BodyParserChunkDataStart; | |
Parser->CurrentChunkParsedSize = 0; | |
break; | |
case BodyParserLastCRLF: | |
// | |
// Judge the byte is belong to the Last CRLF or trailer, and then | |
// configure the state of HttpBodyParse to corresponding state. | |
// | |
if (*Char == '\r') { | |
Char++; | |
Parser->State = BodyParserLastCRLFEnd; | |
break; | |
} else { | |
Parser->State = BodyParserTrailer; | |
break; | |
} | |
case BodyParserLastCRLFEnd: | |
if (*Char == '\n') { | |
Parser->State = BodyParserComplete; | |
Char++; | |
if (Parser->Callback != NULL) { | |
Status = Parser->Callback ( | |
BodyParseEventOnComplete, | |
Char, | |
0, | |
Parser->Context | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
} | |
break; | |
} else { | |
Parser->State = BodyParserStateMax; | |
break; | |
} | |
case BodyParserTrailer: | |
if (*Char == '\r') { | |
Parser->State = BodyParserChunkSizeEndCR; | |
} | |
Char++; | |
break; | |
case BodyParserChunkDataStart: | |
// | |
// First byte of chunk-data, the chunk data also might be truncated. | |
// | |
RemainderLengthInThis = BodyLength - (Char - Body); | |
LengthForCallback = MIN (Parser->CurrentChunkSize - Parser->CurrentChunkParsedSize, RemainderLengthInThis); | |
if (Parser->Callback != NULL) { | |
Status = Parser->Callback ( | |
BodyParseEventOnData, | |
Char, | |
LengthForCallback, | |
Parser->Context | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
} | |
Char += LengthForCallback; | |
Parser->ContentLength += LengthForCallback; | |
Parser->CurrentChunkParsedSize += LengthForCallback; | |
if (Parser->CurrentChunkParsedSize == Parser->CurrentChunkSize) { | |
Parser->State = BodyParserChunkDataEnd; | |
} | |
break; | |
case BodyParserChunkDataEnd: | |
if (*Char == '\r') { | |
Parser->State = BodyParserChunkDataEndCR; | |
} else { | |
Parser->State = BodyParserStateMax; | |
} | |
Char++; | |
break; | |
case BodyParserChunkDataEndCR: | |
if (*Char != '\n') { | |
Parser->State = BodyParserStateMax; | |
break; | |
} | |
Char++; | |
Parser->State = BodyParserChunkSizeStart; | |
break; | |
default: | |
break; | |
} | |
} | |
if (Parser->State == BodyParserStateMax) { | |
return EFI_ABORTED; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Check whether the message-body is complete or not. | |
@param[in] MsgParser Pointer to the message parser. | |
@retval TRUE Message-body is complete. | |
@retval FALSE Message-body is not complete. | |
**/ | |
BOOLEAN | |
EFIAPI | |
HttpIsMessageComplete ( | |
IN VOID *MsgParser | |
) | |
{ | |
HTTP_BODY_PARSER *Parser; | |
if (MsgParser == NULL) { | |
return FALSE; | |
} | |
Parser = (HTTP_BODY_PARSER *) MsgParser; | |
if (Parser->State == BodyParserComplete) { | |
return TRUE; | |
} | |
return FALSE; | |
} | |
/** | |
Get the content length of the entity. | |
Note that in trunk transfer, the entity length is not valid until the whole message body is received. | |
@param[in] MsgParser Pointer to the message parser. | |
@param[out] ContentLength Pointer to store the length of the entity. | |
@retval EFI_SUCCESS Successfully to get the entity length. | |
@retval EFI_NOT_READY Entity length is not valid yet. | |
@retval EFI_INVALID_PARAMETER MsgParser is NULL or ContentLength is NULL. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
HttpGetEntityLength ( | |
IN VOID *MsgParser, | |
OUT UINTN *ContentLength | |
) | |
{ | |
HTTP_BODY_PARSER *Parser; | |
if (MsgParser == NULL || ContentLength == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
Parser = (HTTP_BODY_PARSER *) MsgParser; | |
if (!Parser->ContentLengthIsValid) { | |
return EFI_NOT_READY; | |
} | |
*ContentLength = Parser->ContentLength; | |
return EFI_SUCCESS; | |
} | |
/** | |
Release the resource of the message parser. | |
@param[in] MsgParser Pointer to the message parser. | |
**/ | |
VOID | |
EFIAPI | |
HttpFreeMsgParser ( | |
IN VOID *MsgParser | |
) | |
{ | |
FreePool (MsgParser); | |
} | |
/** | |
Get the next string, which is distinguished by specified separator. | |
@param[in] String Pointer to the string. | |
@param[in] Separator Specified separator used to distinguish where is the beginning | |
of next string. | |
@return Pointer to the next string. | |
@return NULL if not find or String is NULL. | |
**/ | |
CHAR8 * | |
AsciiStrGetNextToken ( | |
IN CONST CHAR8 *String, | |
IN CHAR8 Separator | |
) | |
{ | |
CONST CHAR8 *Token; | |
Token = String; | |
while (TRUE) { | |
if (*Token == 0) { | |
return NULL; | |
} | |
if (*Token == Separator) { | |
return (CHAR8 *)(Token + 1); | |
} | |
Token++; | |
} | |
} | |
/** | |
Set FieldName and FieldValue into specified HttpHeader. | |
@param[in,out] HttpHeader Specified HttpHeader. | |
@param[in] FieldName FieldName of this HttpHeader, a NULL terminated ASCII string. | |
@param[in] FieldValue FieldValue of this HttpHeader, a NULL terminated ASCII string. | |
@retval EFI_SUCCESS The FieldName and FieldValue are set into HttpHeader successfully. | |
@retval EFI_INVALID_PARAMETER The parameter is invalid. | |
@retval EFI_OUT_OF_RESOURCES Failed to allocate resources. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
HttpSetFieldNameAndValue ( | |
IN OUT EFI_HTTP_HEADER *HttpHeader, | |
IN CONST CHAR8 *FieldName, | |
IN CONST CHAR8 *FieldValue | |
) | |
{ | |
UINTN FieldNameSize; | |
UINTN FieldValueSize; | |
if (HttpHeader == NULL || FieldName == NULL || FieldValue == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if (HttpHeader->FieldName != NULL) { | |
FreePool (HttpHeader->FieldName); | |
} | |
if (HttpHeader->FieldValue != NULL) { | |
FreePool (HttpHeader->FieldValue); | |
} | |
FieldNameSize = AsciiStrSize (FieldName); | |
HttpHeader->FieldName = AllocateZeroPool (FieldNameSize); | |
if (HttpHeader->FieldName == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
CopyMem (HttpHeader->FieldName, FieldName, FieldNameSize); | |
HttpHeader->FieldName[FieldNameSize - 1] = 0; | |
FieldValueSize = AsciiStrSize (FieldValue); | |
HttpHeader->FieldValue = AllocateZeroPool (FieldValueSize); | |
if (HttpHeader->FieldValue == NULL) { | |
FreePool (HttpHeader->FieldName); | |
return EFI_OUT_OF_RESOURCES; | |
} | |
CopyMem (HttpHeader->FieldValue, FieldValue, FieldValueSize); | |
HttpHeader->FieldValue[FieldValueSize - 1] = 0; | |
return EFI_SUCCESS; | |
} | |
/** | |
Get one key/value header pair from the raw string. | |
@param[in] String Pointer to the raw string. | |
@param[out] FieldName Points directly to field name within 'HttpHeader'. | |
@param[out] FieldValue Points directly to field value within 'HttpHeader'. | |
@return Pointer to the next raw string. | |
@return NULL if no key/value header pair from this raw string. | |
**/ | |
CHAR8 * | |
EFIAPI | |
HttpGetFieldNameAndValue ( | |
IN CHAR8 *String, | |
OUT CHAR8 **FieldName, | |
OUT CHAR8 **FieldValue | |
) | |
{ | |
CHAR8 *FieldNameStr; | |
CHAR8 *FieldValueStr; | |
CHAR8 *StrPtr; | |
CHAR8 *EndofHeader; | |
if (String == NULL || FieldName == NULL || FieldValue == NULL) { | |
return NULL; | |
} | |
*FieldName = NULL; | |
*FieldValue = NULL; | |
FieldNameStr = NULL; | |
FieldValueStr = NULL; | |
StrPtr = NULL; | |
EndofHeader = NULL; | |
// | |
// Check whether the raw HTTP header string is valid or not. | |
// | |
EndofHeader = AsciiStrStr (String, "\r\n\r\n"); | |
if (EndofHeader == NULL) { | |
return NULL; | |
} | |
// | |
// Each header field consists of a name followed by a colon (":") and the field value. | |
// The field value MAY be preceded by any amount of LWS, though a single SP is preferred. | |
// | |
// message-header = field-name ":" [ field-value ] | |
// field-name = token | |
// field-value = *( field-content | LWS ) | |
// | |
// Note: "*(element)" allows any number element, including zero; "1*(element)" requires at least one element. | |
// [element] means element is optional. | |
// LWS = [CRLF] 1*(SP|HT), it can be ' ' or '\t' or '\r\n ' or '\r\n\t'. | |
// CRLF = '\r\n'. | |
// SP = ' '. | |
// HT = '\t' (Tab). | |
// | |
FieldNameStr = String; | |
FieldValueStr = AsciiStrGetNextToken (FieldNameStr, ':'); | |
if (FieldValueStr == NULL) { | |
return NULL; | |
} | |
// | |
// Replace ':' with 0, then FieldName has been retrived from String. | |
// | |
*(FieldValueStr - 1) = 0; | |
// | |
// Handle FieldValueStr, skip all the preceded LWS. | |
// | |
while (TRUE) { | |
if (*FieldValueStr == ' ' || *FieldValueStr == '\t') { | |
// | |
// Boundary condition check. | |
// | |
if ((UINTN) EndofHeader - (UINTN) FieldValueStr < 1) { | |
// | |
// Wrong String format! | |
// | |
return NULL; | |
} | |
FieldValueStr ++; | |
} else if (*FieldValueStr == '\r') { | |
// | |
// Boundary condition check. | |
// | |
if ((UINTN) EndofHeader - (UINTN) FieldValueStr < 3) { | |
// | |
// No more preceded LWS, so break here. | |
// | |
break; | |
} | |
if (*(FieldValueStr + 1) == '\n' ) { | |
if (*(FieldValueStr + 2) == ' ' || *(FieldValueStr + 2) == '\t') { | |
FieldValueStr = FieldValueStr + 3; | |
} else { | |
// | |
// No more preceded LWS, so break here. | |
// | |
break; | |
} | |
} else { | |
// | |
// Wrong String format! | |
// | |
return NULL; | |
} | |
} else { | |
// | |
// No more preceded LWS, so break here. | |
// | |
break; | |
} | |
} | |
StrPtr = FieldValueStr; | |
do { | |
// | |
// Handle the LWS within the field value. | |
// | |
StrPtr = AsciiStrGetNextToken (StrPtr, '\r'); | |
if (StrPtr == NULL || *StrPtr != '\n') { | |
// | |
// Wrong String format! | |
// | |
return NULL; | |
} | |
StrPtr++; | |
} while (*StrPtr == ' ' || *StrPtr == '\t'); | |
// | |
// Replace '\r' with 0 | |
// | |
*(StrPtr - 2) = 0; | |
// | |
// Get FieldName and FieldValue. | |
// | |
*FieldName = FieldNameStr; | |
*FieldValue = FieldValueStr; | |
return StrPtr; | |
} | |
/** | |
Free existing HeaderFields. | |
@param[in] HeaderFields Pointer to array of key/value header pairs waiting for free. | |
@param[in] FieldCount The number of header pairs in HeaderFields. | |
**/ | |
VOID | |
EFIAPI | |
HttpFreeHeaderFields ( | |
IN EFI_HTTP_HEADER *HeaderFields, | |
IN UINTN FieldCount | |
) | |
{ | |
UINTN Index; | |
if (HeaderFields != NULL) { | |
for (Index = 0; Index < FieldCount; Index++) { | |
if (HeaderFields[Index].FieldName != NULL) { | |
FreePool (HeaderFields[Index].FieldName); | |
} | |
if (HeaderFields[Index].FieldValue != NULL) { | |
FreePool (HeaderFields[Index].FieldValue); | |
} | |
} | |
FreePool (HeaderFields); | |
} | |
} | |
/** | |
Generate HTTP request message. | |
This function will allocate memory for the whole HTTP message and generate a | |
well formatted HTTP Request message in it, include the Request-Line, header | |
fields and also the message body. It is the caller's responsibility to free | |
the buffer returned in *RequestMsg. | |
@param[in] Message Pointer to the EFI_HTTP_MESSAGE structure which | |
contains the required information to generate | |
the HTTP request message. | |
@param[in] Url The URL of a remote host. | |
@param[out] RequestMsg Pointer to the created HTTP request message. | |
NULL if any error occurred. | |
@param[out] RequestMsgSize Size of the RequestMsg (in bytes). | |
@retval EFI_SUCCESS If HTTP request string was created successfully. | |
@retval EFI_OUT_OF_RESOURCES Failed to allocate resources. | |
@retval EFI_INVALID_PARAMETER The input arguments are invalid. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
HttpGenRequestMessage ( | |
IN CONST EFI_HTTP_MESSAGE *Message, | |
IN CONST CHAR8 *Url, | |
OUT CHAR8 **RequestMsg, | |
OUT UINTN *RequestMsgSize | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN StrLength; | |
CHAR8 *RequestPtr; | |
UINTN HttpHdrSize; | |
UINTN MsgSize; | |
BOOLEAN Success; | |
VOID *HttpHdr; | |
EFI_HTTP_HEADER **AppendList; | |
UINTN Index; | |
EFI_HTTP_UTILITIES_PROTOCOL *HttpUtilitiesProtocol; | |
Status = EFI_SUCCESS; | |
HttpHdrSize = 0; | |
MsgSize = 0; | |
Success = FALSE; | |
HttpHdr = NULL; | |
AppendList = NULL; | |
HttpUtilitiesProtocol = NULL; | |
// | |
// 1. If we have a Request, we cannot have a NULL Url | |
// 2. If we have a Request, HeaderCount can not be non-zero | |
// 3. If we do not have a Request, HeaderCount should be zero | |
// 4. If we do not have Request and Headers, we need at least a message-body | |
// | |
if ((Message == NULL || RequestMsg == NULL || RequestMsgSize == NULL) || | |
(Message->Data.Request != NULL && Url == NULL) || | |
(Message->Data.Request != NULL && Message->HeaderCount == 0) || | |
(Message->Data.Request == NULL && Message->HeaderCount != 0) || | |
(Message->Data.Request == NULL && Message->HeaderCount == 0 && Message->BodyLength == 0)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if (Message->HeaderCount != 0) { | |
// | |
// Locate the HTTP_UTILITIES protocol. | |
// | |
Status = gBS->LocateProtocol ( | |
&gEfiHttpUtilitiesProtocolGuid, | |
NULL, | |
(VOID **) &HttpUtilitiesProtocol | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR,"Failed to locate Http Utilities protocol. Status = %r.\n", Status)); | |
return Status; | |
} | |
// | |
// Build AppendList to send into HttpUtilitiesBuild | |
// | |
AppendList = AllocateZeroPool (sizeof (EFI_HTTP_HEADER *) * (Message->HeaderCount)); | |
if (AppendList == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
for(Index = 0; Index < Message->HeaderCount; Index++){ | |
AppendList[Index] = &Message->Headers[Index]; | |
} | |
// | |
// Build raw HTTP Headers | |
// | |
Status = HttpUtilitiesProtocol->Build ( | |
HttpUtilitiesProtocol, | |
0, | |
NULL, | |
0, | |
NULL, | |
Message->HeaderCount, | |
AppendList, | |
&HttpHdrSize, | |
&HttpHdr | |
); | |
FreePool (AppendList); | |
if (EFI_ERROR (Status) || HttpHdr == NULL){ | |
return Status; | |
} | |
} | |
// | |
// If we have headers to be sent, account for it. | |
// | |
if (Message->HeaderCount != 0) { | |
MsgSize = HttpHdrSize; | |
} | |
// | |
// If we have a request line, account for the fields. | |
// | |
if (Message->Data.Request != NULL) { | |
MsgSize += HTTP_METHOD_MAXIMUM_LEN + AsciiStrLen (HTTP_VERSION_CRLF_STR) + AsciiStrLen (Url); | |
} | |
// | |
// If we have a message body to be sent, account for it. | |
// | |
MsgSize += Message->BodyLength; | |
// | |
// memory for the string that needs to be sent to TCP | |
// | |
*RequestMsg = NULL; | |
*RequestMsg = AllocateZeroPool (MsgSize); | |
if (*RequestMsg == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto Exit; | |
} | |
RequestPtr = *RequestMsg; | |
// | |
// Construct header request | |
// | |
if (Message->Data.Request != NULL) { | |
switch (Message->Data.Request->Method) { | |
case HttpMethodGet: | |
StrLength = sizeof (HTTP_METHOD_GET) - 1; | |
CopyMem (RequestPtr, HTTP_METHOD_GET, StrLength); | |
RequestPtr += StrLength; | |
break; | |
case HttpMethodPut: | |
StrLength = sizeof (HTTP_METHOD_PUT) - 1; | |
CopyMem (RequestPtr, HTTP_METHOD_PUT, StrLength); | |
RequestPtr += StrLength; | |
break; | |
case HttpMethodPatch: | |
StrLength = sizeof (HTTP_METHOD_PATCH) - 1; | |
CopyMem (RequestPtr, HTTP_METHOD_PATCH, StrLength); | |
RequestPtr += StrLength; | |
break; | |
case HttpMethodPost: | |
StrLength = sizeof (HTTP_METHOD_POST) - 1; | |
CopyMem (RequestPtr, HTTP_METHOD_POST, StrLength); | |
RequestPtr += StrLength; | |
break; | |
case HttpMethodHead: | |
StrLength = sizeof (HTTP_METHOD_HEAD) - 1; | |
CopyMem (RequestPtr, HTTP_METHOD_HEAD, StrLength); | |
RequestPtr += StrLength; | |
break; | |
case HttpMethodDelete: | |
StrLength = sizeof (HTTP_METHOD_DELETE) - 1; | |
CopyMem (RequestPtr, HTTP_METHOD_DELETE, StrLength); | |
RequestPtr += StrLength; | |
break; | |
default: | |
ASSERT (FALSE); | |
Status = EFI_INVALID_PARAMETER; | |
goto Exit; | |
} | |
StrLength = AsciiStrLen(EMPTY_SPACE); | |
CopyMem (RequestPtr, EMPTY_SPACE, StrLength); | |
RequestPtr += StrLength; | |
StrLength = AsciiStrLen (Url); | |
CopyMem (RequestPtr, Url, StrLength); | |
RequestPtr += StrLength; | |
StrLength = sizeof (HTTP_VERSION_CRLF_STR) - 1; | |
CopyMem (RequestPtr, HTTP_VERSION_CRLF_STR, StrLength); | |
RequestPtr += StrLength; | |
if (HttpHdr != NULL) { | |
// | |
// Construct header | |
// | |
CopyMem (RequestPtr, HttpHdr, HttpHdrSize); | |
RequestPtr += HttpHdrSize; | |
} | |
} | |
// | |
// Construct body | |
// | |
if (Message->Body != NULL) { | |
CopyMem (RequestPtr, Message->Body, Message->BodyLength); | |
RequestPtr += Message->BodyLength; | |
} | |
// | |
// Done | |
// | |
(*RequestMsgSize) = (UINTN)(RequestPtr) - (UINTN)(*RequestMsg); | |
Success = TRUE; | |
Exit: | |
if (!Success) { | |
if (*RequestMsg != NULL) { | |
FreePool (*RequestMsg); | |
} | |
*RequestMsg = NULL; | |
return Status; | |
} | |
if (HttpHdr != NULL) { | |
FreePool (HttpHdr); | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Translate the status code in HTTP message to EFI_HTTP_STATUS_CODE defined | |
in UEFI 2.5 specification. | |
@param[in] StatusCode The status code value in HTTP message. | |
@return Value defined in EFI_HTTP_STATUS_CODE . | |
**/ | |
EFI_HTTP_STATUS_CODE | |
EFIAPI | |
HttpMappingToStatusCode ( | |
IN UINTN StatusCode | |
) | |
{ | |
switch (StatusCode) { | |
case 100: | |
return HTTP_STATUS_100_CONTINUE; | |
case 101: | |
return HTTP_STATUS_101_SWITCHING_PROTOCOLS; | |
case 200: | |
return HTTP_STATUS_200_OK; | |
case 201: | |
return HTTP_STATUS_201_CREATED; | |
case 202: | |
return HTTP_STATUS_202_ACCEPTED; | |
case 203: | |
return HTTP_STATUS_203_NON_AUTHORITATIVE_INFORMATION; | |
case 204: | |
return HTTP_STATUS_204_NO_CONTENT; | |
case 205: | |
return HTTP_STATUS_205_RESET_CONTENT; | |
case 206: | |
return HTTP_STATUS_206_PARTIAL_CONTENT; | |
case 300: | |
return HTTP_STATUS_300_MULTIPLE_CHOICES; | |
case 301: | |
return HTTP_STATUS_301_MOVED_PERMANENTLY; | |
case 302: | |
return HTTP_STATUS_302_FOUND; | |
case 303: | |
return HTTP_STATUS_303_SEE_OTHER; | |
case 304: | |
return HTTP_STATUS_304_NOT_MODIFIED; | |
case 305: | |
return HTTP_STATUS_305_USE_PROXY; | |
case 307: | |
return HTTP_STATUS_307_TEMPORARY_REDIRECT; | |
case 308: | |
return HTTP_STATUS_308_PERMANENT_REDIRECT; | |
case 400: | |
return HTTP_STATUS_400_BAD_REQUEST; | |
case 401: | |
return HTTP_STATUS_401_UNAUTHORIZED; | |
case 402: | |
return HTTP_STATUS_402_PAYMENT_REQUIRED; | |
case 403: | |
return HTTP_STATUS_403_FORBIDDEN; | |
case 404: | |
return HTTP_STATUS_404_NOT_FOUND; | |
case 405: | |
return HTTP_STATUS_405_METHOD_NOT_ALLOWED; | |
case 406: | |
return HTTP_STATUS_406_NOT_ACCEPTABLE; | |
case 407: | |
return HTTP_STATUS_407_PROXY_AUTHENTICATION_REQUIRED; | |
case 408: | |
return HTTP_STATUS_408_REQUEST_TIME_OUT; | |
case 409: | |
return HTTP_STATUS_409_CONFLICT; | |
case 410: | |
return HTTP_STATUS_410_GONE; | |
case 411: | |
return HTTP_STATUS_411_LENGTH_REQUIRED; | |
case 412: | |
return HTTP_STATUS_412_PRECONDITION_FAILED; | |
case 413: | |
return HTTP_STATUS_413_REQUEST_ENTITY_TOO_LARGE; | |
case 414: | |
return HTTP_STATUS_414_REQUEST_URI_TOO_LARGE; | |
case 415: | |
return HTTP_STATUS_415_UNSUPPORTED_MEDIA_TYPE; | |
case 416: | |
return HTTP_STATUS_416_REQUESTED_RANGE_NOT_SATISFIED; | |
case 417: | |
return HTTP_STATUS_417_EXPECTATION_FAILED; | |
case 500: | |
return HTTP_STATUS_500_INTERNAL_SERVER_ERROR; | |
case 501: | |
return HTTP_STATUS_501_NOT_IMPLEMENTED; | |
case 502: | |
return HTTP_STATUS_502_BAD_GATEWAY; | |
case 503: | |
return HTTP_STATUS_503_SERVICE_UNAVAILABLE; | |
case 504: | |
return HTTP_STATUS_504_GATEWAY_TIME_OUT; | |
case 505: | |
return HTTP_STATUS_505_HTTP_VERSION_NOT_SUPPORTED; | |
default: | |
return HTTP_STATUS_UNSUPPORTED_STATUS; | |
} | |
} | |
/** | |
Check whether header field called FieldName is in DeleteList. | |
@param[in] DeleteList Pointer to array of key/value header pairs. | |
@param[in] DeleteCount The number of header pairs. | |
@param[in] FieldName Pointer to header field's name. | |
@return TRUE if FieldName is not in DeleteList, that means this header field is valid. | |
@return FALSE if FieldName is in DeleteList, that means this header field is invalid. | |
**/ | |
BOOLEAN | |
EFIAPI | |
HttpIsValidHttpHeader ( | |
IN CHAR8 *DeleteList[], | |
IN UINTN DeleteCount, | |
IN CHAR8 *FieldName | |
) | |
{ | |
UINTN Index; | |
if (FieldName == NULL) { | |
return FALSE; | |
} | |
for (Index = 0; Index < DeleteCount; Index++) { | |
if (DeleteList[Index] == NULL) { | |
continue; | |
} | |
if (AsciiStrCmp (FieldName, DeleteList[Index]) == 0) { | |
return FALSE; | |
} | |
} | |
return TRUE; | |
} | |
/** | |
Create a HTTP_IO_HEADER to hold the HTTP header items. | |
@param[in] MaxHeaderCount The maximun number of HTTP header in this holder. | |
@return A pointer of the HTTP header holder or NULL if failed. | |
**/ | |
HTTP_IO_HEADER * | |
HttpIoCreateHeader ( | |
UINTN MaxHeaderCount | |
) | |
{ | |
HTTP_IO_HEADER *HttpIoHeader; | |
if (MaxHeaderCount == 0) { | |
return NULL; | |
} | |
HttpIoHeader = AllocateZeroPool (sizeof (HTTP_IO_HEADER) + MaxHeaderCount * sizeof (EFI_HTTP_HEADER)); | |
if (HttpIoHeader == NULL) { | |
return NULL; | |
} | |
HttpIoHeader->MaxHeaderCount = MaxHeaderCount; | |
HttpIoHeader->Headers = (EFI_HTTP_HEADER *) (HttpIoHeader + 1); | |
return HttpIoHeader; | |
} | |
/** | |
Destroy the HTTP_IO_HEADER and release the resources. | |
@param[in] HttpIoHeader Point to the HTTP header holder to be destroyed. | |
**/ | |
VOID | |
HttpIoFreeHeader ( | |
IN HTTP_IO_HEADER *HttpIoHeader | |
) | |
{ | |
UINTN Index; | |
if (HttpIoHeader != NULL) { | |
if (HttpIoHeader->HeaderCount != 0) { | |
for (Index = 0; Index < HttpIoHeader->HeaderCount; Index++) { | |
FreePool (HttpIoHeader->Headers[Index].FieldName); | |
ZeroMem (HttpIoHeader->Headers[Index].FieldValue, AsciiStrSize (HttpIoHeader->Headers[Index].FieldValue)); | |
FreePool (HttpIoHeader->Headers[Index].FieldValue); | |
} | |
} | |
FreePool (HttpIoHeader); | |
} | |
} | |
/** | |
Set or update a HTTP header with the field name and corresponding value. | |
@param[in] HttpIoHeader Point to the HTTP header holder. | |
@param[in] FieldName Null terminated string which describes a field name. | |
@param[in] FieldValue Null terminated string which describes the corresponding field value. | |
@retval EFI_SUCCESS The HTTP header has been set or updated. | |
@retval EFI_INVALID_PARAMETER Any input parameter is invalid. | |
@retval EFI_OUT_OF_RESOURCES Insufficient resource to complete the operation. | |
@retval Other Unexpected error happened. | |
**/ | |
EFI_STATUS | |
HttpIoSetHeader ( | |
IN HTTP_IO_HEADER *HttpIoHeader, | |
IN CHAR8 *FieldName, | |
IN CHAR8 *FieldValue | |
) | |
{ | |
EFI_HTTP_HEADER *Header; | |
UINTN StrSize; | |
CHAR8 *NewFieldValue; | |
if (HttpIoHeader == NULL || FieldName == NULL || FieldValue == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
Header = HttpFindHeader (HttpIoHeader->HeaderCount, HttpIoHeader->Headers, FieldName); | |
if (Header == NULL) { | |
// | |
// Add a new header. | |
// | |
if (HttpIoHeader->HeaderCount >= HttpIoHeader->MaxHeaderCount) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Header = &HttpIoHeader->Headers[HttpIoHeader->HeaderCount]; | |
StrSize = AsciiStrSize (FieldName); | |
Header->FieldName = AllocatePool (StrSize); | |
if (Header->FieldName == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
CopyMem (Header->FieldName, FieldName, StrSize); | |
Header->FieldName[StrSize -1] = '\0'; | |
StrSize = AsciiStrSize (FieldValue); | |
Header->FieldValue = AllocatePool (StrSize); | |
if (Header->FieldValue == NULL) { | |
FreePool (Header->FieldName); | |
return EFI_OUT_OF_RESOURCES; | |
} | |
CopyMem (Header->FieldValue, FieldValue, StrSize); | |
Header->FieldValue[StrSize -1] = '\0'; | |
HttpIoHeader->HeaderCount++; | |
} else { | |
// | |
// Update an existing one. | |
// | |
StrSize = AsciiStrSize (FieldValue); | |
NewFieldValue = AllocatePool (StrSize); | |
if (NewFieldValue == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
CopyMem (NewFieldValue, FieldValue, StrSize); | |
NewFieldValue[StrSize -1] = '\0'; | |
if (Header->FieldValue != NULL) { | |
FreePool (Header->FieldValue); | |
} | |
Header->FieldValue = NewFieldValue; | |
} | |
return EFI_SUCCESS; | |
} |