NetworkPkg:HttpDxe: Code changes to support HTTP PUT/POST operations

Code changes enables HttpDxe to handle PUT/POST operations.
EfiHttpRequest assumes "Request" and "HttpMsg->Headers" can
never be NULL. Also, HttpResponseWorker assumes HTTP Reponse
will contain headers. We could have response which could contain
only a string (HTTP 100 Continue) and no headers. Code changes
tries to do-away from these assumptions, which would enable
HttpDxe to support PUT/POST operations.

Contributed-under: TianoCore Contribution Agreement 1.0
Signed-off-by: Hegde, Nagaraj P nagaraj-p.hegde@hpe.com
Reviewed-By: Wu Jiaxin <jiaxin.wu@intel.com>
Reviewed-by: Fu Siyuan <siyuan.fu@intel.com>
This commit is contained in:
Nagaraj Hegde 2016-05-06 18:20:00 +08:00 committed by Fu Siyuan
parent bfba88bc68
commit d8293d3141
3 changed files with 263 additions and 184 deletions

View File

@ -2,6 +2,7 @@
The driver binding and service binding protocol for HttpDxe driver. The driver binding and service binding protocol for HttpDxe driver.
Copyright (c) 2015, Intel Corporation. All rights reserved.<BR> Copyright (c) 2015, Intel Corporation. All rights reserved.<BR>
(C) Copyright 2016 Hewlett Packard Enterprise Development LP<BR>
This program and the accompanying materials This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License are licensed and made available under the terms and conditions of the BSD License
@ -939,6 +940,8 @@ HttpServiceBindingCreateChild (
HttpInstance->Signature = HTTP_PROTOCOL_SIGNATURE; HttpInstance->Signature = HTTP_PROTOCOL_SIGNATURE;
HttpInstance->Service = HttpService; HttpInstance->Service = HttpService;
HttpInstance->Method = HttpMethodMax;
CopyMem (&HttpInstance->Http, &mEfiHttpTemplate, sizeof (HttpInstance->Http)); CopyMem (&HttpInstance->Http, &mEfiHttpTemplate, sizeof (HttpInstance->Http));
NetMapInit (&HttpInstance->TxTokens); NetMapInit (&HttpInstance->TxTokens);
NetMapInit (&HttpInstance->RxTokens); NetMapInit (&HttpInstance->RxTokens);

View File

@ -249,150 +249,184 @@ EfiHttpRequest (
CHAR8 *FileUrl; CHAR8 *FileUrl;
UINTN RequestMsgSize; UINTN RequestMsgSize;
//
// Initializations
//
Url = NULL;
HostName = NULL;
RequestMsg = NULL;
HostNameStr = NULL;
Wrap = NULL;
FileUrl = NULL;
if ((This == NULL) || (Token == NULL)) { if ((This == NULL) || (Token == NULL)) {
return EFI_INVALID_PARAMETER; return EFI_INVALID_PARAMETER;
} }
HttpMsg = Token->Message; HttpMsg = Token->Message;
if ((HttpMsg == NULL) || (HttpMsg->Headers == NULL)) { if (HttpMsg == NULL) {
return EFI_INVALID_PARAMETER; return EFI_INVALID_PARAMETER;
} }
//
// Current implementation does not support POST/PUT method.
// If future version supports these two methods, Request could be NULL for a special case that to send large amounts
// of data. For this case, the implementation need check whether previous call to Request() has been completed or not.
//
//
Request = HttpMsg->Data.Request; Request = HttpMsg->Data.Request;
if ((Request == NULL) || (Request->Url == NULL)) {
return EFI_INVALID_PARAMETER;
}
// //
// Only support GET and HEAD method in current implementation. // Only support GET, HEAD, PUT and POST method in current implementation.
// //
if ((Request->Method != HttpMethodGet) && (Request->Method != HttpMethodHead)) { if ((Request != NULL) && (Request->Method != HttpMethodGet) &&
(Request->Method != HttpMethodHead) && (Request->Method != HttpMethodPut) && (Request->Method != HttpMethodPost)) {
return EFI_UNSUPPORTED; return EFI_UNSUPPORTED;
} }
HttpInstance = HTTP_INSTANCE_FROM_PROTOCOL (This); HttpInstance = HTTP_INSTANCE_FROM_PROTOCOL (This);
ASSERT (HttpInstance != NULL); ASSERT (HttpInstance != NULL);
//
// Capture the method into HttpInstance.
//
if (Request != NULL) {
HttpInstance->Method = Request->Method;
}
if (HttpInstance->State < HTTP_STATE_HTTP_CONFIGED) { if (HttpInstance->State < HTTP_STATE_HTTP_CONFIGED) {
return EFI_NOT_STARTED; return EFI_NOT_STARTED;
} }
// if (Request == NULL) {
// Check whether the token already existed. //
// // Request would be NULL only for PUT/POST operation (in the current implementation)
if (EFI_ERROR (NetMapIterate (&HttpInstance->TxTokens, HttpTokenExist, Token))) { //
return EFI_ACCESS_DENIED; if ((HttpInstance->Method != HttpMethodPut) && (HttpInstance->Method != HttpMethodPost)) {
} return EFI_INVALID_PARAMETER;
HostName = NULL;
Wrap = NULL;
HostNameStr = NULL;
//
// Parse the URI of the remote host.
//
Url = HttpInstance->Url;
UrlLen = StrLen (Request->Url) + 1;
if (UrlLen > HTTP_URL_BUFFER_LEN) {
Url = AllocateZeroPool (UrlLen);
if (Url == NULL) {
return EFI_OUT_OF_RESOURCES;
} }
FreePool (HttpInstance->Url);
HttpInstance->Url = Url;
}
UnicodeStrToAsciiStr (Request->Url, Url);
UrlParser = NULL;
Status = HttpParseUrl (Url, (UINT32) AsciiStrLen (Url), FALSE, &UrlParser);
if (EFI_ERROR (Status)) {
goto Error1;
}
RequestMsg = NULL;
HostName = NULL;
Status = HttpUrlGetHostName (Url, UrlParser, &HostName);
if (EFI_ERROR (Status)) {
goto Error1;
}
Status = HttpUrlGetPort (Url, UrlParser, &RemotePort);
if (EFI_ERROR (Status)) {
RemotePort = HTTP_DEFAULT_PORT;
}
//
// If Configure is TRUE, it indicates the first time to call Request();
// If ReConfigure is TRUE, it indicates the request URL is not same
// with the previous call to Request();
//
Configure = TRUE;
ReConfigure = TRUE;
if (HttpInstance->RemoteHost == NULL) {
// //
// Request() is called the first time. // For PUT/POST, we need to have the TCP already configured. Bail out if it is not!
// //
if (HttpInstance->State < HTTP_STATE_TCP_CONFIGED) {
return EFI_INVALID_PARAMETER;
}
//
// We need to have the Message Body for sending the HTTP message across in these cases.
//
if (HttpMsg->Body == NULL || HttpMsg->BodyLength == 0) {
return EFI_INVALID_PARAMETER;
}
//
// Use existing TCP instance to transmit the packet.
//
Configure = FALSE;
ReConfigure = FALSE; ReConfigure = FALSE;
} else { } else {
if ((HttpInstance->RemotePort == RemotePort) && //
// Check whether the token already existed.
//
if (EFI_ERROR (NetMapIterate (&HttpInstance->TxTokens, HttpTokenExist, Token))) {
return EFI_ACCESS_DENIED;
}
//
// Parse the URI of the remote host.
//
Url = HttpInstance->Url;
UrlLen = StrLen (Request->Url) + 1;
if (UrlLen > HTTP_URL_BUFFER_LEN) {
Url = AllocateZeroPool (UrlLen);
if (Url == NULL) {
return EFI_OUT_OF_RESOURCES;
}
FreePool (HttpInstance->Url);
HttpInstance->Url = Url;
}
UnicodeStrToAsciiStr (Request->Url, Url);
UrlParser = NULL;
Status = HttpParseUrl (Url, (UINT32) AsciiStrLen (Url), FALSE, &UrlParser);
if (EFI_ERROR (Status)) {
goto Error1;
}
HostName = NULL;
Status = HttpUrlGetHostName (Url, UrlParser, &HostName);
if (EFI_ERROR (Status)) {
goto Error1;
}
Status = HttpUrlGetPort (Url, UrlParser, &RemotePort);
if (EFI_ERROR (Status)) {
RemotePort = HTTP_DEFAULT_PORT;
}
//
// If Configure is TRUE, it indicates the first time to call Request();
// If ReConfigure is TRUE, it indicates the request URL is not same
// with the previous call to Request();
//
Configure = TRUE;
ReConfigure = TRUE;
if (HttpInstance->RemoteHost == NULL) {
//
// Request() is called the first time.
//
ReConfigure = FALSE;
} else {
if ((HttpInstance->RemotePort == RemotePort) &&
(AsciiStrCmp (HttpInstance->RemoteHost, HostName) == 0)) { (AsciiStrCmp (HttpInstance->RemoteHost, HostName) == 0)) {
//
// Host Name and port number of the request URL are the same with previous call to Request().
// Check whether previous TCP packet sent out.
//
if (EFI_ERROR (NetMapIterate (&HttpInstance->TxTokens, HttpTcpNotReady, NULL))) {
// //
// Wrap the HTTP token in HTTP_TOKEN_WRAP // Host Name and port number of the request URL are the same with previous call to Request().
// Check whether previous TCP packet sent out.
// //
Wrap = AllocateZeroPool (sizeof (HTTP_TOKEN_WRAP));
if (Wrap == NULL) { if (EFI_ERROR (NetMapIterate (&HttpInstance->TxTokens, HttpTcpNotReady, NULL))) {
Status = EFI_OUT_OF_RESOURCES; //
goto Error1; // Wrap the HTTP token in HTTP_TOKEN_WRAP
//
Wrap = AllocateZeroPool (sizeof (HTTP_TOKEN_WRAP));
if (Wrap == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto Error1;
}
Wrap->HttpToken = Token;
Wrap->HttpInstance = HttpInstance;
Status = HttpCreateTcpTxEvent (Wrap);
if (EFI_ERROR (Status)) {
goto Error1;
}
Status = NetMapInsertTail (&HttpInstance->TxTokens, Token, Wrap);
if (EFI_ERROR (Status)) {
goto Error1;
}
Wrap->TcpWrap.Method = Request->Method;
FreePool (HostName);
//
// Queue the HTTP token and return.
//
return EFI_SUCCESS;
} else {
//
// Use existing TCP instance to transmit the packet.
//
Configure = FALSE;
ReConfigure = FALSE;
} }
Wrap->HttpToken = Token;
Wrap->HttpInstance = HttpInstance;
Status = HttpCreateTcpTxEvent (Wrap);
if (EFI_ERROR (Status)) {
goto Error1;
}
Status = NetMapInsertTail (&HttpInstance->TxTokens, Token, Wrap);
if (EFI_ERROR (Status)) {
goto Error1;
}
Wrap->TcpWrap.Method = Request->Method;
FreePool (HostName);
//
// Queue the HTTP token and return.
//
return EFI_SUCCESS;
} else { } else {
// //
// Use existing TCP instance to transmit the packet. // Need close existing TCP instance and create a new TCP instance for data transmit.
// //
Configure = FALSE; if (HttpInstance->RemoteHost != NULL) {
ReConfigure = FALSE; FreePool (HttpInstance->RemoteHost);
} HttpInstance->RemoteHost = NULL;
} else { HttpInstance->RemotePort = 0;
// }
// Need close existing TCP instance and create a new TCP instance for data transmit.
//
if (HttpInstance->RemoteHost != NULL) {
FreePool (HttpInstance->RemoteHost);
HttpInstance->RemoteHost = NULL;
HttpInstance->RemotePort = 0;
} }
} }
} }
@ -461,7 +495,9 @@ EfiHttpRequest (
Wrap->HttpToken = Token; Wrap->HttpToken = Token;
Wrap->HttpInstance = HttpInstance; Wrap->HttpInstance = HttpInstance;
Wrap->TcpWrap.Method = Request->Method; if (Request != NULL) {
Wrap->TcpWrap.Method = Request->Method;
}
Status = HttpInitTcp (HttpInstance, Wrap, Configure); Status = HttpInitTcp (HttpInstance, Wrap, Configure);
if (EFI_ERROR (Status)) { if (EFI_ERROR (Status)) {
@ -482,7 +518,7 @@ EfiHttpRequest (
// Create request message. // Create request message.
// //
FileUrl = Url; FileUrl = Url;
if (*FileUrl != '/') { if (Url != NULL && *FileUrl != '/') {
// //
// Convert the absolute-URI to the absolute-path // Convert the absolute-URI to the absolute-path
// //
@ -506,9 +542,17 @@ EfiHttpRequest (
goto Error3; goto Error3;
} }
Status = NetMapInsertTail (&HttpInstance->TxTokens, Token, Wrap); //
if (EFI_ERROR (Status)) { // Every request we insert a TxToken and a response call would remove the TxToken.
goto Error4; // In cases of PUT/POST, after an initial request-response pair, we would do a
// continuous request without a response call. So, in such cases, where Request
// structure is NULL, we would not insert a TxToken.
//
if (Request != NULL) {
Status = NetMapInsertTail (&HttpInstance->TxTokens, Token, Wrap);
if (EFI_ERROR (Status)) {
goto Error4;
}
} }
// //
@ -533,7 +577,13 @@ EfiHttpRequest (
return EFI_SUCCESS; return EFI_SUCCESS;
Error5: Error5:
NetMapRemoveTail (&HttpInstance->TxTokens, NULL); //
// We would have inserted a TxToken only if Request structure is not NULL.
// Hence check before we do a remove in this error case.
//
if (Request != NULL) {
NetMapRemoveTail (&HttpInstance->TxTokens, NULL);
}
Error4: Error4:
if (RequestMsg != NULL) { if (RequestMsg != NULL) {
@ -970,89 +1020,114 @@ HttpResponseWorker (
goto Error; goto Error;
} }
//
// We could have response with just a HTTP message and no headers. For Example,
// "100 Continue". In such cases, we would not want to unnecessarily call a Parse
// method. A "\r\n" following Tmp string again would indicate an end. Compare and
// set SizeofHeaders to 0.
//
Tmp = Tmp + AsciiStrLen (HTTP_CRLF_STR); Tmp = Tmp + AsciiStrLen (HTTP_CRLF_STR);
SizeofHeaders = SizeofHeaders - (Tmp - HttpHeaders); if (CompareMem (Tmp, HTTP_CRLF_STR, AsciiStrLen (HTTP_CRLF_STR)) == 0) {
HeaderTmp = AllocateZeroPool (SizeofHeaders); SizeofHeaders = 0;
if (HeaderTmp == NULL) { } else {
goto Error; SizeofHeaders = SizeofHeaders - (Tmp - HttpHeaders);
} }
CopyMem (HeaderTmp, Tmp, SizeofHeaders);
FreePool (HttpHeaders);
HttpHeaders = HeaderTmp;
//
// Check whether the EFI_HTTP_UTILITIES_PROTOCOL is available.
//
if (mHttpUtilities == NULL) {
Status = EFI_NOT_READY;
goto Error;
}
//
// Parse the HTTP header into array of key/value pairs.
//
Status = mHttpUtilities->Parse (
mHttpUtilities,
HttpHeaders,
SizeofHeaders,
&HttpMsg->Headers,
&HttpMsg->HeaderCount
);
if (EFI_ERROR (Status)) {
goto Error;
}
FreePool (HttpHeaders);
HttpHeaders = NULL;
HttpMsg->Data.Response->StatusCode = HttpMappingToStatusCode (StatusCode); HttpMsg->Data.Response->StatusCode = HttpMappingToStatusCode (StatusCode);
HttpInstance->StatusCode = StatusCode; HttpInstance->StatusCode = StatusCode;
//
// Init message-body parser by header information.
//
Status = EFI_NOT_READY; Status = EFI_NOT_READY;
ValueInItem = NULL; ValueInItem = NULL;
NetMapRemoveHead (&HttpInstance->TxTokens, (VOID**) &ValueInItem);
if (ValueInItem == NULL) {
goto Error;
}
// //
// The first Tx Token not transmitted yet, insert back and return error. // In cases of PUT/POST, after an initial request-response pair, we would do a
// continuous request without a response call. So, we would not do an insert of
// TxToken. After we have sent the complete file, we will call a response to get
// a final response from server. In such a case, we would not have any TxTokens.
// Hence, check that case before doing a NetMapRemoveHead.
// //
if (!ValueInItem->TcpWrap.IsTxDone) { if (!NetMapIsEmpty (&HttpInstance->TxTokens)) {
goto Error2; NetMapRemoveHead (&HttpInstance->TxTokens, (VOID**) &ValueInItem);
if (ValueInItem == NULL) {
goto Error;
}
//
// The first Tx Token not transmitted yet, insert back and return error.
//
if (!ValueInItem->TcpWrap.IsTxDone) {
goto Error2;
}
} }
Status = HttpInitMsgParser ( if (SizeofHeaders != 0) {
ValueInItem->TcpWrap.Method, HeaderTmp = AllocateZeroPool (SizeofHeaders);
HttpMsg->Data.Response->StatusCode, if (HeaderTmp == NULL) {
HttpMsg->HeaderCount, goto Error;
HttpMsg->Headers, }
HttpBodyParserCallback,
(VOID *) ValueInItem,
&HttpInstance->MsgParser
);
if (EFI_ERROR (Status)) {
goto Error2;
}
// CopyMem (HeaderTmp, Tmp, SizeofHeaders);
// Check whether we received a complete HTTP message. FreePool (HttpHeaders);
// HttpHeaders = HeaderTmp;
if (HttpInstance->CacheBody != NULL) {
Status = HttpParseMessageBody (HttpInstance->MsgParser, HttpInstance->CacheLen, HttpInstance->CacheBody); //
// Check whether the EFI_HTTP_UTILITIES_PROTOCOL is available.
//
if (mHttpUtilities == NULL) {
Status = EFI_NOT_READY;
goto Error;
}
//
// Parse the HTTP header into array of key/value pairs.
//
Status = mHttpUtilities->Parse (
mHttpUtilities,
HttpHeaders,
SizeofHeaders,
&HttpMsg->Headers,
&HttpMsg->HeaderCount
);
if (EFI_ERROR (Status)) {
goto Error;
}
FreePool (HttpHeaders);
HttpHeaders = NULL;
//
// Init message-body parser by header information.
//
Status = HttpInitMsgParser (
HttpInstance->Method,
HttpMsg->Data.Response->StatusCode,
HttpMsg->HeaderCount,
HttpMsg->Headers,
HttpBodyParserCallback,
(VOID *) ValueInItem,
&HttpInstance->MsgParser
);
if (EFI_ERROR (Status)) { if (EFI_ERROR (Status)) {
goto Error2; goto Error2;
} }
if (HttpIsMessageComplete (HttpInstance->MsgParser)) { //
// // Check whether we received a complete HTTP message.
// Free the MsgParse since we already have a full HTTP message. //
// if (HttpInstance->CacheBody != NULL) {
HttpFreeMsgParser (HttpInstance->MsgParser); Status = HttpParseMessageBody (HttpInstance->MsgParser, HttpInstance->CacheLen, HttpInstance->CacheBody);
HttpInstance->MsgParser = NULL; if (EFI_ERROR (Status)) {
goto Error2;
}
if (HttpIsMessageComplete (HttpInstance->MsgParser)) {
//
// Free the MsgParse since we already have a full HTTP message.
//
HttpFreeMsgParser (HttpInstance->MsgParser);
HttpInstance->MsgParser = NULL;
}
} }
} }

View File

@ -91,6 +91,7 @@ typedef struct _HTTP_PROTOCOL {
LIST_ENTRY Link; // Link to all HTTP instance from the service. LIST_ENTRY Link; // Link to all HTTP instance from the service.
BOOLEAN InDestroy; BOOLEAN InDestroy;
INTN State; INTN State;
EFI_HTTP_METHOD Method;
UINTN StatusCode; UINTN StatusCode;