summaryrefslogtreecommitdiffstats
path: root/other-licenses/nsis/Contrib/InetBgDL/InetBgDL.cpp
blob: 813f1300d4c90484ea718e6b5e4cdc00f1da3edd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
//
// Copyright (C) Anders Kjersem. Licensed under the zlib/libpng license
//

// This file is intended to be compiled with MSVC's Omit Default Library Name (/Zl)
// option enabled, in order to keep the file size low for bundling this DLL with
// the stub installer. That means that any code requiring the C runtime will fail
// to link. You'll see a couple of odd-looking things here for this reason; they
// should all be called out with comments.

#include "InetBgDL.h"

#define USERAGENT _T("NSIS InetBgDL (Mozilla 2024)")

#define STATUS_COMPLETEDALL 0
#define STATUS_INITIAL 202
#define STATUS_CONNECTING STATUS_INITIAL //102
#define STATUS_DOWNLOADING STATUS_INITIAL
#define STATUS_ERR_GETLASTERROR 418 //HTTP: I'm a teapot: Win32 error code in $3
#define STATUS_ERR_LOCALFILEWRITEERROR 450 //HTTP: MS parental control extension
#define STATUS_ERR_CANCELLED 499
#define STATUS_ERR_CONNECTION_LOST 1000

typedef DWORD FILESIZE_T; // Limit to 4GB for now...
#define FILESIZE_UNKNOWN (-1)

#define MAX_STRLEN 1024

HINSTANCE g_hInst;
NSIS::stack_t*g_pLocations = NULL;
HANDLE g_hThread = NULL;
HANDLE g_hGETStartedEvent = NULL;
HINTERNET g_hInetSes = NULL;
HINTERNET g_hInetFile = NULL;
volatile UINT g_FilesTotal = 0;
volatile UINT g_FilesCompleted = 0;
volatile UINT g_Status = STATUS_INITIAL;
volatile FILESIZE_T g_cbCurrXF;
volatile FILESIZE_T g_cbCurrTot = FILESIZE_UNKNOWN;
CRITICAL_SECTION g_CritLock;
UINT g_N_CCH;
PTSTR g_N_Vars;
TCHAR g_ServerIP[128] = { _T('\0') };

DWORD g_ConnectTimeout = 0;
DWORD g_ReceiveTimeout = 0;

// Setup a buffer of size 256KiB to store the downloaded data.
constexpr UINT g_cbBufXF = 262144;
// This buffer is only needed inside TaskThreadProc(), but declaring it on
// the stack there triggers a runtime stack size check, which is implemented
// by a C runtime library function, so we have to avoid the compiler wanting
// to build that check by not having any large stack buffers.
BYTE g_bufXF[g_cbBufXF];

#define NSISPI_INITGLOBALS(N_CCH, N_Vars) do { \
  g_N_CCH = N_CCH; \
  g_N_Vars = N_Vars; \
  } while(0)

#define ONELOCKTORULETHEMALL
#ifdef ONELOCKTORULETHEMALL
#define TaskLock_AcquireExclusive() EnterCriticalSection(&g_CritLock)
#define TaskLock_ReleaseExclusive() LeaveCriticalSection(&g_CritLock)
#define StatsLock_AcquireExclusive() TaskLock_AcquireExclusive()
#define StatsLock_ReleaseExclusive() TaskLock_ReleaseExclusive()
#define StatsLock_AcquireShared() StatsLock_AcquireExclusive()
#define StatsLock_ReleaseShared() StatsLock_ReleaseExclusive()
#endif

// Normally we would just call the C library wcstol, but since we can't use the
// C runtime, we'll supply our own function as an understudy.
static DWORD
MyTStrToL(TCHAR const* str)
{
  if (!str) {
    return 0;
  }

  int len = lstrlen(str);
  DWORD place = 1;
  DWORD rv = 0;
  for (int i = len - 1; i >= 0; --i) {
    int digit = str[i] - 0x30;
    rv += digit * place;
    place *= 10;
  }
  return rv;
}

