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