tor-browser

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

InetBgDL.cpp (23305B)


      1 //
      2 // Copyright (C) Anders Kjersem. Licensed under the zlib/libpng license
      3 //
      4 
      5 // This file is intended to be compiled with MSVC's Omit Default Library Name (/Zl)
      6 // option enabled, in order to keep the file size low for bundling this DLL with
      7 // the stub installer. That means that any code requiring the C runtime will fail
      8 // to link. You'll see a couple of odd-looking things here for this reason; they
      9 // should all be called out with comments.
     10 
     11 #include "InetBgDL.h"
     12 
     13 #define USERAGENT _T("NSIS InetBgDL (Mozilla 2024)")
     14 
     15 #define STATUS_COMPLETEDALL 0
     16 #define STATUS_INITIAL 202
     17 #define STATUS_CONNECTING STATUS_INITIAL //102
     18 #define STATUS_DOWNLOADING STATUS_INITIAL
     19 #define STATUS_ERR_GETLASTERROR 418 //HTTP: I'm a teapot: Win32 error code in $3
     20 #define STATUS_ERR_LOCALFILEWRITEERROR 450 //HTTP: MS parental control extension
     21 #define STATUS_ERR_CANCELLED 499
     22 #define STATUS_ERR_CONNECTION_LOST 1000
     23 
     24 typedef DWORD FILESIZE_T; // Limit to 4GB for now...
     25 #define FILESIZE_UNKNOWN (-1)
     26 
     27 #define MAX_STRLEN 1024
     28 
     29 HINSTANCE g_hInst;
     30 NSIS::stack_t*g_pLocations = NULL;
     31 HANDLE g_hThread = NULL;
     32 HANDLE g_hGETStartedEvent = NULL;
     33 HINTERNET g_hInetSes = NULL;
     34 HINTERNET g_hInetFile = NULL;
     35 volatile UINT g_FilesTotal = 0;
     36 volatile UINT g_FilesCompleted = 0;
     37 volatile UINT g_Status = STATUS_INITIAL;
     38 volatile FILESIZE_T g_cbCurrXF;
     39 volatile FILESIZE_T g_cbCurrTot = FILESIZE_UNKNOWN;
     40 CRITICAL_SECTION g_CritLock;
     41 UINT g_N_CCH;
     42 PTSTR g_N_Vars;
     43 TCHAR g_ServerIP[128] = { _T('\0') };
     44 
     45 DWORD g_ConnectTimeout = 0;
     46 DWORD g_ReceiveTimeout = 0;
     47 
     48 // Setup a buffer of size 256KiB to store the downloaded data.
     49 constexpr UINT g_cbBufXF = 262144;
     50 // This buffer is only needed inside TaskThreadProc(), but declaring it on
     51 // the stack there triggers a runtime stack size check, which is implemented
     52 // by a C runtime library function, so we have to avoid the compiler wanting
     53 // to build that check by not having any large stack buffers.
     54 BYTE g_bufXF[g_cbBufXF];
     55 
     56 #define NSISPI_INITGLOBALS(N_CCH, N_Vars) do { \
     57  g_N_CCH = N_CCH; \
     58  g_N_Vars = N_Vars; \
     59  } while(0)
     60 
     61 #define ONELOCKTORULETHEMALL
     62 #ifdef ONELOCKTORULETHEMALL
     63 #define TaskLock_AcquireExclusive() EnterCriticalSection(&g_CritLock)
     64 #define TaskLock_ReleaseExclusive() LeaveCriticalSection(&g_CritLock)
     65 #define StatsLock_AcquireExclusive() TaskLock_AcquireExclusive()
     66 #define StatsLock_ReleaseExclusive() TaskLock_ReleaseExclusive()
     67 #define StatsLock_AcquireShared() StatsLock_AcquireExclusive()
     68 #define StatsLock_ReleaseShared() StatsLock_ReleaseExclusive()
     69 #endif
     70 
     71 // Normally we would just call the C library wcstol, but since we can't use the
     72 // C runtime, we'll supply our own function as an understudy.
     73 static DWORD
     74 MyTStrToL(TCHAR const* str)
     75 {
     76  if (!str) {
     77    return 0;
     78  }
     79 
     80  int len = lstrlen(str);
     81  DWORD place = 1;
     82  DWORD rv = 0;
     83  for (int i = len - 1; i >= 0; --i) {
     84    int digit = str[i] - 0x30;
     85    rv += digit * place;
     86    place *= 10;
     87  }
     88  return rv;
     89 }
     90 
     91 PTSTR NSIS_SetRegStr(UINT Reg, LPCTSTR Value)
     92 {
     93  PTSTR s = g_N_Vars + (Reg * g_N_CCH);
     94  lstrcpy(s, Value);
     95  return s;
     96 }
     97 #define NSIS_SetRegStrEmpty(r) NSIS_SetRegStr(r, _T(""))
     98 void NSIS_SetRegUINT(UINT Reg, UINT Value)
     99 {
    100  TCHAR buf[32];
    101  wsprintf(buf, _T("%u"), Value);
    102  NSIS_SetRegStr(Reg, buf);
    103 }
    104 #define StackFreeItem(pI) GlobalFree(pI)
    105 NSIS::stack_t* StackPopItem(NSIS::stack_t**ppST)
    106 {
    107  if (*ppST)
    108  {
    109    NSIS::stack_t*pItem = *ppST;
    110    *ppST = pItem->next;
    111    return pItem;
    112  }
    113  return NULL;
    114 }
    115 
    116 void Reset()
    117 {
    118  // The g_hGETStartedEvent event is used to make sure that the Get() call will
    119  // acquire the lock before the Reset() call acquires the lock.
    120  if (g_hGETStartedEvent) {
    121    TRACE(_T("InetBgDl: waiting on g_hGETStartedEvent\n"));
    122    WaitForSingleObject(g_hGETStartedEvent, INFINITE);
    123    CloseHandle(g_hGETStartedEvent);
    124    g_hGETStartedEvent = NULL;
    125  }
    126 
    127  TaskLock_AcquireExclusive();
    128 #ifndef ONELOCKTORULETHEMALL
    129  StatsLock_AcquireExclusive();
    130 #endif
    131  g_FilesTotal = 0; // This causes the Task thread to exit the transfer loop
    132  if (g_hThread)
    133  {
    134    TRACE(_T("InetBgDl: waiting on g_hThread\n"));
    135    if (WAIT_OBJECT_0 != WaitForSingleObject(g_hThread, 5 * 1000))
    136    {
    137      TRACE(_T("InetBgDl: terminating g_hThread\n"));
    138      // Suspend the thread so that it's not still trying to use these handles
    139      // that we're about to close out from under it.
    140      SuspendThread(g_hThread);
    141      if (g_hInetFile) {
    142        InternetCloseHandle(g_hInetFile);
    143        g_hInetFile = nullptr;
    144      }
    145      if (g_hInetSes) {
    146        InternetCloseHandle(g_hInetSes);
    147        g_hInetSes = nullptr;
    148      }
    149      TerminateThread(g_hThread, ERROR_OPERATION_ABORTED);
    150    }
    151    CloseHandle(g_hThread);
    152    g_hThread = NULL;
    153  }
    154  g_FilesTotal = 0;
    155  g_FilesCompleted = 0;
    156  g_Status = STATUS_INITIAL;
    157 #ifndef ONELOCKTORULETHEMALL
    158  StatsLock_ReleaseExclusive();
    159 #endif
    160  for (NSIS::stack_t*pTmpTast,*pTask = g_pLocations; pTask ;)
    161  {
    162    pTmpTast = pTask;
    163    pTask = pTask->next;
    164    StackFreeItem(pTmpTast);
    165  }
    166  g_pLocations = NULL;
    167  TaskLock_ReleaseExclusive();
    168 }
    169 
    170 UINT_PTR __cdecl NSISPluginCallback(UINT Event)
    171 {
    172  switch(Event)
    173  {
    174  case NSPIM_UNLOAD:
    175    Reset();
    176    break;
    177  }
    178  return NULL;
    179 }
    180 
    181 void __stdcall InetStatusCallback(HINTERNET hInternet, DWORD_PTR dwContext,
    182                                  DWORD dwInternetStatus,
    183                                  LPVOID lpvStatusInformation,
    184                                  DWORD dwStatusInformationLength)
    185 {
    186  if (dwInternetStatus == INTERNET_STATUS_NAME_RESOLVED) {
    187    // If we're in the process of being reset, don't try to update g_ServerIP;
    188    // there's no need for it, and Reset() will be holding the StatsLock, so
    189    // we'll hang here and block the reset if we try to acquire it.
    190    if (g_FilesTotal != 0) {
    191      // The documentation states the IP address is a PCTSTR but it is usually a
    192      // PCSTR and only sometimes a PCTSTR.
    193      StatsLock_AcquireExclusive();
    194      wsprintf(g_ServerIP, _T("%S"), lpvStatusInformation);
    195      if (lstrlen(g_ServerIP) == 1)
    196      {
    197        wsprintf(g_ServerIP, _T("%s"), lpvStatusInformation);
    198      }
    199      StatsLock_ReleaseExclusive();
    200    }
    201  }
    202 
    203 #if defined(PLUGIN_DEBUG)
    204  switch (dwInternetStatus)
    205  {
    206    case INTERNET_STATUS_RESOLVING_NAME:
    207      TRACE(_T("InetBgDl: INTERNET_STATUS_RESOLVING_NAME (%d), name=%s\n"),
    208            dwStatusInformationLength, lpvStatusInformation);
    209      break;
    210    case INTERNET_STATUS_NAME_RESOLVED:
    211      TRACE(_T("InetBgDl: INTERNET_STATUS_NAME_RESOLVED (%d), resolved name=%s\n"),
    212            dwStatusInformationLength, g_ServerIP);
    213      break;
    214    case INTERNET_STATUS_CONNECTING_TO_SERVER:
    215      TRACE(_T("InetBgDl: INTERNET_STATUS_CONNECTING_TO_SERVER (%d)\n"),
    216            dwStatusInformationLength);
    217      break;
    218    case INTERNET_STATUS_CONNECTED_TO_SERVER:
    219      TRACE(_T("InetBgDl: INTERNET_STATUS_CONNECTED_TO_SERVER (%d)\n"),
    220            dwStatusInformationLength);
    221      break;
    222    case INTERNET_STATUS_SENDING_REQUEST:
    223      TRACE(_T("InetBgDl: INTERNET_STATUS_SENDING_REQUEST (%d)\n"),
    224               dwStatusInformationLength);
    225      break;
    226    case INTERNET_STATUS_REQUEST_SENT:
    227      TRACE(_T("InetBgDl: INTERNET_STATUS_REQUEST_SENT (%d), bytes sent=%d\n"),
    228             dwStatusInformationLength, lpvStatusInformation);
    229      break;
    230    case INTERNET_STATUS_RECEIVING_RESPONSE:
    231      TRACE(_T("InetBgDl: INTERNET_STATUS_RECEIVING_RESPONSE (%d)\n"),
    232            dwStatusInformationLength);
    233      break;
    234    case INTERNET_STATUS_RESPONSE_RECEIVED:
    235      TRACE(_T("InetBgDl: INTERNET_STATUS_RESPONSE_RECEIVED (%d)\n"),
    236            dwStatusInformationLength);
    237      break;
    238    case INTERNET_STATUS_CTL_RESPONSE_RECEIVED:
    239      TRACE(_T("InetBgDl: INTERNET_STATUS_CTL_RESPONSE_RECEIVED (%d)\n"),
    240            dwStatusInformationLength);
    241      break;
    242    case INTERNET_STATUS_PREFETCH:
    243      TRACE(_T("InetBgDl: INTERNET_STATUS_PREFETCH (%d)\n"),
    244            dwStatusInformationLength);
    245      break;
    246    case INTERNET_STATUS_CLOSING_CONNECTION:
    247      TRACE(_T("InetBgDl: INTERNET_STATUS_CLOSING_CONNECTION (%d)\n"),
    248            dwStatusInformationLength);
    249      break;
    250    case INTERNET_STATUS_CONNECTION_CLOSED:
    251      TRACE(_T("InetBgDl: INTERNET_STATUS_CONNECTION_CLOSED (%d)\n"),
    252            dwStatusInformationLength);
    253      break;
    254    case INTERNET_STATUS_HANDLE_CREATED:
    255      TRACE(_T("InetBgDl: INTERNET_STATUS_HANDLE_CREATED (%d)\n"),
    256            dwStatusInformationLength);
    257      break;
    258    case INTERNET_STATUS_HANDLE_CLOSING:
    259      TRACE(_T("InetBgDl: INTERNET_STATUS_HANDLE_CLOSING (%d)\n"),
    260            dwStatusInformationLength);
    261      break;
    262    case INTERNET_STATUS_DETECTING_PROXY:
    263      TRACE(_T("InetBgDl: INTERNET_STATUS_DETECTING_PROXY (%d)\n"),
    264            dwStatusInformationLength);
    265      break;
    266    case INTERNET_STATUS_REQUEST_COMPLETE:
    267      TRACE(_T("InetBgDl: INTERNET_STATUS_REQUEST_COMPLETE (%d)\n"),
    268            dwStatusInformationLength);
    269      break;
    270    case INTERNET_STATUS_REDIRECT:
    271      TRACE(_T("InetBgDl: INTERNET_STATUS_REDIRECT (%d), new url=%s\n"),
    272            dwStatusInformationLength, lpvStatusInformation);
    273      break;
    274    case INTERNET_STATUS_INTERMEDIATE_RESPONSE:
    275      TRACE(_T("InetBgDl: INTERNET_STATUS_INTERMEDIATE_RESPONSE (%d)\n"),
    276            dwStatusInformationLength);
    277      break;
    278    case INTERNET_STATUS_USER_INPUT_REQUIRED:
    279      TRACE(_T("InetBgDl: INTERNET_STATUS_USER_INPUT_REQUIRED (%d)\n"),
    280            dwStatusInformationLength);
    281      break;
    282    case INTERNET_STATUS_STATE_CHANGE:
    283      TRACE(_T("InetBgDl: INTERNET_STATUS_STATE_CHANGE (%d)\n"),
    284            dwStatusInformationLength);
    285      break;
    286    case INTERNET_STATUS_COOKIE_SENT:
    287      TRACE(_T("InetBgDl: INTERNET_STATUS_COOKIE_SENT (%d)\n"),
    288            dwStatusInformationLength);
    289      break;
    290    case INTERNET_STATUS_COOKIE_RECEIVED:
    291      TRACE(_T("InetBgDl: INTERNET_STATUS_COOKIE_RECEIVED (%d)\n"),
    292            dwStatusInformationLength);
    293      break;
    294    case INTERNET_STATUS_PRIVACY_IMPACTED:
    295      TRACE(_T("InetBgDl: INTERNET_STATUS_PRIVACY_IMPACTED (%d)\n"),
    296            dwStatusInformationLength);
    297      break;
    298    case INTERNET_STATUS_P3P_HEADER:
    299      TRACE(_T("InetBgDl: INTERNET_STATUS_P3P_HEADER (%d)\n"),
    300            dwStatusInformationLength);
    301      break;
    302    case INTERNET_STATUS_P3P_POLICYREF:
    303      TRACE(_T("InetBgDl: INTERNET_STATUS_P3P_POLICYREF (%d)\n"),
    304            dwStatusInformationLength);
    305      break;
    306    case INTERNET_STATUS_COOKIE_HISTORY:
    307      TRACE(_T("InetBgDl: INTERNET_STATUS_COOKIE_HISTORY (%d)\n"),
    308            dwStatusInformationLength);
    309      break;
    310    default:
    311      TRACE(_T("InetBgDl: Unknown Status %d\n"), dwInternetStatus);
    312      break;
    313  }
    314 #endif
    315 }
    316 
    317 DWORD CALLBACK TaskThreadProc(LPVOID ThreadParam)
    318 {
    319  NSIS::stack_t *pURL,*pFile;
    320  DWORD cbio = sizeof(DWORD);
    321  DWORD previouslyWritten = 0, writtenThisSession = 0;
    322  HANDLE hLocalFile;
    323  bool completedFile = false;
    324 startnexttask:
    325  hLocalFile = INVALID_HANDLE_VALUE;
    326  pFile = NULL;
    327  TaskLock_AcquireExclusive();
    328  // Now that we've acquired the lock, we can set the event to indicate this.
    329  // SetEvent will likely never fail, but if it does we should set it to NULL
    330  // to avoid anyone waiting on it.
    331  if (!SetEvent(g_hGETStartedEvent)) {
    332    CloseHandle(g_hGETStartedEvent);
    333    g_hGETStartedEvent = NULL;
    334  }
    335  pURL = g_pLocations;
    336  if (pURL)
    337  {
    338    pFile = pURL->next;
    339    g_pLocations = pFile->next;
    340  }
    341 #ifndef ONELOCKTORULETHEMALL
    342  StatsLock_AcquireExclusive();
    343 #endif
    344  if (completedFile)
    345  {
    346    ++g_FilesCompleted;
    347  }
    348  completedFile = false;
    349  g_cbCurrXF = 0;
    350  g_cbCurrTot = FILESIZE_UNKNOWN;
    351  if (!pURL)
    352  {
    353    if (g_FilesTotal)
    354    {
    355      if (g_FilesTotal == g_FilesCompleted)
    356      {
    357        g_Status = STATUS_COMPLETEDALL;
    358      }
    359    }
    360    g_hThread = NULL;
    361  }
    362 #ifndef ONELOCKTORULETHEMALL
    363  StatsLock_ReleaseExclusive();
    364 #endif
    365  TaskLock_ReleaseExclusive();
    366 
    367  if (!pURL)
    368  {
    369    if (0)
    370    {
    371 diegle:
    372      DWORD gle = GetLastError();
    373      //TODO? if (ERROR_INTERNET_EXTENDED_ERROR==gle) InternetGetLastResponseInfo(...)
    374      g_Status = STATUS_ERR_GETLASTERROR;
    375    }
    376 die:
    377    if (g_hInetSes)
    378    {
    379      InternetCloseHandle(g_hInetSes);
    380      g_hInetSes = nullptr;
    381    }
    382    if (INVALID_HANDLE_VALUE != hLocalFile)
    383    {
    384      CloseHandle(hLocalFile);
    385    }
    386    StackFreeItem(pURL);
    387    StackFreeItem(pFile);
    388    return 0;
    389  }
    390 
    391  if (!g_hInetSes)
    392  {
    393    g_hInetSes = InternetOpen(USERAGENT, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
    394    if (!g_hInetSes)
    395    {
    396      TRACE(_T("InetBgDl: InternetOpen failed with gle=%u\n"),
    397            GetLastError());
    398      goto diegle;
    399    }
    400    InternetSetStatusCallback(g_hInetSes, (INTERNET_STATUS_CALLBACK)InetStatusCallback);
    401 
    402    //msdn.microsoft.com/library/default.asp?url=/workshop/components/offline/offline.asp#Supporting Offline Browsing in Applications and Components
    403    ULONG longOpt;
    404    DWORD cbio = sizeof(ULONG);
    405    if (InternetQueryOption(g_hInetSes, INTERNET_OPTION_CONNECTED_STATE, &longOpt, &cbio))
    406    {
    407      if (INTERNET_STATE_DISCONNECTED_BY_USER&longOpt)
    408      {
    409        INTERNET_CONNECTED_INFO ci = {INTERNET_STATE_CONNECTED, 0};
    410        InternetSetOption(g_hInetSes, INTERNET_OPTION_CONNECTED_STATE, &ci, sizeof(ci));
    411      }
    412    }
    413 
    414    // Change the default connect timeout if specified.
    415    if(g_ConnectTimeout > 0)
    416    {
    417      InternetSetOption(g_hInetSes, INTERNET_OPTION_CONNECT_TIMEOUT,
    418                        &g_ConnectTimeout, sizeof(g_ConnectTimeout));
    419    }
    420 
    421    // Change the default receive timeout if specified.
    422    if (g_ReceiveTimeout)
    423    {
    424      InternetSetOption(g_hInetSes, INTERNET_OPTION_RECEIVE_TIMEOUT,
    425                        &g_ReceiveTimeout, sizeof(DWORD));
    426    }
    427  }
    428 
    429  DWORD ec = ERROR_SUCCESS;
    430  hLocalFile = CreateFile(pFile->text, GENERIC_READ | GENERIC_WRITE,
    431                          FILE_SHARE_READ | FILE_SHARE_DELETE,
    432                          NULL, OPEN_ALWAYS, 0, NULL);
    433  if (INVALID_HANDLE_VALUE == hLocalFile)
    434  {
    435    TRACE(_T("InetBgDl: CreateFile file handle invalid\n"));
    436    goto diegle;
    437  }
    438  if (GetLastError() == ERROR_ALREADY_EXISTS) {
    439    // Resuming a download that was started earlier and then aborted.
    440    previouslyWritten = GetFileSize(hLocalFile, NULL);
    441    g_cbCurrXF = previouslyWritten;
    442    SetFilePointer(hLocalFile, previouslyWritten, NULL, FILE_BEGIN);
    443  }
    444 
    445  const DWORD IOURedirFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP |
    446                              INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS;
    447  const DWORD IOUCacheFlags = INTERNET_FLAG_RESYNCHRONIZE |
    448                              INTERNET_FLAG_NO_CACHE_WRITE |
    449                              INTERNET_FLAG_PRAGMA_NOCACHE |
    450                              INTERNET_FLAG_RELOAD;
    451  const DWORD IOUCookieFlags = INTERNET_FLAG_NO_COOKIES;
    452  DWORD IOUFlags = IOURedirFlags | IOUCacheFlags | IOUCookieFlags |
    453                   INTERNET_FLAG_NO_UI | INTERNET_FLAG_EXISTING_CONNECT;
    454 
    455  TCHAR *hostname = (TCHAR*) GlobalAlloc(GPTR, MAX_STRLEN * sizeof(TCHAR)),
    456        *urlpath = (TCHAR*) GlobalAlloc(GPTR, MAX_STRLEN * sizeof(TCHAR)),
    457        *extrainfo = (TCHAR*) GlobalAlloc(GPTR, MAX_STRLEN * sizeof(TCHAR));
    458 
    459  URL_COMPONENTS uc = { sizeof(URL_COMPONENTS), NULL, 0, (INTERNET_SCHEME)0,
    460                        hostname, MAX_STRLEN, (INTERNET_PORT)0, NULL, 0,
    461                        NULL, 0, urlpath, MAX_STRLEN, extrainfo, MAX_STRLEN};
    462  uc.dwHostNameLength = uc.dwUrlPathLength = uc.dwExtraInfoLength = MAX_STRLEN;
    463 
    464  if (!InternetCrackUrl(pURL->text, 0, ICU_ESCAPE, &uc))
    465  {
    466    // Bad url or param passed in
    467    TRACE(_T("InetBgDl: InternetCrackUrl false with url=%s, gle=%u\n"),
    468          pURL->text, GetLastError());
    469    goto diegle;
    470  }
    471 
    472  TRACE(_T("InetBgDl: scheme_id=%d, hostname=%s, port=%d, urlpath=%s, extrainfo=%s\n"),
    473        uc.nScheme, hostname, uc.nPort, urlpath, extrainfo);
    474 
    475  // Only http and https are supported
    476  if (uc.nScheme != INTERNET_SCHEME_HTTP &&
    477      uc.nScheme != INTERNET_SCHEME_HTTPS)
    478  {
    479    TRACE(_T("InetBgDl: only http and https is supported, aborting...\n"));
    480    goto diegle;
    481  }
    482 
    483  // Tell the server to pick up wherever we left off.
    484  TCHAR headers[32];
    485  // We're skipping building the C runtime to keep the file size low, so we
    486  // can't use a normal string initialization because that would call memset.
    487  headers[0] = _T('\0');
    488  wsprintf(headers, _T("Range: bytes=%d-\r\n"), previouslyWritten);
    489 
    490  TRACE(_T("InetBgDl: calling InternetOpenUrl with url=%s\n"), pURL->text);
    491  g_hInetFile = InternetOpenUrl(g_hInetSes, pURL->text,
    492                                headers, -1, IOUFlags |
    493                                (uc.nScheme == INTERNET_SCHEME_HTTPS ?
    494                                 INTERNET_FLAG_SECURE : 0), 1);
    495  if (!g_hInetFile)
    496  {
    497    TRACE(_T("InetBgDl: InternetOpenUrl failed with gle=%u\n"),
    498          GetLastError());
    499    goto diegle;
    500  }
    501 
    502  // Get the file length via the Content-Length header
    503  FILESIZE_T cbThisFile;
    504  cbio = sizeof(cbThisFile);
    505  if (!HttpQueryInfo(g_hInetFile,
    506                     HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER,
    507                     &cbThisFile, &cbio, NULL))
    508  {
    509    cbThisFile = FILESIZE_UNKNOWN;
    510  }
    511  TRACE(_T("InetBgDl: file size=%d bytes\n"), cbThisFile);
    512 
    513  // Use a 4MiB read buffer for the connection.
    514  // Bigger buffers will be faster.
    515  // cbReadBufXF should be a multiple of g_cbBufXF.
    516  const UINT cbReadBufXF = 4194304;
    517 
    518  // Up the default internal buffer size from 4096 to internalReadBufferSize.
    519  DWORD internalReadBufferSize = cbReadBufXF;
    520  if (!InternetSetOption(g_hInetFile, INTERNET_OPTION_READ_BUFFER_SIZE,
    521                         &internalReadBufferSize, sizeof(DWORD)))
    522  {
    523    TRACE(_T("InetBgDl: InternetSetOption failed to set read buffer size to %u bytes, gle=%u\n"),
    524          internalReadBufferSize, GetLastError());
    525 
    526    // Maybe it's too big, try half of the optimal value.  If that fails just
    527    // use the default.
    528    internalReadBufferSize /= 2;
    529    if (!InternetSetOption(g_hInetFile, INTERNET_OPTION_READ_BUFFER_SIZE,
    530                           &internalReadBufferSize, sizeof(DWORD)))
    531    {
    532      TRACE(_T("InetBgDl: InternetSetOption failed to set read buffer size ") \
    533            _T("to %u bytes (using default read buffer size), gle=%u\n"),
    534            internalReadBufferSize, GetLastError());
    535    }
    536  }
    537 
    538  for(;;)
    539  {
    540    DWORD cbio = 0, cbXF = 0;
    541    BOOL retXF = InternetReadFile(g_hInetFile, g_bufXF, g_cbBufXF, &cbio);
    542    if (!retXF)
    543    {
    544      ec = GetLastError();
    545      TRACE(_T("InetBgDl: InternetReadFile failed, gle=%u\n"), ec);
    546      if (ERROR_INTERNET_CONNECTION_ABORTED == ec ||
    547          ERROR_INTERNET_CONNECTION_RESET == ec)
    548      {
    549        ec = ERROR_BROKEN_PIPE;
    550      }
    551      break;
    552    }
    553 
    554    if (0 == cbio)
    555    {
    556      ASSERT(ERROR_SUCCESS == ec);
    557      // EOF or broken connection?
    558      // TODO: Can InternetQueryDataAvailable detect this?
    559 
    560      TRACE(_T("InetBgDl: InternetReadFile true with 0 cbio, cbThisFile=%d, gle=%u\n"),
    561            cbThisFile, GetLastError());
    562      // If we haven't transferred all of the file, and we know how big the file
    563      // is, and we have no more data to read from the HTTP request, then set a
    564      // broken pipe error. Reading without StatsLock is ok in this thread.
    565      if (FILESIZE_UNKNOWN != cbThisFile && writtenThisSession != cbThisFile)
    566      {
    567        TRACE(_T("InetBgDl: expected Content-Length of %d bytes, ")
    568              _T("but transferred %d bytes\n"),
    569              cbThisFile, writtenThisSession);
    570        ec = ERROR_BROKEN_PIPE;
    571      }
    572      break;
    573    }
    574 
    575    // Check if we canceled the download
    576    if (0 == g_FilesTotal)
    577    {
    578      TRACE(_T("InetBgDl: 0 == g_FilesTotal, aborting transfer loop...\n"));
    579      ec = ERROR_CANCELLED;
    580      break;
    581    }
    582 
    583    cbXF = cbio;
    584    if (cbXF)
    585    {
    586      retXF = WriteFile(hLocalFile, g_bufXF, cbXF, &cbio, NULL);
    587      if (!retXF || cbXF != cbio)
    588      {
    589        ec = GetLastError();
    590        break;
    591      }
    592 
    593      StatsLock_AcquireExclusive();
    594      if (FILESIZE_UNKNOWN != cbThisFile) {
    595        g_cbCurrTot = cbThisFile;
    596      }
    597      writtenThisSession += cbXF;
    598      g_cbCurrXF += cbXF;
    599      StatsLock_ReleaseExclusive();
    600    }
    601  }
    602 
    603  TRACE(_T("InetBgDl: TaskThreadProc completed %s, ec=%u\n"), pURL->text, ec);
    604  InternetCloseHandle(g_hInetFile);
    605  g_hInetFile = nullptr;
    606  if (ERROR_SUCCESS == ec)
    607  {
    608    if (INVALID_HANDLE_VALUE != hLocalFile)
    609    {
    610      CloseHandle(hLocalFile);
    611      hLocalFile = INVALID_HANDLE_VALUE;
    612    }
    613    StackFreeItem(pURL);
    614    StackFreeItem(pFile);
    615    ++completedFile;
    616  }
    617  else if (ERROR_BROKEN_PIPE == ec)
    618  {
    619    g_Status = STATUS_ERR_CONNECTION_LOST;
    620    goto die;
    621  }
    622  else
    623  {
    624    TRACE(_T("InetBgDl: failed with ec=%u\n"), ec);
    625    SetLastError(ec);
    626    goto diegle;
    627  }
    628  goto startnexttask;
    629 }
    630 
    631 NSISPIEXPORTFUNC Get(HWND hwndNSIS, UINT N_CCH, TCHAR*N_Vars, NSIS::stack_t**ppST, NSIS::xparams_t*pX)
    632 {
    633  pX->RegisterPluginCallback(g_hInst, NSISPluginCallback);
    634  for (;;)
    635  {
    636    NSIS::stack_t*pURL = StackPopItem(ppST);
    637    if (!pURL)
    638    {
    639      break;
    640    }
    641 
    642    if (lstrcmpi(pURL->text, _T("/connecttimeout")) == 0)
    643    {
    644      NSIS::stack_t*pConnectTimeout = StackPopItem(ppST);
    645      g_ConnectTimeout = MyTStrToL(pConnectTimeout->text) * 1000;
    646      continue;
    647    }
    648    else if (lstrcmpi(pURL->text, _T("/receivetimeout")) == 0)
    649    {
    650      NSIS::stack_t*pReceiveTimeout = StackPopItem(ppST);
    651      g_ReceiveTimeout = MyTStrToL(pReceiveTimeout->text) * 1000;
    652      continue;
    653    }
    654    else if (lstrcmpi(pURL->text, _T("/reset")) == 0)
    655    {
    656      StackFreeItem(pURL);
    657      Reset();
    658      continue;
    659    }
    660    else if (lstrcmpi(pURL->text, _T("/end")) == 0)
    661    {
    662 freeurlandexit:
    663      StackFreeItem(pURL);
    664      break;
    665    }
    666 
    667    NSIS::stack_t*pFile = StackPopItem(ppST);
    668    if (!pFile)
    669    {
    670      goto freeurlandexit;
    671    }
    672 
    673    TaskLock_AcquireExclusive();
    674 
    675    pFile->next = NULL;
    676    pURL->next = pFile;
    677    NSIS::stack_t*pTasksTail = g_pLocations;
    678    while(pTasksTail && pTasksTail->next) pTasksTail = pTasksTail->next;
    679    if (pTasksTail)
    680    {
    681      pTasksTail->next = pURL;
    682    }
    683    else
    684    {
    685      g_pLocations = pURL;
    686    }
    687 
    688    if (!g_hThread)
    689    {
    690      DWORD tid;
    691      if (g_hGETStartedEvent) {
    692        CloseHandle(g_hGETStartedEvent);
    693      }
    694      g_hGETStartedEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    695      g_hThread = CreateThread(NULL, 0, TaskThreadProc, NULL, 0, &tid);
    696    }
    697 
    698    if (!g_hThread)
    699    {
    700      goto freeurlandexit;
    701    }
    702 
    703 #ifndef ONELOCKTORULETHEMALL
    704    StatsLock_AcquireExclusive();
    705 #endif
    706    ++g_FilesTotal;
    707 #ifndef ONELOCKTORULETHEMALL
    708    StatsLock_ReleaseExclusive();
    709 #endif
    710    TaskLock_ReleaseExclusive();
    711  }
    712 }
    713 
    714 NSISPIEXPORTFUNC GetStats(HWND hwndNSIS, UINT N_CCH, TCHAR*N_Vars, NSIS::stack_t**ppST, NSIS::xparams_t*pX)
    715 {
    716  NSISPI_INITGLOBALS(N_CCH, N_Vars);
    717  StatsLock_AcquireShared();
    718  NSIS_SetRegUINT(0, g_Status);
    719  NSIS_SetRegUINT(1, g_FilesCompleted);
    720  NSIS_SetRegUINT(2, g_FilesTotal - g_FilesCompleted);
    721  NSIS_SetRegUINT(3, g_cbCurrXF);
    722  NSIS_SetRegStrEmpty(4);
    723  if (FILESIZE_UNKNOWN != g_cbCurrTot)
    724  {
    725    NSIS_SetRegUINT(4, g_cbCurrTot);
    726  }
    727  NSIS_SetRegStr(5, g_ServerIP);
    728  StatsLock_ReleaseShared();
    729 }
    730 
    731 BOOL WINAPI DllMain(HINSTANCE hInst, ULONG Reason, LPVOID pCtx)
    732 {
    733  if (DLL_PROCESS_ATTACH==Reason)
    734  {
    735    g_hInst=hInst;
    736    InitializeCriticalSection(&g_CritLock);
    737  }
    738  return TRUE;
    739 }