PTSTR NSIS_SetRegStr(UINT Reg, LPCTSTR Value)
{
  PTSTR s = g_N_Vars + (Reg * g_N_CCH);
  lstrcpy(s, Value);
  return s;
}
#define NSIS_SetRegStrEmpty(r) NSIS_SetRegStr(r, _T(""))
void NSIS_SetRegUINT(UINT Reg, UINT Value)
{
  TCHAR buf[32];
  wsprintf(buf, _T("%u"), Value);
  NSIS_SetRegStr(Reg, buf);
}
#define StackFreeItem(pI) GlobalFree(pI)
NSIS::stack_t* StackPopItem(NSIS::stack_t**ppST)
{
  if (*ppST)
  {
    NSIS::stack_t*pItem = *ppST;
    *ppST = pItem->next;
    return pItem;
  }
  return NULL;
}

void Reset()
{
  // The g_hGETStartedEvent event is used to make sure that the Get() call will
  // acquire the lock before the Reset() call acquires the lock.
  if (g_hGETStartedEvent) {
    TRACE(_T("InetBgDl: waiting on g_hGETStartedEvent\n"));
    WaitForSingleObject(g_hGETStartedEvent, INFINITE);
    CloseHandle(g_hGETStartedEvent);
    g_hGETStartedEvent = NULL;
  }

  TaskLock_AcquireExclusive();
#ifndef ONELOCKTORULETHEMALL
  StatsLock_AcquireExclusive();
#endif
  g_FilesTotal = 0; // This causes the Task thread to exit the transfer loop
  if (g_hThread)
  {
    TRACE(_T("InetBgDl: waiting on g_hThread\n"));
    if (WAIT_OBJECT_0 != WaitForSingleObject(g_hThread, 5 * 1000))
    {
      TRACE(_T("InetBgDl: terminating g_hThread\n"));
      // Suspend the thread so that it's not still trying to use these handles
      // that we're about to close out from under it.
      SuspendThread(g_hThread);
      if (g_hInetFile) {
        InternetCloseHandle(g_hInetFile);
        g_hInetFile = nullptr;
      }
      if (g_hInetSes) {
        InternetCloseHandle(g_hInetSes);
        g_hInetSes = nullptr;
      }
      TerminateThread(g_hThread, ERROR_OPERATION_ABORTED);
    }
    CloseHandle(g_hThread);
    g_hThread = NULL;
  }
  g_FilesTotal = 0;
  g_FilesCompleted = 0;
  g_Status = STATUS_INITIAL;
#ifndef ONELOCKTORULETHEMALL
  StatsLock_ReleaseExclusive();
#endif
  for (NSIS::stack_t*pTmpTast,*pTask = g_pLocations; pTask ;)
  {
    pTmpTast = pTask;
    pTask = pTask->next;
    StackFreeItem(pTmpTast);
  }
  g_pLocations = NULL;
  TaskLock_ReleaseExclusive();
}

UINT_PTR __cdecl NSISPluginCallback(UINT Event)
{
  switch(Event)
  {
  case NSPIM_UNLOAD:
    Reset();
    break;
  }
  return NULL;
}

