// // 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)") #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; }