summaryrefslogtreecommitdiffstats
path: root/NetworkPkg/HttpBootDxe/HttpBootClient.c
diff options
context:
space:
mode:
Diffstat (limited to 'NetworkPkg/HttpBootDxe/HttpBootClient.c')
-rw-r--r--NetworkPkg/HttpBootDxe/HttpBootClient.c182
1 files changed, 176 insertions, 6 deletions
diff --git a/NetworkPkg/HttpBootDxe/HttpBootClient.c b/NetworkPkg/HttpBootDxe/HttpBootClient.c
index 40f64fcb6b..858e7c2103 100644
--- a/NetworkPkg/HttpBootDxe/HttpBootClient.c
+++ b/NetworkPkg/HttpBootDxe/HttpBootClient.c
@@ -923,6 +923,9 @@ HttpBootGetBootFileCallback (
BufferSize has been updated with the size needed to complete
the request.
@retval EFI_ACCESS_DENIED The server needs to authenticate the client.
+ @retval EFI_NOT_READY Data transfer has timed-out, call HttpBootGetBootFile again to resume
+ the download operation using HTTP Range headers.
+ @retval EFI_UNSUPPORTED Some HTTP response header is not supported.
@retval Others Unexpected error happened.
**/
@@ -955,6 +958,10 @@ HttpBootGetBootFile (
CHAR8 BaseAuthValue[80];
EFI_HTTP_HEADER *HttpHeader;
CHAR8 *Data;
+ UINTN HeadersCount;
+ BOOLEAN ResumingOperation;
+ CHAR8 *ContentRangeResponseValue;
+ CHAR8 RangeValue[64];
ASSERT (Private != NULL);
ASSERT (Private->HttpCreated);
@@ -985,6 +992,16 @@ HttpBootGetBootFile (
}
}
+ // Check if this is a previous download that has failed and need to be resumed
+ if ((!HeaderOnly) &&
+ (Private->PartialTransferredSize > 0) &&
+ (Private->BootFileSize == *BufferSize))
+ {
+ ResumingOperation = TRUE;
+ } else {
+ ResumingOperation = FALSE;
+ }
+
//
// Not found in cache, try to download it through HTTP.
//
@@ -1014,8 +1031,23 @@ HttpBootGetBootFile (
// Accept
// User-Agent
// [Authorization]
+ // [Range]
+ // [If-Match]|[If-Unmodified-Since]
//
- HttpIoHeader = HttpIoCreateHeader ((Private->AuthData != NULL) ? 4 : 3);
+ HeadersCount = 3;
+ if (Private->AuthData != NULL) {
+ HeadersCount++;
+ }
+
+ if (ResumingOperation) {
+ HeadersCount++;
+ if (Private->LastModifiedOrEtag) {
+ HeadersCount++;
+ }
+ }
+
+ HttpIoHeader = HttpIoCreateHeader (HeadersCount);
+
if (HttpIoHeader == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto ERROR_2;
@@ -1098,6 +1130,62 @@ HttpBootGetBootFile (
}
//
+ // Add HTTP header field 5 (optional): Range
+ //
+ if (ResumingOperation) {
+ // Resuming a failed download. Prepare the HTTP Range Header
+ Status = AsciiSPrint (
+ RangeValue,
+ sizeof (RangeValue),
+ "bytes=%lu-%lu",
+ Private->PartialTransferredSize,
+ Private->BootFileSize - 1
+ );
+ if (EFI_ERROR (Status)) {
+ goto ERROR_3;
+ }
+
+ Status = HttpIoSetHeader (HttpIoHeader, "Range", RangeValue);
+ if (EFI_ERROR (Status)) {
+ goto ERROR_3;
+ }
+
+ DEBUG (
+ (DEBUG_WARN | DEBUG_INFO,
+ "HttpBootGetBootFile: Resuming failed download. Range: %a\n",
+ RangeValue)
+ );
+
+ //
+ // Add HTTP header field 6 (optional): If-Match or If-Unmodified-Since
+ //
+ if (Private->LastModifiedOrEtag) {
+ if (Private->LastModifiedOrEtag[0] == '"') {
+ // An ETag value starts with "
+ DEBUG (
+ (DEBUG_WARN | DEBUG_INFO,
+ "HttpBootGetBootFile: If-Match=%a\n",
+ Private->LastModifiedOrEtag)
+ );
+ // Add If-Match header with the ETag value got from the first request.
+ Status = HttpIoSetHeader (HttpIoHeader, HTTP_HEADER_IF_MATCH, Private->LastModifiedOrEtag);
+ } else {
+ DEBUG (
+ (DEBUG_WARN | DEBUG_INFO,
+ "HttpBootGetBootFile: If-Unmodified-Since=%a\n",
+ Private->LastModifiedOrEtag)
+ );
+ // Add If-Unmodified-Since header with the timestamp value (Last-Modified) got from the first request.
+ Status = HttpIoSetHeader (HttpIoHeader, HTTP_HEADER_IF_UNMODIFIED_SINCE, Private->LastModifiedOrEtag);
+ }
+
+ if (EFI_ERROR (Status)) {
+ goto ERROR_3;
+ }
+ }
+ }
+
+ //
// 2.2 Build the rest of HTTP request info.
//
RequestData = AllocatePool (sizeof (EFI_HTTP_REQUEST_DATA));
@@ -1245,6 +1333,62 @@ HttpBootGetBootFile (
Cache->ImageType = *ImageType;
}
+ // Cache ETag or Last-Modified response header value to
+ // be used when resuming an interrupted download.
+ HttpHeader = HttpFindHeader (
+ ResponseData->HeaderCount,
+ ResponseData->Headers,
+ HTTP_HEADER_ETAG
+ );
+ if (HttpHeader == NULL) {
+ HttpHeader = HttpFindHeader (
+ ResponseData->HeaderCount,
+ ResponseData->Headers,
+ HTTP_HEADER_LAST_MODIFIED
+ );
+ }
+
+ if (HttpHeader) {
+ if (Private->LastModifiedOrEtag) {
+ FreePool (Private->LastModifiedOrEtag);
+ }
+
+ Private->LastModifiedOrEtag = AllocateCopyPool (AsciiStrSize (HttpHeader->FieldValue), HttpHeader->FieldValue);
+ }
+
+ //
+ // 3.2.2 Validate the range response. If operation is being resumed,
+ // server must respond with Content-Range.
+ //
+ if (ResumingOperation) {
+ HttpHeader = HttpFindHeader (
+ ResponseData->HeaderCount,
+ ResponseData->Headers,
+ HTTP_HEADER_CONTENT_RANGE
+ );
+ if ((HttpHeader == NULL) ||
+ (AsciiStrnCmp (HttpHeader->FieldValue, "bytes", 5) != 0))
+ {
+ Status = EFI_UNSUPPORTED;
+ goto ERROR_5;
+ }
+
+ // Gets the total size of ranged data (Content-Range: <unit> <range-start>-<range-end>/<size>)
+ // and check if it remains the same
+ ContentRangeResponseValue = AsciiStrStr (HttpHeader->FieldValue, "/");
+ if (ContentRangeResponseValue == NULL) {
+ Status = EFI_INVALID_PARAMETER;
+ goto ERROR_5;
+ }
+
+ ContentRangeResponseValue++;
+ ContentLength = AsciiStrDecimalToUintn (ContentRangeResponseValue);
+ if (ContentLength != *BufferSize) {
+ Status = EFI_INVALID_PARAMETER;
+ goto ERROR_5;
+ }
+ }
+
//
// 3.3 Init a message-body parser from the header information.
//
@@ -1295,10 +1439,15 @@ HttpBootGetBootFile (
// In identity transfer-coding there is no need to parse the message body,
// just download the message body to the user provided buffer directly.
//
+ if (ResumingOperation && ((ContentLength + Private->PartialTransferredSize) > *BufferSize)) {
+ Status = EFI_INVALID_PARAMETER;
+ goto ERROR_6;
+ }
+
ReceivedSize = 0;
while (ReceivedSize < ContentLength) {
- ResponseBody.Body = (CHAR8 *)Buffer + ReceivedSize;
- ResponseBody.BodyLength = *BufferSize - ReceivedSize;
+ ResponseBody.Body = (CHAR8 *)Buffer + (ReceivedSize + Private->PartialTransferredSize);
+ ResponseBody.BodyLength = *BufferSize - (ReceivedSize + Private->PartialTransferredSize);
Status = HttpIoRecvResponse (
&Private->HttpIo,
FALSE,
@@ -1309,6 +1458,20 @@ HttpBootGetBootFile (
Status = ResponseBody.Status;
}
+ if ((Status == EFI_TIMEOUT) || (Status == EFI_DEVICE_ERROR)) {
+ // For EFI_TIMEOUT and EFI_DEVICE_ERROR errors, we may resume the operation.
+ // We will not check if server sent Accept-Ranges header, because some back-ends
+ // do not report this header, even when supporting it. Know example: CloudFlare CDN Cache.
+ Private->PartialTransferredSize = ReceivedSize;
+ DEBUG (
+ (
+ DEBUG_WARN | DEBUG_INFO,
+ "HttpBootGetBootFile: Transfer error. Bytes transferred so far: %lu.\n",
+ ReceivedSize
+ )
+ );
+ }
+
goto ERROR_6;
}
@@ -1326,6 +1489,9 @@ HttpBootGetBootFile (
}
}
}
+
+ // download completed, there is no more partial data
+ Private->PartialTransferredSize = 0;
} else {
//
// In "chunked" transfer-coding mode, so we need to parse the received
@@ -1385,9 +1551,13 @@ HttpBootGetBootFile (
//
// 3.5 Message-body receive & parse is completed, we should be able to get the file size now.
//
- Status = HttpGetEntityLength (Parser, &ContentLength);
- if (EFI_ERROR (Status)) {
- goto ERROR_6;
+ if (!ResumingOperation) {
+ Status = HttpGetEntityLength (Parser, &ContentLength);
+ if (EFI_ERROR (Status)) {
+ goto ERROR_6;
+ }
+ } else {
+ ContentLength = Private->BootFileSize;
}
if (*BufferSize < ContentLength) {