HttpBootClient.c revision a93786ae6c4f8a92e216899c05366ba7aacf72e7
1/** @file
2  Implementation of the boot file download function.
3
4Copyright (c) 2015 - 2016, Intel Corporation. All rights reserved.<BR>
5(C) Copyright 2016 Hewlett Packard Enterprise Development LP<BR>
6This program and the accompanying materials are licensed and made available under
7the terms and conditions of the BSD License that accompanies this distribution.
8The full text of the license may be found at
9http://opensource.org/licenses/bsd-license.php.
10
11THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
12WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
13
14**/
15
16#include "HttpBootDxe.h"
17
18/**
19  Update the IP and URL device path node to include the boot resource information.
20
21  @param[in]    Private            The pointer to the driver's private data.
22
23  @retval EFI_SUCCESS              Device patch successfully updated.
24  @retval EFI_OUT_OF_RESOURCES     Could not allocate needed resources.
25  @retval Others                   Unexpected error happened.
26
27**/
28EFI_STATUS
29HttpBootUpdateDevicePath (
30  IN   HTTP_BOOT_PRIVATE_DATA   *Private
31  )
32{
33  EFI_DEV_PATH               *Node;
34  EFI_DEVICE_PATH_PROTOCOL   *TmpDevicePath;
35  EFI_DEVICE_PATH_PROTOCOL   *NewDevicePath;
36  UINTN                      Length;
37  EFI_STATUS                 Status;
38
39  TmpDevicePath = NULL;
40
41  //
42  // Update the IP node with DHCP assigned information.
43  //
44  if (!Private->UsingIpv6) {
45    Node = AllocateZeroPool (sizeof (IPv4_DEVICE_PATH));
46    if (Node == NULL) {
47      return EFI_OUT_OF_RESOURCES;
48    }
49    Node->Ipv4.Header.Type    = MESSAGING_DEVICE_PATH;
50    Node->Ipv4.Header.SubType = MSG_IPv4_DP;
51    SetDevicePathNodeLength (Node, sizeof (IPv4_DEVICE_PATH));
52    CopyMem (&Node->Ipv4.LocalIpAddress, &Private->StationIp, sizeof (EFI_IPv4_ADDRESS));
53    Node->Ipv4.RemotePort      = Private->Port;
54    Node->Ipv4.Protocol        = EFI_IP_PROTO_TCP;
55    Node->Ipv4.StaticIpAddress = FALSE;
56    CopyMem (&Node->Ipv4.GatewayIpAddress, &Private->GatewayIp, sizeof (EFI_IPv4_ADDRESS));
57    CopyMem (&Node->Ipv4.SubnetMask, &Private->SubnetMask, sizeof (EFI_IPv4_ADDRESS));
58  } else {
59    Node = AllocateZeroPool (sizeof (IPv6_DEVICE_PATH));
60    if (Node == NULL) {
61      return EFI_OUT_OF_RESOURCES;
62    }
63    Node->Ipv6.Header.Type     = MESSAGING_DEVICE_PATH;
64    Node->Ipv6.Header.SubType  = MSG_IPv6_DP;
65    SetDevicePathNodeLength (Node, sizeof (IPv6_DEVICE_PATH));
66    Node->Ipv6.PrefixLength    = IP6_PREFIX_LENGTH;
67    Node->Ipv6.RemotePort      = Private->Port;
68    Node->Ipv6.Protocol        = EFI_IP_PROTO_TCP;
69    Node->Ipv6.IpAddressOrigin = 0;
70    CopyMem (&Node->Ipv6.LocalIpAddress, &Private->StationIp.v6, sizeof (EFI_IPv6_ADDRESS));
71    CopyMem (&Node->Ipv6.RemoteIpAddress, &Private->ServerIp.v6, sizeof (EFI_IPv6_ADDRESS));
72    CopyMem (&Node->Ipv6.GatewayIpAddress, &Private->GatewayIp.v6, sizeof (EFI_IPv6_ADDRESS));
73  }
74
75  TmpDevicePath = AppendDevicePathNode (Private->ParentDevicePath, (EFI_DEVICE_PATH_PROTOCOL*) Node);
76  FreePool (Node);
77  if (TmpDevicePath == NULL) {
78    return EFI_OUT_OF_RESOURCES;
79  }
80
81  //
82  // Update the URI node with the boot file URI.
83  //
84  Length = sizeof (EFI_DEVICE_PATH_PROTOCOL) + AsciiStrSize (Private->BootFileUri);
85  Node = AllocatePool (Length);
86  if (Node == NULL) {
87    FreePool (TmpDevicePath);
88    return EFI_OUT_OF_RESOURCES;
89  }
90  Node->DevPath.Type    = MESSAGING_DEVICE_PATH;
91  Node->DevPath.SubType = MSG_URI_DP;
92  SetDevicePathNodeLength (Node, Length);
93  CopyMem ((UINT8*) Node + sizeof (EFI_DEVICE_PATH_PROTOCOL), Private->BootFileUri, AsciiStrSize (Private->BootFileUri));
94
95  NewDevicePath = AppendDevicePathNode (TmpDevicePath, (EFI_DEVICE_PATH_PROTOCOL*) Node);
96  FreePool (Node);
97  FreePool (TmpDevicePath);
98  if (NewDevicePath == NULL) {
99    return EFI_OUT_OF_RESOURCES;
100  }
101
102  if (!Private->UsingIpv6) {
103    //
104    // Reinstall the device path protocol of the child handle.
105    //
106    Status = gBS->ReinstallProtocolInterface (
107                    Private->Ip4Nic->Controller,
108                    &gEfiDevicePathProtocolGuid,
109                    Private->Ip4Nic->DevicePath,
110                    NewDevicePath
111                    );
112    if (EFI_ERROR (Status)) {
113      return Status;
114    }
115
116    FreePool (Private->Ip4Nic->DevicePath);
117    Private->Ip4Nic->DevicePath = NewDevicePath;
118  } else {
119    //
120    // Reinstall the device path protocol of the child handle.
121    //
122    Status = gBS->ReinstallProtocolInterface (
123                    Private->Ip6Nic->Controller,
124                    &gEfiDevicePathProtocolGuid,
125                    Private->Ip6Nic->DevicePath,
126                    NewDevicePath
127                    );
128    if (EFI_ERROR (Status)) {
129      return Status;
130    }
131    FreePool (Private->Ip6Nic->DevicePath);
132    Private->Ip6Nic->DevicePath = NewDevicePath;
133  }
134
135  return EFI_SUCCESS;
136}
137
138/**
139  Parse the boot file URI information from the selected Dhcp4 offer packet.
140
141  @param[in]    Private        The pointer to the driver's private data.
142
143  @retval EFI_SUCCESS          Successfully parsed out all the boot information.
144  @retval Others               Failed to parse out the boot information.
145
146**/
147EFI_STATUS
148HttpBootDhcp4ExtractUriInfo (
149  IN     HTTP_BOOT_PRIVATE_DATA   *Private
150  )
151{
152  HTTP_BOOT_DHCP4_PACKET_CACHE    *SelectOffer;
153  HTTP_BOOT_DHCP4_PACKET_CACHE    *HttpOffer;
154  UINT32                          SelectIndex;
155  UINT32                          ProxyIndex;
156  EFI_DHCP4_PACKET_OPTION         *Option;
157  EFI_STATUS                      Status;
158
159  ASSERT (Private != NULL);
160  ASSERT (Private->SelectIndex != 0);
161  SelectIndex = Private->SelectIndex - 1;
162  ASSERT (SelectIndex < HTTP_BOOT_OFFER_MAX_NUM);
163
164  Status = EFI_SUCCESS;
165
166  //
167  // SelectOffer contains the IP address configuration and name server configuration.
168  // HttpOffer contains the boot file URL.
169  //
170  SelectOffer = &Private->OfferBuffer[SelectIndex].Dhcp4;
171  if (Private->FilePathUri == NULL) {
172    //
173    // In Corporate environment, we need a HttpOffer.
174    //
175    if ((SelectOffer->OfferType == HttpOfferTypeDhcpIpUri) ||
176        (SelectOffer->OfferType == HttpOfferTypeDhcpIpUriDns) ||
177        (SelectOffer->OfferType == HttpOfferTypeDhcpNameUriDns)) {
178      HttpOffer = SelectOffer;
179    } else {
180      ASSERT (Private->SelectProxyType != HttpOfferTypeMax);
181      ProxyIndex = Private->OfferIndex[Private->SelectProxyType][0];
182      HttpOffer = &Private->OfferBuffer[ProxyIndex].Dhcp4;
183    }
184    Private->BootFileUriParser = HttpOffer->UriParser;
185    Private->BootFileUri = (CHAR8*) HttpOffer->OptList[HTTP_BOOT_DHCP4_TAG_INDEX_BOOTFILE]->Data;
186  } else {
187    //
188    // In Home environment the BootFileUri comes from the FilePath.
189    //
190    Private->BootFileUriParser = Private->FilePathUriParser;
191    Private->BootFileUri = Private->FilePathUri;
192  }
193
194  //
195  // Configure the default DNS server if server assigned.
196  //
197  if ((SelectOffer->OfferType == HttpOfferTypeDhcpNameUriDns) ||
198      (SelectOffer->OfferType == HttpOfferTypeDhcpDns) ||
199      (SelectOffer->OfferType == HttpOfferTypeDhcpIpUriDns)) {
200    Option = SelectOffer->OptList[HTTP_BOOT_DHCP4_TAG_INDEX_DNS_SERVER];
201    ASSERT (Option != NULL);
202    Status = HttpBootRegisterIp4Dns (
203               Private,
204               Option->Length,
205               Option->Data
206               );
207    if (EFI_ERROR (Status)) {
208      return Status;
209    }
210  }
211
212  //
213  // Extract the port from URL, and use default HTTP port 80 if not provided.
214  //
215  Status = HttpUrlGetPort (
216             Private->BootFileUri,
217             Private->BootFileUriParser,
218             &Private->Port
219             );
220  if (EFI_ERROR (Status) || Private->Port == 0) {
221    Private->Port = 80;
222  }
223
224  //
225  // All boot informations are valid here.
226  //
227  AsciiPrint ("\n  URI: %a", Private->BootFileUri);
228
229  //
230  // Update the device path to include the IP and boot URI information.
231  //
232  Status = HttpBootUpdateDevicePath (Private);
233
234  return Status;
235}
236
237/**
238  Parse the boot file URI information from the selected Dhcp6 offer packet.
239
240  @param[in]    Private        The pointer to the driver's private data.
241
242  @retval EFI_SUCCESS          Successfully parsed out all the boot information.
243  @retval Others               Failed to parse out the boot information.
244
245**/
246EFI_STATUS
247HttpBootDhcp6ExtractUriInfo (
248  IN     HTTP_BOOT_PRIVATE_DATA   *Private
249  )
250{
251  HTTP_BOOT_DHCP6_PACKET_CACHE    *SelectOffer;
252  HTTP_BOOT_DHCP6_PACKET_CACHE    *HttpOffer;
253  UINT32                          SelectIndex;
254  UINT32                          ProxyIndex;
255  EFI_DHCP6_PACKET_OPTION         *Option;
256  EFI_IPv6_ADDRESS                IpAddr;
257  CHAR8                           *HostName;
258  CHAR16                          *HostNameStr;
259  EFI_STATUS                      Status;
260
261  ASSERT (Private != NULL);
262  ASSERT (Private->SelectIndex != 0);
263  SelectIndex = Private->SelectIndex - 1;
264  ASSERT (SelectIndex < HTTP_BOOT_OFFER_MAX_NUM);
265
266  Status   = EFI_SUCCESS;
267  HostName = NULL;
268  //
269  // SelectOffer contains the IP address configuration and name server configuration.
270  // HttpOffer contains the boot file URL.
271  //
272  SelectOffer = &Private->OfferBuffer[SelectIndex].Dhcp6;
273  if (Private->FilePathUri == NULL) {
274    //
275    // In Corporate environment, we need a HttpOffer.
276    //
277    if ((SelectOffer->OfferType == HttpOfferTypeDhcpIpUri) ||
278        (SelectOffer->OfferType == HttpOfferTypeDhcpIpUriDns) ||
279        (SelectOffer->OfferType == HttpOfferTypeDhcpNameUriDns)) {
280      HttpOffer = SelectOffer;
281    } else {
282      ASSERT (Private->SelectProxyType != HttpOfferTypeMax);
283      ProxyIndex = Private->OfferIndex[Private->SelectProxyType][0];
284      HttpOffer = &Private->OfferBuffer[ProxyIndex].Dhcp6;
285    }
286    Private->BootFileUriParser = HttpOffer->UriParser;
287    Private->BootFileUri = (CHAR8*) HttpOffer->OptList[HTTP_BOOT_DHCP6_IDX_BOOT_FILE_URL]->Data;
288  } else {
289    //
290    // In Home environment the BootFileUri comes from the FilePath.
291    //
292    Private->BootFileUriParser = Private->FilePathUriParser;
293    Private->BootFileUri = Private->FilePathUri;
294  }
295
296  //
297  //  Set the Local station address to IP layer.
298  //
299  Status = HttpBootSetIp6Address (Private);
300  if (EFI_ERROR (Status)) {
301    return Status;
302  }
303
304  //
305  // Configure the default DNS server if server assigned.
306  //
307  if ((SelectOffer->OfferType == HttpOfferTypeDhcpNameUriDns) ||
308      (SelectOffer->OfferType == HttpOfferTypeDhcpDns) ||
309      (SelectOffer->OfferType == HttpOfferTypeDhcpIpUriDns)) {
310    Option = SelectOffer->OptList[HTTP_BOOT_DHCP6_IDX_DNS_SERVER];
311    ASSERT (Option != NULL);
312    Status = HttpBootSetIp6Dns (
313               Private,
314               HTONS (Option->OpLen),
315               Option->Data
316               );
317    if (EFI_ERROR (Status)) {
318      return Status;
319    }
320  }
321
322  //
323  // Extract the HTTP server Ip frome URL. This is used to Check route table
324  // whether can send message to HTTP Server Ip through the GateWay.
325  //
326  Status = HttpUrlGetIp6 (
327             Private->BootFileUri,
328             Private->BootFileUriParser,
329             &IpAddr
330             );
331
332  if (EFI_ERROR (Status)) {
333    //
334    // The Http server address is expressed by Name Ip, so perform DNS resolution
335    //
336    Status = HttpUrlGetHostName (
337               Private->BootFileUri,
338               Private->BootFileUriParser,
339               &HostName
340               );
341    if (EFI_ERROR (Status)) {
342      return Status;
343    }
344
345    HostNameStr = AllocateZeroPool ((AsciiStrLen (HostName) + 1) * sizeof (CHAR16));
346    if (HostNameStr == NULL) {
347      Status = EFI_OUT_OF_RESOURCES;
348      goto Error;
349    }
350
351    AsciiStrToUnicodeStr (HostName, HostNameStr);
352    Status = HttpBootDns (Private, HostNameStr, &IpAddr);
353    FreePool (HostNameStr);
354    if (EFI_ERROR (Status)) {
355      goto Error;
356    }
357  }
358
359  CopyMem (&Private->ServerIp.v6, &IpAddr, sizeof (EFI_IPv6_ADDRESS));
360
361  //
362  // register the IPv6 gateway address to the network device.
363  //
364  Status = HttpBootSetIp6Gateway (Private);
365  if (EFI_ERROR (Status)) {
366    return Status;
367  }
368
369  //
370  // Extract the port from URL, and use default HTTP port 80 if not provided.
371  //
372  Status = HttpUrlGetPort (
373             Private->BootFileUri,
374             Private->BootFileUriParser,
375             &Private->Port
376             );
377  if (EFI_ERROR (Status) || Private->Port == 0) {
378    Private->Port = 80;
379  }
380
381  //
382  // All boot informations are valid here.
383  //
384  AsciiPrint ("\n  URI: %a", Private->BootFileUri);
385  //
386  // Update the device path to include the IP and boot URI information.
387  //
388  Status = HttpBootUpdateDevicePath (Private);
389
390Error:
391
392  if (HostName != NULL) {
393    FreePool (HostName);
394  }
395
396  return Status;
397}
398
399
400/**
401  Discover all the boot information for boot file.
402
403  @param[in, out]    Private        The pointer to the driver's private data.
404
405  @retval EFI_SUCCESS          Successfully obtained all the boot information .
406  @retval Others               Failed to retrieve the boot information.
407
408**/
409EFI_STATUS
410HttpBootDiscoverBootInfo (
411  IN OUT HTTP_BOOT_PRIVATE_DATA   *Private
412  )
413{
414  EFI_STATUS              Status;
415
416  //
417  // Start D.O.R.A/S.A.R.R exchange to acquire station ip address and
418  // other Http boot information.
419  //
420  Status = HttpBootDhcp (Private);
421  if (EFI_ERROR (Status)) {
422    return Status;
423  }
424
425  if (!Private->UsingIpv6) {
426    Status = HttpBootDhcp4ExtractUriInfo (Private);
427  } else {
428    Status = HttpBootDhcp6ExtractUriInfo (Private);
429  }
430
431  return Status;
432}
433
434/**
435  Create a HttpIo instance for the file download.
436
437  @param[in]    Private        The pointer to the driver's private data.
438
439  @retval EFI_SUCCESS          Successfully created.
440  @retval Others               Failed to create HttpIo.
441
442**/
443EFI_STATUS
444HttpBootCreateHttpIo (
445  IN     HTTP_BOOT_PRIVATE_DATA       *Private
446  )
447{
448  HTTP_IO_CONFIG_DATA          ConfigData;
449  EFI_STATUS                   Status;
450  EFI_HANDLE                   ImageHandle;
451
452  ASSERT (Private != NULL);
453
454  ZeroMem (&ConfigData, sizeof (HTTP_IO_CONFIG_DATA));
455  if (!Private->UsingIpv6) {
456    ConfigData.Config4.HttpVersion    = HttpVersion11;
457    ConfigData.Config4.RequestTimeOut = HTTP_BOOT_REQUEST_TIMEOUT;
458    IP4_COPY_ADDRESS (&ConfigData.Config4.LocalIp, &Private->StationIp.v4);
459    IP4_COPY_ADDRESS (&ConfigData.Config4.SubnetMask, &Private->SubnetMask.v4);
460    ImageHandle = Private->Ip4Nic->ImageHandle;
461  } else {
462    ConfigData.Config6.HttpVersion    = HttpVersion11;
463    ConfigData.Config6.RequestTimeOut = HTTP_BOOT_REQUEST_TIMEOUT;
464    IP6_COPY_ADDRESS (&ConfigData.Config6.LocalIp, &Private->StationIp.v6);
465    ImageHandle = Private->Ip6Nic->ImageHandle;
466  }
467
468  Status = HttpIoCreateIo (
469             ImageHandle,
470             Private->Controller,
471             Private->UsingIpv6 ? IP_VERSION_6 : IP_VERSION_4,
472             &ConfigData,
473             &Private->HttpIo
474             );
475  if (EFI_ERROR (Status)) {
476    return Status;
477  }
478
479  Private->HttpCreated = TRUE;
480  return EFI_SUCCESS;
481}
482
483/**
484  Release all the resource of a cache item.
485
486  @param[in]          Cache         The pointer to the cache item.
487
488**/
489VOID
490HttpBootFreeCache (
491  IN  HTTP_BOOT_CACHE_CONTENT    *Cache
492  )
493{
494  UINTN                       Index;
495  LIST_ENTRY                  *Entry;
496  LIST_ENTRY                  *NextEntry;
497  HTTP_BOOT_ENTITY_DATA       *EntityData;
498
499  if (Cache != NULL) {
500    //
501    // Free the request data
502    //
503    if (Cache->RequestData != NULL) {
504      if (Cache->RequestData->Url != NULL) {
505        FreePool (Cache->RequestData->Url);
506      }
507      FreePool (Cache->RequestData);
508    }
509
510    //
511    // Free the response header
512    //
513    if (Cache->ResponseData != NULL) {
514      if (Cache->ResponseData->Headers != NULL) {
515        for (Index = 0; Index < Cache->ResponseData->HeaderCount; Index++) {
516          FreePool (Cache->ResponseData->Headers[Index].FieldName);
517          FreePool (Cache->ResponseData->Headers[Index].FieldValue);
518        }
519        FreePool (Cache->ResponseData->Headers);
520      }
521    }
522
523    //
524    // Free the response body
525    //
526    NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Cache->EntityDataList) {
527      EntityData = NET_LIST_USER_STRUCT (Entry, HTTP_BOOT_ENTITY_DATA, Link);
528      if (EntityData->Block != NULL) {
529        FreePool (EntityData->Block);
530      }
531      RemoveEntryList (&EntityData->Link);
532      FreePool (EntityData);
533    }
534
535    FreePool (Cache);
536  }
537}
538
539/**
540  Clean up all cached data.
541
542  @param[in]          Private         The pointer to the driver's private data.
543
544**/
545VOID
546HttpBootFreeCacheList (
547  IN     HTTP_BOOT_PRIVATE_DATA   *Private
548  )
549{
550  LIST_ENTRY                  *Entry;
551  LIST_ENTRY                  *NextEntry;
552  HTTP_BOOT_CACHE_CONTENT     *Cache;
553
554  NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Private->CacheList) {
555    Cache = NET_LIST_USER_STRUCT (Entry, HTTP_BOOT_CACHE_CONTENT, Link);
556    RemoveEntryList (&Cache->Link);
557    HttpBootFreeCache (Cache);
558  }
559}
560
561/**
562  Get the file content from cached data.
563
564  @param[in]          Private         The pointer to the driver's private data.
565  @param[in]          Uri             Uri of the file to be retrieved from cache.
566  @param[in, out]     BufferSize      On input the size of Buffer in bytes. On output with a return
567                                      code of EFI_SUCCESS, the amount of data transferred to
568                                      Buffer. On output with a return code of EFI_BUFFER_TOO_SMALL,
569                                      the size of Buffer required to retrieve the requested file.
570  @param[out]         Buffer          The memory buffer to transfer the file to. IF Buffer is NULL,
571                                      then the size of the requested file is returned in
572                                      BufferSize.
573  @param[out]         ImageType       The image type of the downloaded file.
574
575  @retval EFI_SUCCESS          Successfully created.
576  @retval Others               Failed to create HttpIo.
577
578**/
579EFI_STATUS
580HttpBootGetFileFromCache (
581  IN     HTTP_BOOT_PRIVATE_DATA   *Private,
582  IN     CHAR16                   *Uri,
583  IN OUT UINTN                    *BufferSize,
584     OUT UINT8                    *Buffer,
585     OUT HTTP_BOOT_IMAGE_TYPE     *ImageType
586  )
587{
588  LIST_ENTRY                  *Entry;
589  LIST_ENTRY                  *Entry2;
590  HTTP_BOOT_CACHE_CONTENT     *Cache;
591  HTTP_BOOT_ENTITY_DATA       *EntityData;
592  UINTN                       CopyedSize;
593
594  if (Uri == NULL || BufferSize == 0 || Buffer == NULL || ImageType == NULL) {
595    return EFI_INVALID_PARAMETER;
596  }
597
598  NET_LIST_FOR_EACH (Entry, &Private->CacheList) {
599    Cache = NET_LIST_USER_STRUCT (Entry, HTTP_BOOT_CACHE_CONTENT, Link);
600    //
601    // Compare the URI to see whether we already have a cache for this file.
602    //
603    if ((Cache->RequestData != NULL) &&
604        (Cache->RequestData->Url != NULL) &&
605        (StrCmp (Uri, Cache->RequestData->Url) == 0))
606    {
607      //
608      // Hit in cache, record image type.
609      //
610      *ImageType  = Cache->ImageType;
611
612      //
613      // Check buffer size.
614      //
615      if (*BufferSize < Cache->EntityLength) {
616        *BufferSize = Cache->EntityLength;
617        return EFI_BUFFER_TOO_SMALL;
618      }
619
620      //
621      // Fill data to buffer.
622      //
623      CopyedSize = 0;
624      NET_LIST_FOR_EACH (Entry2, &Cache->EntityDataList) {
625        EntityData = NET_LIST_USER_STRUCT (Entry2, HTTP_BOOT_ENTITY_DATA, Link);
626        if (*BufferSize > CopyedSize) {
627          CopyMem (
628            Buffer + CopyedSize,
629            EntityData->DataStart,
630            MIN (EntityData->DataLength, *BufferSize - CopyedSize)
631            );
632          CopyedSize += MIN (EntityData->DataLength, *BufferSize - CopyedSize);
633        }
634      }
635      *BufferSize = CopyedSize;
636      return EFI_SUCCESS;
637    }
638  }
639
640  return EFI_NOT_FOUND;
641}
642
643/**
644  A callback function to intercept events during message parser.
645
646  This function will be invoked during HttpParseMessageBody() with various events type. An error
647  return status of the callback function will cause the HttpParseMessageBody() aborted.
648
649  @param[in]    EventType          Event type of this callback call.
650  @param[in]    Data               A pointer to data buffer.
651  @param[in]    Length             Length in bytes of the Data.
652  @param[in]    Context            Callback context set by HttpInitMsgParser().
653
654  @retval EFI_SUCCESS              Continue to parser the message body.
655  @retval Others                   Abort the parse.
656
657**/
658EFI_STATUS
659EFIAPI
660HttpBootGetBootFileCallback (
661  IN HTTP_BODY_PARSE_EVENT      EventType,
662  IN CHAR8                      *Data,
663  IN UINTN                      Length,
664  IN VOID                       *Context
665  )
666{
667  HTTP_BOOT_CALLBACK_DATA      *CallbackData;
668  HTTP_BOOT_ENTITY_DATA        *NewEntityData;
669
670  //
671  // We only care about the entity data.
672  //
673  if (EventType != BodyParseEventOnData) {
674    return EFI_SUCCESS;
675  }
676
677  CallbackData = (HTTP_BOOT_CALLBACK_DATA *) Context;
678  //
679  // Copy data if caller has provided a buffer.
680  //
681  if (CallbackData->BufferSize > CallbackData->CopyedSize) {
682    CopyMem (
683      CallbackData->Buffer + CallbackData->CopyedSize,
684      Data,
685      MIN (Length, CallbackData->BufferSize - CallbackData->CopyedSize)
686      );
687    CallbackData->CopyedSize += MIN (Length, CallbackData->BufferSize - CallbackData->CopyedSize);
688  }
689
690  //
691  // The caller doesn't provide a buffer, save the block into cache list.
692  //
693  if (CallbackData->Cache != NULL) {
694    NewEntityData = AllocatePool (sizeof (HTTP_BOOT_ENTITY_DATA));
695    if (NewEntityData == NULL) {
696      return EFI_OUT_OF_RESOURCES;
697    }
698    if (CallbackData->NewBlock) {
699      NewEntityData->Block = CallbackData->Block;
700      CallbackData->Block = NULL;
701    }
702    NewEntityData->DataLength = Length;
703    NewEntityData->DataStart  = (UINT8*) Data;
704    InsertTailList (&CallbackData->Cache->EntityDataList, &NewEntityData->Link);
705  }
706  return EFI_SUCCESS;
707}
708
709/**
710  This function download the boot file by using UEFI HTTP protocol.
711
712  @param[in]       Private         The pointer to the driver's private data.
713  @param[in]       HeaderOnly      Only request the response header, it could save a lot of time if
714                                   the caller only want to know the size of the requested file.
715  @param[in, out]  BufferSize      On input the size of Buffer in bytes. On output with a return
716                                   code of EFI_SUCCESS, the amount of data transferred to
717                                   Buffer. On output with a return code of EFI_BUFFER_TOO_SMALL,
718                                   the size of Buffer required to retrieve the requested file.
719  @param[out]      Buffer          The memory buffer to transfer the file to. IF Buffer is NULL,
720                                   then the size of the requested file is returned in
721                                   BufferSize.
722  @param[out]      ImageType       The image type of the downloaded file.
723
724  @retval EFI_SUCCESS              The file was loaded.
725  @retval EFI_INVALID_PARAMETER    BufferSize is NULL or Buffer Size is not NULL but Buffer is NULL.
726  @retval EFI_OUT_OF_RESOURCES     Could not allocate needed resources
727  @retval EFI_BUFFER_TOO_SMALL     The BufferSize is too small to read the current directory entry.
728                                   BufferSize has been updated with the size needed to complete
729                                   the request.
730  @retval Others                   Unexpected error happened.
731
732**/
733EFI_STATUS
734HttpBootGetBootFile (
735  IN     HTTP_BOOT_PRIVATE_DATA   *Private,
736  IN     BOOLEAN                  HeaderOnly,
737  IN OUT UINTN                    *BufferSize,
738     OUT UINT8                    *Buffer,
739     OUT HTTP_BOOT_IMAGE_TYPE     *ImageType
740  )
741{
742  EFI_STATUS                 Status;
743  EFI_HTTP_STATUS_CODE       StatusCode;
744  CHAR8                      *HostName;
745  EFI_HTTP_REQUEST_DATA      *RequestData;
746  HTTP_IO_RESPONSE_DATA      *ResponseData;
747  HTTP_IO_RESPONSE_DATA      ResponseBody;
748  HTTP_IO                    *HttpIo;
749  HTTP_IO_HEADER             *HttpIoHeader;
750  VOID                       *Parser;
751  HTTP_BOOT_CALLBACK_DATA    Context;
752  UINTN                      ContentLength;
753  HTTP_BOOT_CACHE_CONTENT    *Cache;
754  UINT8                      *Block;
755  CHAR16                     *Url;
756  BOOLEAN                    IdentityMode;
757  UINTN                      ReceivedSize;
758
759  ASSERT (Private != NULL);
760  ASSERT (Private->HttpCreated);
761
762  if (BufferSize == NULL || ImageType == NULL) {
763    return EFI_INVALID_PARAMETER;
764  }
765
766  if (*BufferSize != 0 && Buffer == NULL) {
767    return EFI_INVALID_PARAMETER;
768  }
769
770  //
771  // First, check whether we already cached the requested Uri.
772  //
773  Url = AllocatePool ((AsciiStrLen (Private->BootFileUri) + 1) * sizeof (CHAR16));
774  if (Url == NULL) {
775    return EFI_OUT_OF_RESOURCES;
776  }
777  AsciiStrToUnicodeStr (Private->BootFileUri, Url);
778  if (!HeaderOnly) {
779    Status = HttpBootGetFileFromCache (Private, Url, BufferSize, Buffer, ImageType);
780    if (Status != EFI_NOT_FOUND) {
781      FreePool (Url);
782      return Status;
783    }
784  }
785
786  //
787  // Not found in cache, try to download it through HTTP.
788  //
789
790  //
791  // 1. Create a temp cache item for the requested URI if caller doesn't provide buffer.
792  //
793  Cache = NULL;
794  if ((!HeaderOnly) && (*BufferSize == 0)) {
795    Cache = AllocateZeroPool (sizeof (HTTP_BOOT_CACHE_CONTENT));
796    if (Cache == NULL) {
797      Status = EFI_OUT_OF_RESOURCES;
798      goto ERROR_1;
799    }
800    Cache->ImageType = ImageTypeMax;
801    InitializeListHead (&Cache->EntityDataList);
802  }
803
804  //
805  // 2. Send HTTP request message.
806  //
807
808  //
809  // 2.1 Build HTTP header for the request, 3 header is needed to download a boot file:
810  //       Host
811  //       Accept
812  //       User-Agent
813  //
814  HttpIoHeader = HttpBootCreateHeader (3);
815  if (HttpIoHeader == NULL) {
816    Status = EFI_OUT_OF_RESOURCES;
817    goto ERROR_2;
818  }
819
820  //
821  // Add HTTP header field 1: Host
822  //
823  HostName = NULL;
824  Status = HttpUrlGetHostName (
825             Private->BootFileUri,
826             Private->BootFileUriParser,
827             &HostName
828             );
829  if (EFI_ERROR (Status)) {
830    goto ERROR_3;
831  }
832  Status = HttpBootSetHeader (
833             HttpIoHeader,
834             HTTP_HEADER_HOST,
835             HostName
836             );
837  FreePool (HostName);
838  if (EFI_ERROR (Status)) {
839    goto ERROR_3;
840  }
841
842  //
843  // Add HTTP header field 2: Accept
844  //
845  Status = HttpBootSetHeader (
846             HttpIoHeader,
847             HTTP_HEADER_ACCEPT,
848             "*/*"
849             );
850  if (EFI_ERROR (Status)) {
851    goto ERROR_3;
852  }
853
854  //
855  // Add HTTP header field 3: User-Agent
856  //
857  Status = HttpBootSetHeader (
858             HttpIoHeader,
859             HTTP_HEADER_USER_AGENT,
860             HTTP_USER_AGENT_EFI_HTTP_BOOT
861             );
862  if (EFI_ERROR (Status)) {
863    goto ERROR_3;
864  }
865
866  //
867  // 2.2 Build the rest of HTTP request info.
868  //
869  RequestData = AllocatePool (sizeof (EFI_HTTP_REQUEST_DATA));
870  if (RequestData == NULL) {
871    Status = EFI_OUT_OF_RESOURCES;
872    goto ERROR_3;
873  }
874  RequestData->Method = HeaderOnly ? HttpMethodHead : HttpMethodGet;
875  RequestData->Url = Url;
876  if (RequestData->Url == NULL) {
877    Status = EFI_OUT_OF_RESOURCES;
878    goto ERROR_4;
879  }
880  AsciiStrToUnicodeStr (Private->BootFileUri, RequestData->Url);
881
882  //
883  // 2.3 Record the request info in a temp cache item.
884  //
885  if (Cache != NULL) {
886    Cache->RequestData = RequestData;
887  }
888
889  //
890  // 2.4 Send out the request to HTTP server.
891  //
892  HttpIo = &Private->HttpIo;
893  Status = HttpIoSendRequest (
894             HttpIo,
895             RequestData,
896             HttpIoHeader->HeaderCount,
897             HttpIoHeader->Headers,
898             0,
899             NULL
900            );
901  if (EFI_ERROR (Status)) {
902    goto ERROR_4;
903  }
904
905  //
906  // 3. Receive HTTP response message.
907  //
908
909  //
910  // 3.1 First step, use zero BodyLength to only receive the response headers.
911  //
912  ResponseData = AllocateZeroPool (sizeof(HTTP_IO_RESPONSE_DATA));
913  if (ResponseData == NULL) {
914    Status = EFI_OUT_OF_RESOURCES;
915    goto ERROR_4;
916  }
917  Status = HttpIoRecvResponse (
918             &Private->HttpIo,
919             TRUE,
920             ResponseData
921             );
922  if (EFI_ERROR (Status) || EFI_ERROR (ResponseData->Status)) {
923    if (EFI_ERROR (ResponseData->Status)) {
924      StatusCode = HttpIo->RspToken.Message->Data.Response->StatusCode;
925      HttpBootPrintErrorMessage (StatusCode);
926      Status = ResponseData->Status;
927    }
928    goto ERROR_5;
929  }
930
931  //
932  // Check the image type according to server's response.
933  //
934  Status = HttpBootCheckImageType (
935             Private->BootFileUri,
936             Private->BootFileUriParser,
937             ResponseData->HeaderCount,
938             ResponseData->Headers,
939             ImageType
940             );
941  if (EFI_ERROR (Status)) {
942    goto ERROR_5;
943  }
944
945  //
946  // 3.2 Cache the response header.
947  //
948  if (Cache != NULL) {
949    Cache->ResponseData = ResponseData;
950    Cache->ImageType = *ImageType;
951  }
952
953  //
954  // 3.3 Init a message-body parser from the header information.
955  //
956  Parser = NULL;
957  Context.NewBlock   = FALSE;
958  Context.Block      = NULL;
959  Context.CopyedSize = 0;
960  Context.Buffer     = Buffer;
961  Context.BufferSize = *BufferSize;
962  Context.Cache      = Cache;
963  Status = HttpInitMsgParser (
964             HeaderOnly? HttpMethodHead : HttpMethodGet,
965             ResponseData->Response.StatusCode,
966             ResponseData->HeaderCount,
967             ResponseData->Headers,
968             HttpBootGetBootFileCallback,
969             (VOID*) &Context,
970             &Parser
971             );
972  if (EFI_ERROR (Status)) {
973    goto ERROR_6;
974  }
975
976  //
977  // 3.4 Continue to receive and parse message-body if needed.
978  //
979  Block = NULL;
980  if (!HeaderOnly) {
981    //
982    // 3.4.1, check whether we are in identity transfer-coding.
983    //
984    ContentLength = 0;
985    Status = HttpGetEntityLength (Parser, &ContentLength);
986    if (!EFI_ERROR (Status)) {
987      IdentityMode = TRUE;
988    } else {
989      IdentityMode = FALSE;
990    }
991
992    //
993    // 3.4.2, start the message-body download, the identity and chunked transfer-coding
994    // is handled in different path here.
995    //
996    ZeroMem (&ResponseBody, sizeof (HTTP_IO_RESPONSE_DATA));
997    if (IdentityMode) {
998      //
999      // In identity transfer-coding there is no need to parse the message body,
1000      // just download the message body to the user provided buffer directly.
1001      //
1002      ReceivedSize = 0;
1003      while (ReceivedSize < ContentLength) {
1004        ResponseBody.Body       = (CHAR8*) Buffer + ReceivedSize;
1005        ResponseBody.BodyLength = *BufferSize - ReceivedSize;
1006        Status = HttpIoRecvResponse (
1007                   &Private->HttpIo,
1008                   FALSE,
1009                   &ResponseBody
1010                   );
1011        if (EFI_ERROR (Status)) {
1012          goto ERROR_6;
1013        }
1014        ReceivedSize += ResponseBody.BodyLength;
1015      }
1016    } else {
1017      //
1018      // In "chunked" transfer-coding mode, so we need to parse the received
1019      // data to get the real entity content.
1020      //
1021      Block = NULL;
1022      while (!HttpIsMessageComplete (Parser)) {
1023        //
1024        // Allocate a buffer in Block to hold the message-body.
1025        // If caller provides a buffer, this Block will be reused in every HttpIoRecvResponse().
1026        // Otherwise a buffer, the buffer in Block will be cached and we should allocate a new before
1027        // every HttpIoRecvResponse().
1028        //
1029        if (Block == NULL || Context.BufferSize == 0) {
1030          Block = AllocatePool (HTTP_BOOT_BLOCK_SIZE);
1031          if (Block == NULL) {
1032            Status = EFI_OUT_OF_RESOURCES;
1033            goto ERROR_6;
1034          }
1035          Context.NewBlock = TRUE;
1036          Context.Block = Block;
1037        } else {
1038          Context.NewBlock = FALSE;
1039        }
1040
1041        ResponseBody.Body       = (CHAR8*) Block;
1042        ResponseBody.BodyLength = HTTP_BOOT_BLOCK_SIZE;
1043        Status = HttpIoRecvResponse (
1044                   &Private->HttpIo,
1045                   FALSE,
1046                   &ResponseBody
1047                   );
1048        if (EFI_ERROR (Status)) {
1049          goto ERROR_6;
1050        }
1051
1052        //
1053        // Parse the new received block of the message-body, the block will be saved in cache.
1054        //
1055        Status = HttpParseMessageBody (
1056                   Parser,
1057                   ResponseBody.BodyLength,
1058                   ResponseBody.Body
1059                   );
1060        if (EFI_ERROR (Status)) {
1061          goto ERROR_6;
1062        }
1063      }
1064    }
1065  }
1066
1067  //
1068  // 3.5 Message-body receive & parse is completed, we should be able to get the file size now.
1069  //
1070  Status = HttpGetEntityLength (Parser, &ContentLength);
1071  if (EFI_ERROR (Status)) {
1072    goto ERROR_6;
1073  }
1074
1075  if (*BufferSize < ContentLength) {
1076    Status = EFI_BUFFER_TOO_SMALL;
1077  } else {
1078    Status = EFI_SUCCESS;
1079  }
1080  *BufferSize = ContentLength;
1081
1082  //
1083  // 4. Save the cache item to driver's cache list and return.
1084  //
1085  if (Cache != NULL) {
1086    Cache->EntityLength = ContentLength;
1087    InsertTailList (&Private->CacheList, &Cache->Link);
1088  }
1089
1090  if (Parser != NULL) {
1091    HttpFreeMsgParser (Parser);
1092  }
1093
1094  return Status;
1095
1096ERROR_6:
1097  if (Parser != NULL) {
1098    HttpFreeMsgParser (Parser);
1099  }
1100  if (Context.Block != NULL) {
1101    FreePool (Context.Block);
1102  }
1103  HttpBootFreeCache (Cache);
1104
1105ERROR_5:
1106  if (ResponseData != NULL) {
1107    FreePool (ResponseData);
1108  }
1109ERROR_4:
1110  if (RequestData != NULL) {
1111    FreePool (RequestData);
1112  }
1113ERROR_3:
1114  HttpBootFreeHeader (HttpIoHeader);
1115ERROR_2:
1116  if (Cache != NULL) {
1117    FreePool (Cache);
1118  }
1119ERROR_1:
1120  if (Url != NULL) {
1121    FreePool (Url);
1122  }
1123
1124  return Status;
1125}
1126
1127