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