void __stdcall InetStatusCallback(HINTERNET hInternet, DWORD_PTR dwContext,
                                  DWORD dwInternetStatus,
                                  LPVOID lpvStatusInformation,
                                  DWORD dwStatusInformationLength)
{
  if (dwInternetStatus == INTERNET_STATUS_NAME_RESOLVED) {
    // If we're in the process of being reset, don't try to update g_ServerIP;
    // there's no need for it, and Reset() will be holding the StatsLock, so
    // we'll hang here and block the reset if we try to acquire it.
    if (g_FilesTotal != 0) {
      // The documentation states the IP address is a PCTSTR but it is usually a
      // PCSTR and only sometimes a PCTSTR.
      StatsLock_AcquireExclusive();
      wsprintf(g_ServerIP, _T("%S"), lpvStatusInformation);
      if (lstrlen(g_ServerIP) == 1)
      {
        wsprintf(g_ServerIP, _T("%s"), lpvStatusInformation);
      }
      StatsLock_ReleaseExclusive();
    }
  }

#if defined(PLUGIN_DEBUG)
  switch (dwInternetStatus)
  {
    case INTERNET_STATUS_RESOLVING_NAME:
      TRACE(_T("InetBgDl: INTERNET_STATUS_RESOLVING_NAME (%d), name=%s\n"),
            dwStatusInformationLength, lpvStatusInformation);
      break;
    case INTERNET_STATUS_NAME_RESOLVED:
      TRACE(_T("InetBgDl: INTERNET_STATUS_NAME_RESOLVED (%d), resolved name=%s\n"),
            dwStatusInformationLength, g_ServerIP);
      break;
    case INTERNET_STATUS_CONNECTING_TO_SERVER:
      TRACE(_T("InetBgDl: INTERNET_STATUS_CONNECTING_TO_SERVER (%d)\n"),
            dwStatusInformationLength);
      break;
    case INTERNET_STATUS_CONNECTED_TO_SERVER:
      TRACE(_T("InetBgDl: INTERNET_STATUS_CONNECTED_TO_SERVER (%d)\n"),
            dwStatusInformationLength);
      break;
    case INTERNET_STATUS_SENDING_REQUEST:
      TRACE(_T("InetBgDl: INTERNET_STATUS_SENDING_REQUEST (%d)\n"),
               dwStatusInformationLength);
      break;
    case INTERNET_STATUS_REQUEST_SENT:
      TRACE(_T("InetBgDl: INTERNET_STATUS_REQUEST_SENT (%d), bytes sent=%d\n"),
             dwStatusInformationLength, lpvStatusInformation);
      break;
    case INTERNET_STATUS_RECEIVING_RESPONSE:
      TRACE(_T("InetBgDl: INTERNET_STATUS_RECEIVING_RESPONSE (%d)\n"),
            dwStatusInformationLength);
      break;
    case INTERNET_STATUS_RESPONSE_RECEIVED:
      TRACE(_T("InetBgDl: INTERNET_STATUS_RESPONSE_RECEIVED (%d)\n"),
            dwStatusInformationLength);
      break;
    case INTERNET_STATUS_CTL_RESPONSE_RECEIVED:
      TRACE(_T("InetBgDl: INTERNET_STATUS_CTL_RESPONSE_RECEIVED (%d)\n"),
            dwStatusInformationLength);
      break;
    case INTERNET_STATUS_PREFETCH:
      TRACE(_T("InetBgDl: INTERNET_STATUS_PREFETCH (%d)\n"),
            dwStatusInformationLength);
      break;
    case INTERNET_STATUS_CLOSING_CONNECTION:
      TRACE(_T("InetBgDl: INTERNET_STATUS_CLOSING_CONNECTION (%d)\n"),
            dwStatusInformationLength);
      break;
    case INTERNET_STATUS_CONNECTION_CLOSED:
      TRACE(_T("InetBgDl: INTERNET_STATUS_CONNECTION_CLOSED (%d)\n"),
            dwStatusInformationLength);
      break;
    case INTERNET_STATUS_HANDLE_CREATED:
      TRACE(_T("InetBgDl: INTERNET_STATUS_HANDLE_CREATED (%d)\n"),
            dwStatusInformationLength);
      break;
    case INTERNET_STATUS_HANDLE_CLOSING:
      TRACE(_T("InetBgDl: INTERNET_STATUS_HANDLE_CLOSING (%d)\n"),
            dwStatusInformationLength);
      break;
    case INTERNET_STATUS_DETECTING_PROXY:
      TRACE(_T("InetBgDl: INTERNET_STATUS_DETECTING_PROXY (%d)\n"),
            dwStatusInformationLength);
      break;
    case INTERNET_STATUS_REQUEST_COMPLETE:
      TRACE(_T("InetBgDl: INTERNET_STATUS_REQUEST_COMPLETE (%d)\n"),
            dwStatusInformationLength);
      break;
    case INTERNET_STATUS_REDIRECT:
      TRACE(_T("InetBgDl: INTERNET_STATUS_REDIRECT (%d), new url=%s\n"),
            dwStatusInformationLength, lpvStatusInformation);
      break;
    case INTERNET_STATUS_INTERMEDIATE_RESPONSE:
      TRACE(_T("InetBgDl: INTERNET_STATUS_INTERMEDIATE_RESPONSE (%d)\n"),
            dwStatusInformationLength);
      break;
    case INTERNET_STATUS_USER_INPUT_REQUIRED:
      TRACE(_T("InetBgDl: INTERNET_STATUS_USER_INPUT_REQUIRED (%d)\n"),
            dwStatusInformationLength);
      break;
    case INTERNET_STATUS_STATE_CHANGE:
      TRACE(_T("InetBgDl: INTERNET_STATUS_STATE_CHANGE (%d)\n"),
            dwStatusInformationLength);
      break;
    case INTERNET_STATUS_COOKIE_SENT:
      TRACE(_T("InetBgDl: INTERNET_STATUS_COOKIE_SENT (%d)\n"),
            dwStatusInformationLength);
      break;
    case INTERNET_STATUS_COOKIE_RECEIVED:
      TRACE(_T("InetBgDl: INTERNET_STATUS_COOKIE_RECEIVED (%d)\n"),
            dwStatusInformationLength);
      break;
    case INTERNET_STATUS_PRIVACY_IMPACTED:
      TRACE(_T("InetBgDl: INTERNET_STATUS_PRIVACY_IMPACTED (%d)\n"),
            dwStatusInformationLength);
      break;
    case INTERNET_STATUS_P3P_HEADER:
      TRACE(_T("InetBgDl: INTERNET_STATUS_P3P_HEADER (%d)\n"),
            dwStatusInformationLength);
      break;
    case INTERNET_STATUS_P3P_POLICYREF:
      TRACE(_T("InetBgDl: INTERNET_STATUS_P3P_POLICYREF (%d)\n"),
            dwStatusInformationLength);
      break;
    case INTERNET_STATUS_COOKIE_HISTORY:
      TRACE(_T("InetBgDl: INTERNET_STATUS_COOKIE_HISTORY (%d)\n"),
            dwStatusInformationLength);
      break;
    default:
      TRACE(_T("InetBgDl: Unknown Status %d\n"), dwInternetStatus);
      break;
  }
#endif
}

