tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

download_firefox.cpp (11364B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "download_firefox.h"
      6 #include <iostream>
      7 #include <optional>
      8 #include <string>
      9 #include <wchar.h>
     10 #include <windows.h>
     11 #include <winhttp.h>
     12 #include "data_sink.h"
     13 
     14 static const wchar_t* user_agent = L"FirefoxDesktopLauncher/0.1.0";
     15 
     16 // This is how long we allow users to wait before giving up on the download.
     17 static const DWORD timeout_ms = 5000;
     18 static const int BUFFER_SIZE = 1 << 16;
     19 static char buffer[BUFFER_SIZE];
     20 
     21 struct DownloadContext {
     22  HINTERNET hsession;
     23  HINTERNET hconnection;
     24  HINTERNET hrequest;
     25  DataSink* dataSink;
     26  std::wstring contentType;
     27  HANDLE eventHandle;
     28  ErrCode asyncStatus;
     29 };
     30 
     31 std::optional<std::wstring> get_architecture() {
     32  SYSTEM_INFO sysinfo;
     33  GetSystemInfo(&sysinfo);
     34  switch (sysinfo.wProcessorArchitecture) {
     35    case PROCESSOR_ARCHITECTURE_AMD64:
     36      return L"win64";
     37    case PROCESSOR_ARCHITECTURE_INTEL:
     38      return L"win";
     39    case PROCESSOR_ARCHITECTURE_ARM64:
     40      return L"win64-aarch64";
     41    default:
     42      return std::nullopt;
     43  }
     44 }
     45 
     46 /**
     47 * Generate the path and query parameters needed for the request
     48 * to download the Firefox stub installer. The object name
     49 * includes the user's locale, which indicates which language/locale
     50 * version of Firefox to download.
     51 * @return The path and query params for including in the HTTP request
     52 */
     53 std::optional<std::wstring> get_object_name() {
     54  wchar_t locale_name[LOCALE_NAME_MAX_LENGTH]{};
     55  int ct = GetUserDefaultLocaleName(locale_name, sizeof locale_name);
     56  if (ct == 0) {
     57    return std::nullopt;
     58  }
     59  std::wstring lang = locale_name;
     60  std::optional<std::wstring> arch = get_architecture();
     61  if (!arch.has_value()) {
     62    return std::nullopt;
     63  }
     64 
     65 #if defined(MOZ_BRANDING_IS_OFFICIAL)
     66  // Common case: download stub installer for release. Note that ESR releases
     67  // will (eventually) also go this route, and the stub installer is responsible
     68  // for installing the supported release for the new machine.
     69  std::wstring product = L"firefox-stub";
     70 #elif defined(MOZ_BRANDING_IS_NIGHTLY)
     71  // Nightly build: download the latest Firefox Nightly installer
     72  std::wstring product = L"firefox-nightly-stub";
     73 #elif defined(MOZ_BRANDING_IS_BETA)
     74  // Beta edition build: download the latest Firefox Beta installer
     75  std::wstring product = L"firefox-beta-stub";
     76 #elif defined(MOZ_BRANDING_IS_DEVEDITION)
     77  // Dev edition build: download the latest Firefox Devevloper Edition installer
     78  std::wstring product = L"firefox-devedition-stub";
     79 #elif defined(MOZ_BRANDING_IS_UNOFFICIAL)
     80  // For unofficial/local builds, download the nightly version. The advantage of
     81  // this, over the release version, is that it uses the full installer, which
     82  // gives the user the chance to cancel installation.
     83  std::wstring product = L"firefox-nightly-stub";
     84 #else
     85  // Unexpected case. Fail the build here so we can figure out the missing case.
     86  static_assert(false);
     87 #endif
     88 
     89  return L"?os=" + arch.value() + L"&lang=" + lang + L"&product=" + product;
     90 }
     91 
     92 /**
     93 * To exit from the WinHttp callback, you remove the callback from the
     94 * request object. Additionally, since the download_firefox function is
     95 * blocked waiting for completion, we need to signal the event that it is
     96 * waiting on.
     97 */
     98 static void exitCallback(DownloadContext* context, ErrCode exitStatus) {
     99  context->asyncStatus = exitStatus;
    100  WinHttpSetStatusCallback(context->hrequest, nullptr,
    101                           WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, NULL);
    102  SetEvent(context->eventHandle);
    103 }
    104 
    105 /**
    106 * Async event handler for the WinHttp request, satisfying type
    107 * WINHTTP_STATUS_CALLBACK
    108 */
    109 static void CALLBACK AsyncHttpStatusCallback(HINTERNET hInternet,
    110                                             DWORD_PTR dwContext,
    111                                             DWORD dwInternetStatus,
    112                                             void* lpvStatusInformation,
    113                                             DWORD dwStatusInformationLength) {
    114  DownloadContext* context = (DownloadContext*)dwContext;
    115  DWORD dwCount = 0;
    116  DWORD dwResponseStatus;
    117  wchar_t contentTypeBuf[256]{};
    118  switch (dwInternetStatus) {
    119    case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
    120      // We have completed sending the request. Now tell the API to receive a
    121      // response.
    122      if (!WinHttpReceiveResponse(context->hrequest, nullptr)) {
    123        exitCallback(context, ErrCode::ERR_RECEIVE);
    124      }
    125      break;
    126    case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: {
    127      dwCount = sizeof(DWORD);
    128      if (!WinHttpQueryHeaders(
    129              context->hrequest,
    130              WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
    131              WINHTTP_HEADER_NAME_BY_INDEX, &dwResponseStatus, &dwCount,
    132              WINHTTP_NO_HEADER_INDEX)) {
    133        exitCallback(context, ErrCode::ERR_HEADER);
    134        return;
    135      }
    136      if (dwResponseStatus != 200) {
    137        if (dwResponseStatus == 404) {
    138          exitCallback(context, ErrCode::ERR_FILE_NOT_FOUND);
    139        } else if (dwResponseStatus >= 400 && dwResponseStatus < 500) {
    140          exitCallback(context, ErrCode::ERR_CLIENT_REQUEST);
    141        } else {
    142          exitCallback(context, ErrCode::ERR_SERVER);
    143        }
    144        return;
    145      }
    146      dwCount = sizeof contentTypeBuf;
    147      if (!WinHttpQueryHeaders(context->hrequest, WINHTTP_QUERY_CONTENT_TYPE,
    148                               WINHTTP_HEADER_NAME_BY_INDEX, contentTypeBuf,
    149                               &dwCount, WINHTTP_NO_HEADER_INDEX)) {
    150        exitCallback(context, ErrCode::ERR_HEADER);
    151        return;
    152      }
    153      // We have received the headers. Call query data to start the reading loop
    154      if (!WinHttpQueryDataAvailable(context->hrequest, nullptr)) {
    155        exitCallback(context, ErrCode::ERR_QUERY_DATA);
    156      }
    157    } break;
    158    case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
    159      // We have data available. Tell the API to put it in our buffer
    160      dwCount = *((DWORD*)lpvStatusInformation);
    161      if (dwCount == 0) {
    162        // nothing available. We must be done.
    163        exitCallback(context, ErrCode::OK);
    164        return;
    165      }
    166      if (dwCount > sizeof buffer) {
    167        dwCount = sizeof buffer;
    168      }
    169      ZeroMemory(buffer, sizeof buffer);
    170      if (!WinHttpReadData(context->hrequest, buffer, dwCount, nullptr)) {
    171        exitCallback(context, ErrCode::ERR_READ_DATA);
    172      }
    173      break;
    174 
    175    case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
    176      dwCount = dwStatusInformationLength;
    177      if (!context->dataSink->accept((char*)lpvStatusInformation, dwCount)) {
    178        exitCallback(context, ErrCode::ERR_FILE);
    179      }
    180      // Is there more?
    181      else if (!WinHttpQueryDataAvailable(context->hrequest, nullptr)) {
    182        exitCallback(context, ErrCode::ERR_QUERY_DATA);
    183      }
    184      break;
    185 
    186    case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
    187      exitCallback(context, ErrCode::ERR_REQUEST_INVALID);
    188      break;
    189  }
    190 }
    191 
    192 /**
    193 * Attempt to download a file from an HTTP service, sinking its data to the
    194 * specified DataSink object
    195 * @param dataSink A DataSink object that will accept downloaded data
    196 * @param server_name The DNS name of the HTTP server
    197 * @param server_port The port number for the HTTP service
    198 * @param is_https Whether this is a secure HTTPS service or not
    199 * @param object_name the file path and query parameters to include in the HTTP
    200 * request
    201 * @return An ErrCode enum that indicates the success or failure of the download
    202 * attempt
    203 */
    204 ErrCode download_file(DataSink* dataSink, std::wstring server_name,
    205                      int server_port, bool is_https, std::wstring object_name,
    206                      std::wstring contentType) {
    207  DownloadContext context{};
    208  context.asyncStatus = ErrCode::UNKNOWN;
    209  context.dataSink = dataSink;
    210  context.contentType = contentType;
    211  const wchar_t* accept_types[] = {contentType.c_str(), nullptr};
    212  // Create an event to be used in signaling between our WinHttp callback, which
    213  // runs asynchronously, and this function.
    214  context.eventHandle = CreateEventW(nullptr, false, false, nullptr);
    215  if (!context.eventHandle) {
    216    return ErrCode::ERR_EVENT;
    217  }
    218  // Initiate a WinHttp session.
    219  // Note: The WINHTTP_FLAG_SECURE_DEFAULTS flag instructs WinHttp to use secure
    220  // settings, such as disabling fallback to old versions of TLS, but it has the
    221  // side-effect of also forcing the session to be in async mode.
    222  context.hsession =
    223      WinHttpOpen(user_agent, WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY,
    224                  WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS,
    225                  is_https ? WINHTTP_FLAG_SECURE_DEFAULTS : WINHTTP_FLAG_ASYNC);
    226  if (context.hsession == nullptr) {
    227    return ErrCode::ERR_OPEN;
    228  }
    229  // Create a (disconnected) connection by specifying the server and port
    230  context.hconnection =
    231      WinHttpConnect(context.hsession, server_name.c_str(), server_port, 0);
    232  if (!context.hconnection) {
    233    return ErrCode::ERR_CONNECT;
    234  }
    235  // Create an HTTP request object by specifying the verb (GET) and name
    236  // (path/params) for the URL, as well as some other properties.
    237  context.hrequest = WinHttpOpenRequest(
    238      context.hconnection, L"GET", object_name.c_str(), nullptr,
    239      WINHTTP_NO_REFERER, accept_types, is_https ? WINHTTP_FLAG_SECURE : 0);
    240  if (!context.hrequest) {
    241    return ErrCode::ERR_OPEN_REQ;
    242  }
    243  WINHTTP_STATUS_CALLBACK statusCallback = AsyncHttpStatusCallback;
    244  // Register the async callback to be used in handling the request
    245  if (WinHttpSetStatusCallback(context.hrequest, statusCallback,
    246                               WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS,
    247                               NULL) == WINHTTP_INVALID_STATUS_CALLBACK) {
    248    return ErrCode::ERR_SET_CALLBACK;
    249  }
    250  // Actually send the request
    251  if (!WinHttpSendRequest(context.hrequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
    252                          WINHTTP_NO_REQUEST_DATA, 0, 0, (DWORD_PTR)&context)) {
    253    return ErrCode::ERR_SEND;
    254  }
    255 
    256  // Wait for the async request to complete
    257  ErrCode result;
    258  if (WaitForSingleObject(context.eventHandle, timeout_ms) == WAIT_OBJECT_0) {
    259    // async request completed
    260    result = context.asyncStatus;
    261  } else {
    262    // Timed out waiting for the async request to complete
    263    result = ErrCode::ERR_TIMEOUT;
    264  }
    265  return result;
    266 }
    267 
    268 static const wchar_t* server_name = L"download.mozilla.org";
    269 static const wchar_t* installer_content_type = L"application/x-msdos-program";
    270 static const int standard_server_port = INTERNET_DEFAULT_HTTPS_PORT;
    271 static const bool standard_is_https = true;
    272 
    273 /**
    274 * Attempt to download the Firefox stub installer, sinking its data to the
    275 * specified DataSink object
    276 * @param dataSink A DataSink object that will accept downloaded data
    277 * @return An ErrCode enum that indicates the success or failure of the download
    278 * attempt
    279 */
    280 ErrCode download_firefox(DataSink* dataSink) {
    281  std::optional<std::wstring> object_name_opt = get_object_name();
    282  std::wstring object_name;
    283  if (object_name_opt.has_value()) {
    284    object_name = object_name_opt.value();
    285  } else {
    286    return ErrCode::ERR_ENVIRON;
    287  }
    288 
    289  return download_file(dataSink, server_name, standard_server_port,
    290                       standard_is_https, object_name, installer_content_type);
    291 }