DWORD CALLBACK TaskThreadProc(LPVOID ThreadParam)
{
  NSIS::stack_t *pURL,*pFile;
  DWORD cbio = sizeof(DWORD);
  DWORD previouslyWritten = 0, writtenThisSession = 0;
  HANDLE hLocalFile;
  bool completedFile = false;
startnexttask:
  hLocalFile = INVALID_HANDLE_VALUE;
  pFile = NULL;
  TaskLock_AcquireExclusive();
  // Now that we've acquired the lock, we can set the event to indicate this.
  // SetEvent will likely never fail, but if it does we should set it to NULL
  // to avoid anyone waiting on it.
  if (!SetEvent(g_hGETStartedEvent)) {
    CloseHandle(g_hGETStartedEvent);
    g_hGETStartedEvent = NULL;
  }
  pURL = g_pLocations;
  if (pURL)
  {
    pFile = pURL->next;
    g_pLocations = pFile->next;
  }
#ifndef ONELOCKTORULETHEMALL
  StatsLock_AcquireExclusive();
#endif
  if (completedFile)
  {
    ++g_FilesCompleted;
  }
  completedFile = false;
  g_cbCurrXF = 0;
  g_cbCurrTot = FILESIZE_UNKNOWN;
  if (!pURL)
  {
    if (g_FilesTotal)
    {
      if (g_FilesTotal == g_FilesCompleted)
      {
        g_Status = STATUS_COMPLETEDALL;
      }
    }
    g_hThread = NULL;
  }
#ifndef ONELOCKTORULETHEMALL
  StatsLock_ReleaseExclusive();
#endif
  TaskLock_ReleaseExclusive();

  if (!pURL)
  {
    if (0)
    {
diegle:
      DWORD gle = GetLastError();
      //TODO? if (ERROR_INTERNET_EXTENDED_ERROR==gle) InternetGetLastResponseInfo(...)
      g_Status = STATUS_ERR_GETLASTERROR;
    }
die:
    if (g_hInetSes)
    {
      InternetCloseHandle(g_hInetSes);
      g_hInetSes = nullptr;
    }
    if (INVALID_HANDLE_VALUE != hLocalFile)
    {
      CloseHandle(hLocalFile);
    }
    StackFreeItem(pURL);
    StackFreeItem(pFile);
    return 0;
  }

  if (!g_hInetSes)
  {
    g_hInetSes = InternetOpen(USERAGENT, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
    if (!g_hInetSes)
    {
      TRACE(_T("InetBgDl: InternetOpen failed with gle=%u\n"),
            GetLastError());
      goto diegle;
    }
    InternetSetStatusCallback(g_hInetSes, (INTERNET_STATUS_CALLBACK)InetStatusCallback);

    //msdn.microsoft.com/library/default.asp?url=/workshop/components/offline/offline.asp#Supporting Offline Browsing in Applications and Components
    ULONG longOpt;
    DWORD cbio = sizeof(ULONG);
    if (InternetQueryOption(g_hInetSes, INTERNET_OPTION_CONNECTED_STATE, &longOpt, &cbio))
    {
      if (INTERNET_STATE_DISCONNECTED_BY_USER&longOpt)
      {
        INTERNET_CONNECTED_INFO ci = {INTERNET_STATE_CONNECTED, 0};
        InternetSetOption(g_hInetSes, INTERNET_OPTION_CONNECTED_STATE, &ci, sizeof(ci));
      }
    }

    // Change the default connect timeout if specified.
    if(g_ConnectTimeout > 0)
    {
      InternetSetOption(g_hInetSes, INTERNET_OPTION_CONNECT_TIMEOUT,
                        &g_ConnectTimeout, sizeof(g_ConnectTimeout));
    }

    // Change the default receive timeout if specified.
    if (g_ReceiveTimeout)
    {
      InternetSetOption(g_hInetSes, INTERNET_OPTION_RECEIVE_TIMEOUT,
                        &g_ReceiveTimeout, sizeof(DWORD));
    }
  }

  DWORD ec = ERROR_SUCCESS;
  hLocalFile = CreateFile(pFile->text, GENERIC_READ | GENERIC_WRITE,
                          FILE_SHARE_READ | FILE_SHARE_DELETE,
                          NULL, OPEN_ALWAYS, 0, NULL);
  if (INVALID_HANDLE_VALUE == hLocalFile)
  {
    TRACE(_T("InetBgDl: CreateFile file handle invalid\n"));
    goto diegle;
  }
  if (GetLastError() == ERROR_ALREADY_EXISTS) {
    // Resuming a download that was started earlier and then aborted.
    previouslyWritten = GetFileSize(hLocalFile, NULL);
    g_cbCurrXF = previouslyWritten;
    SetFilePointer(hLocalFile, previouslyWritten, NULL, FILE_BEGIN);
  }

  const DWORD IOURedirFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP |
                              INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS;
  const DWORD IOUCacheFlags = INTERNET_FLAG_RESYNCHRONIZE |
                              INTERNET_FLAG_NO_CACHE_WRITE |
                              INTERNET_FLAG_PRAGMA_NOCACHE |
                              INTERNET_FLAG_RELOAD;
  const DWORD IOUCookieFlags = INTERNET_FLAG_NO_COOKIES;
  DWORD IOUFlags = IOURedirFlags | IOUCacheFlags | IOUCookieFlags |
                   INTERNET_FLAG_NO_UI | INTERNET_FLAG_EXISTING_CONNECT;

  TCHAR *hostname = (TCHAR*) GlobalAlloc(GPTR, MAX_STRLEN * sizeof(TCHAR)),
        *urlpath = (TCHAR*) GlobalAlloc(GPTR, MAX_STRLEN * sizeof(TCHAR)),
        *extrainfo = (TCHAR*) GlobalAlloc(GPTR, MAX_STRLEN * sizeof(TCHAR));

  URL_COMPONENTS uc = { sizeof(URL_COMPONENTS), NULL, 0, (INTERNET_SCHEME)0,
                        hostname, MAX_STRLEN, (INTERNET_PORT)0, NULL, 0,
                        NULL, 0, urlpath, MAX_STRLEN, extrainfo, MAX_STRLEN};
  uc.dwHostNameLength = uc.dwUrlPathLength = uc.dwExtraInfoLength = MAX_STRLEN;

  if (!InternetCrackUrl(pURL->text, 0, ICU_ESCAPE, &uc))
  {
    // Bad url or param passed in
    TRACE(_T("InetBgDl: InternetCrackUrl false with url=%s, gle=%u\n"),
          pURL->text, GetLastError());
    goto diegle;
  }

  TRACE(_T("InetBgDl: scheme_id=%d, hostname=%s, port=%d, urlpath=%s, extrainfo=%s\n"),
        uc.nScheme, hostname, uc.nPort, urlpath, extrainfo);

  // Only http and https are supported
  if (uc.nScheme != INTERNET_SCHEME_HTTP &&
      uc.nScheme != INTERNET_SCHEME_HTTPS)
  {
    TRACE(_T("InetBgDl: only http and https is supported, aborting...\n"));
    goto diegle;
  }

  // Tell the server to pick up wherever we left off.
  TCHAR headers[32];
  // We're skipping building the C runtime to keep the file size low, so we
  // can't use a normal string initialization because that would call memset.
  headers[0] = _T('\0');
  wsprintf(headers, _T("Range: bytes=%d-\r\n"), previouslyWritten);

  TRACE(_T("InetBgDl: calling InternetOpenUrl with url=%s\n"), pURL->text);
  g_hInetFile = InternetOpenUrl(g_hInetSes, pURL->text,
                                headers, -1, IOUFlags |
                                (uc.nScheme == INTERNET_SCHEME_HTTPS ?
                                 INTERNET_FLAG_SECURE : 0), 1);
  if (!g_hInetFile)
  {
    TRACE(_T("InetBgDl: InternetOpenUrl failed with gle=%u\n"),
          GetLastError());
    goto diegle;
  }

  // Get the file length via the Content-Length header
  FILESIZE_T cbThisFile;
  cbio = sizeof(cbThisFile);
  if (!HttpQueryInfo(g_hInetFile,
                     HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER,
                     &cbThisFile, &cbio, NULL))
  {
    cbThisFile = FILESIZE_UNKNOWN;
  }
  TRACE(_T("InetBgDl: file size=%d bytes\n"), cbThisFile);

  // Use a 4MiB read buffer for the connection.
  // Bigger buffers will be faster.
  // cbReadBufXF should be a multiple of g_cbBufXF.
  const UINT cbReadBufXF = 4194304;

  // Up the default internal buffer size from 4096 to internalReadBufferSize.
  DWORD internalReadBufferSize = cbReadBufXF;
  if (!InternetSetOption(g_hInetFile, INTERNET_OPTION_READ_BUFFER_SIZE,
                         &internalReadBufferSize, sizeof(DWORD)))
  {
    TRACE(_T("InetBgDl: InternetSetOption failed to set read buffer size to %u bytes, gle=%u\n"),
          internalReadBufferSize, GetLastError());

    // Maybe it's too big, try half of the optimal value.  If that fails just
    // use the default.
    internalReadBufferSize /= 2;
    if (!InternetSetOption(g_hInetFile, INTERNET_OPTION_READ_BUFFER_SIZE,
                           &internalReadBufferSize, sizeof(DWORD)))
    {
      TRACE(_T("InetBgDl: InternetSetOption failed to set read buffer size ") \
            _T("to %u bytes (using default read buffer size), gle=%u\n"),
            internalReadBufferSize, GetLastError());
    }
  }

  for(;;)
  {
    DWORD cbio = 0, cbXF = 0;
    BOOL retXF = InternetReadFile(g_hInetFile, g_bufXF, g_cbBufXF, &cbio);
    if (!retXF)
    {
      ec = GetLastError();
      TRACE(_T("InetBgDl: InternetReadFile failed, gle=%u\n"), ec);
      if (ERROR_INTERNET_CONNECTION_ABORTED == ec ||
          ERROR_INTERNET_CONNECTION_RESET == ec)
      {
        ec = ERROR_BROKEN_PIPE;
      }
      break;
    }

    if (0 == cbio)
    {
      ASSERT(ERROR_SUCCESS == ec);
      // EOF or broken connection?
      // TODO: Can InternetQueryDataAvailable detect this?

      TRACE(_T("InetBgDl: InternetReadFile true with 0 cbio, cbThisFile=%d, gle=%u\n"),
            cbThisFile, GetLastError());
      // If we haven't transferred all of the file, and we know how big the file
      // is, and we have no more data to read from the HTTP request, then set a
      // broken pipe error. Reading without StatsLock is ok in this thread.
      if (FILESIZE_UNKNOWN != cbThisFile && writtenThisSession != cbThisFile)
      {
        TRACE(_T("InetBgDl: expected Content-Length of %d bytes, ")
              _T("but transferred %d bytes\n"),
              cbThisFile, writtenThisSession);
        ec = ERROR_BROKEN_PIPE;
      }
      break;
    }

    // Check if we canceled the download
    if (0 == g_FilesTotal)
    {
      TRACE(_T("InetBgDl: 0 == g_FilesTotal, aborting transfer loop...\n"));
      ec = ERROR_CANCELLED;
      break;
    }

    cbXF = cbio;
    if (cbXF)
    {
      retXF = WriteFile(hLocalFile, g_bufXF, cbXF, &cbio, NULL);
      if (!retXF || cbXF != cbio)
      {
        ec = GetLastError();
        break;
      }

      StatsLock_AcquireExclusive();
      if (FILESIZE_UNKNOWN != cbThisFile) {
        g_cbCurrTot = cbThisFile;
      }
      writtenThisSession += cbXF;
      g_cbCurrXF += cbXF;
      StatsLock_ReleaseExclusive();
    }
  }

  TRACE(_T("InetBgDl: TaskThreadProc completed %s, ec=%u\n"), pURL->text, ec);
  InternetCloseHandle(g_hInetFile);
  g_hInetFile = nullptr;
  if (ERROR_SUCCESS == ec)
  {
    if (INVALID_HANDLE_VALUE != hLocalFile)
    {
      CloseHandle(hLocalFile);
      hLocalFile = INVALID_HANDLE_VALUE;
    }
    StackFreeItem(pURL);
    StackFreeItem(pFile);
    ++completedFile;
  }
  else if (ERROR_BROKEN_PIPE == ec)
  {
    g_Status = STATUS_ERR_CONNECTION_LOST;
    goto die;
  }
  else
  {
    TRACE(_T("InetBgDl: failed with ec=%u\n"), ec);
    SetLastError(ec);
    goto diegle;
  }
  goto startnexttask;
}

NSISPIEXPORTFUNC Get(HWND hwndNSIS, UINT N_CCH, TCHAR*N_Vars, NSIS::stack_t**ppST, NSIS::xparams_t*pX)
{
  pX->RegisterPluginCallback(g_hInst, NSISPluginCallback);
  for (;;)
  {
    NSIS::stack_t*pURL = StackPopItem(ppST);
    if (!pURL)
    {
      break;
    }

    if (lstrcmpi(pURL->text, _T("/connecttimeout")) == 0)
    {
      NSIS::stack_t*pConnectTimeout = StackPopItem(ppST);
      g_ConnectTimeout = MyTStrToL(pConnectTimeout->text) * 1000;
      continue;
    }
    else if (lstrcmpi(pURL->text, _T("/receivetimeout")) == 0)
    {
      NSIS::stack_t*pReceiveTimeout = StackPopItem(ppST);
      g_ReceiveTimeout = MyTStrToL(pReceiveTimeout->text) * 1000;
      continue;
    }
    else if (lstrcmpi(pURL->text, _T("/reset")) == 0)
    {
      StackFreeItem(pURL);
      Reset();
      continue;
    }
    else if (lstrcmpi(pURL->text, _T("/end")) == 0)
    {
freeurlandexit:
      StackFreeItem(pURL);
      break;
    }

    NSIS::stack_t*pFile = StackPopItem(ppST);
    if (!pFile)
    {
      goto freeurlandexit;
    }

    TaskLock_AcquireExclusive();

    pFile->next = NULL;
    pURL->next = pFile;
    NSIS::stack_t*pTasksTail = g_pLocations;
    while(pTasksTail && pTasksTail->next) pTasksTail = pTasksTail->next;
    if (pTasksTail)
    {
      pTasksTail->next = pURL;
    }
    else
    {
      g_pLocations = pURL;
    }

    if (!g_hThread)
    {
      DWORD tid;
      if (g_hGETStartedEvent) {
        CloseHandle(g_hGETStartedEvent);
      }
      g_hGETStartedEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
      g_hThread = CreateThread(NULL, 0, TaskThreadProc, NULL, 0, &tid);
    }

    if (!g_hThread)
    {
      goto freeurlandexit;
    }

#ifndef ONELOCKTORULETHEMALL
    StatsLock_AcquireExclusive();
#endif
    ++g_FilesTotal;
#ifndef ONELOCKTORULETHEMALL
    StatsLock_ReleaseExclusive();
#endif
    TaskLock_ReleaseExclusive();
  }
}

NSISPIEXPORTFUNC GetStats(HWND hwndNSIS, UINT N_CCH, TCHAR*N_Vars, NSIS::stack_t**ppST, NSIS::xparams_t*pX)
{
  NSISPI_INITGLOBALS(N_CCH, N_Vars);
  StatsLock_AcquireShared();
  NSIS_SetRegUINT(0, g_Status);
  NSIS_SetRegUINT(1, g_FilesCompleted);
  NSIS_SetRegUINT(2, g_FilesTotal - g_FilesCompleted);
  NSIS_SetRegUINT(3, g_cbCurrXF);
  NSIS_SetRegStrEmpty(4);
  if (FILESIZE_UNKNOWN != g_cbCurrTot)
  {
    NSIS_SetRegUINT(4, g_cbCurrTot);
  }
  NSIS_SetRegStr(5, g_ServerIP);
  StatsLock_ReleaseShared();
}

BOOL WINAPI DllMain(HINSTANCE hInst, ULONG Reason, LPVOID pCtx)
{
  if (DLL_PROCESS_ATTACH==Reason)
  {
    g_hInst=hInst;
    InitializeCriticalSection(&g_CritLock);
  }
  return TRUE;
}