Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

519 lines
14 KiB

  1. //=======================================================================
  2. //
  3. // Copyright (c) 1998-1999 Microsoft Corporation. All Rights Reserved.
  4. //
  5. // File: download.cpp
  6. //
  7. // Owner: YanL
  8. //
  9. // Description:
  10. //
  11. // Internet download implementation
  12. //
  13. //=======================================================================
  14. #include <windows.h>
  15. #include <wininet.h>
  16. #include <shlwapi.h>
  17. #include <tchar.h>
  18. #include <wustl.h>
  19. #define LOGGING_LEVEL 2
  20. #include <log.h>
  21. #include <download.h>
  22. static void InfiniteWaitForSingleObject(HANDLE hHandle);
  23. CDownload::CDownload(
  24. IN OPTIONAL int cnMaxThreads /*= 1*/
  25. ) :
  26. m_cnMaxThreads(min(cnMaxThreads, MAX_THREADS_LIMIT)),
  27. m_cnThreads(0)
  28. {
  29. LOG_block("CDownload::CDownload");
  30. // syncronization
  31. m_hEventExit = CreateEvent(NULL, /*bManualReset*/ TRUE, /*bInitialState*/ FALSE, NULL);
  32. m_hEventJob = CreateEvent(NULL, /*bManualReset*/ TRUE, /*bInitialState*/ FALSE, NULL);
  33. m_hEventDone = CreateEvent(NULL, /*bManualReset*/ FALSE, /*bInitialState*/ FALSE, NULL);
  34. m_hSession = InternetOpen(_T("Windows Update Catalog"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
  35. }
  36. CDownload::~CDownload(
  37. void
  38. ) {
  39. LOG_block("CDownload::~CDownload");
  40. if (m_cnThreads)
  41. {
  42. // Wait for all threads to exit:
  43. HANDLE ahThreads[MAX_THREADS_LIMIT];
  44. for (int i = 0; i < m_cnThreads; i ++)
  45. ahThreads[i] = m_ahThreads[i];
  46. SetEvent(m_hEventExit);
  47. WaitForMultipleObjects(m_cnThreads, ahThreads, /*bWaitAll*/TRUE, INFINITE);
  48. }
  49. // to insure that m_hConnection is released before m_hSession
  50. m_hConnection.release();
  51. m_hSession.release();
  52. }
  53. bool CDownload::Connect(
  54. IN LPCTSTR szURL
  55. ) {
  56. LOG_block("CDownload::Connect");
  57. // check that constructor created everything we need
  58. if (
  59. !m_hEventExit.valid() ||
  60. !m_hEventJob.valid() ||
  61. !m_hEventDone.valid() ||
  62. !m_hSession.valid()
  63. ) {
  64. return false;
  65. }
  66. // check parameters
  67. if (NULL == szURL)
  68. return false;
  69. // Prepare to crack URL
  70. URL_COMPONENTS url;
  71. ZeroMemory(&url, sizeof(url));
  72. url.dwStructSize = sizeof(url);
  73. TCHAR szServerName[INTERNET_MAX_HOST_NAME_LENGTH];
  74. url.lpszHostName = szServerName;
  75. url.dwHostNameLength = sizeOfArray(szServerName);
  76. m_szRootPath[0] = 0;
  77. url.lpszUrlPath = m_szRootPath;
  78. url.dwUrlPathLength = sizeOfArray(m_szRootPath);
  79. TCHAR szUserName[INTERNET_MAX_USER_NAME_LENGTH];
  80. url.lpszUserName = szUserName;
  81. url.dwUserNameLength = sizeOfArray(szUserName);
  82. TCHAR szPassword[INTERNET_MAX_PASSWORD_LENGTH];
  83. url.lpszPassword = szPassword;
  84. url.dwPasswordLength = sizeOfArray(szPassword);
  85. if (!InternetCrackUrl(szURL, 0, 0, &url))
  86. {
  87. LOG_error("InternetCrackUrl(%s) failed, last error %d", szURL, GetLastError());
  88. return false;
  89. }
  90. if (url.nScheme != INTERNET_SCHEME_HTTP && url.nScheme != INTERNET_SCHEME_HTTPS)
  91. {
  92. LOG_error("http or https only");
  93. return false;
  94. }
  95. if (_T('/') == m_szRootPath[lstrlen(m_szRootPath) - 1])
  96. m_szRootPath[lstrlen(m_szRootPath) - 1] = 0;
  97. LOG_out("connecting to %s at %s", szServerName, m_szRootPath);
  98. m_hConnection = InternetConnect(m_hSession, szServerName, url.nPort, szUserName, szPassword, INTERNET_SERVICE_HTTP, 0, 1);
  99. return m_hConnection.valid();
  100. }
  101. bool CDownload::Copy(IN LPCTSTR szSourceFile, IN LPCTSTR szDestFile, IN OPTIONAL IDownloadAdviceSink* pSink /*= 0*/)
  102. {
  103. LOG_block("CDownload::Copy");
  104. JOB job = { szSourceFile, szDestFile, 0, NO_ERROR, 0 };
  105. CopyEx(&job, 1, pSink);
  106. SetLastError(job.dwResult);
  107. return NO_ERROR == job.dwResult || ERROR_ALREADY_EXISTS == job.dwResult ? true : false;
  108. }
  109. bool CDownload::Copy(IN LPCTSTR szSourceFile, IN byte_buffer& bufDest, IN OPTIONAL IDownloadAdviceSink* pSink /*= 0*/)
  110. {
  111. LOG_block("CDownload::Copy");
  112. JOB job = { szSourceFile, 0, &bufDest, NO_ERROR, 0 };
  113. CopyEx(&job, 1, pSink);
  114. SetLastError(job.dwResult);
  115. return NO_ERROR == job.dwResult || ERROR_ALREADY_EXISTS == job.dwResult ? true : false;
  116. }
  117. void CDownload::CopyEx(IN PJOB pJobs, IN int cnJobs, IN OPTIONAL IDownloadAdviceSink* pSink /*= 0*/, IN bool fWaitComplete /*= true*/)
  118. {
  119. LOG_block("CDownload::CopyEx");
  120. // Init jobs queue
  121. m_pJobsQueue = pJobs;
  122. m_cnJobsTotal = cnJobs;
  123. m_cnJobsToComplete = cnJobs;
  124. m_nJobTop = 0;
  125. // init status
  126. m_pSink = pSink;
  127. // Create more threads if we need to
  128. int cnThreadsNeed = min(m_cnMaxThreads, cnJobs);
  129. while (m_cnThreads < cnThreadsNeed)
  130. {
  131. DWORD dwThreadId;
  132. m_ahThreads[m_cnThreads] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)StaticThreadProc, this, 0, &dwThreadId);
  133. if (!m_ahThreads[m_cnThreads].valid())
  134. break;
  135. m_cnThreads ++;
  136. }
  137. if (0 == m_cnThreads) // No threads to work on
  138. {
  139. // This case is only if we are in new device wizard on 98
  140. DWORD dwError = GetLastError();
  141. LOG_out("No thread to work on (last error = %d), will work on the main one", dwError);
  142. // turn proxy autodetection off
  143. INTERNET_PER_CONN_OPTION option = { INTERNET_PER_CONN_FLAGS, 0 };
  144. INTERNET_PER_CONN_OPTION_LIST list = { sizeof(INTERNET_PER_CONN_OPTION_LIST), 0, 1, 0, &option };
  145. DWORD dwBufLen = sizeof(list);
  146. BOOL fQueryOK = InternetQueryOption(NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, &dwBufLen);
  147. DWORD dwConnFlags = option.dwOption;
  148. if (fQueryOK)
  149. {
  150. option.dwOption &= ~PROXY_TYPE_AUTO_DETECT;
  151. InternetSetOption(NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, sizeof(list));
  152. }
  153. while (m_nJobTop < m_cnJobsTotal)
  154. {
  155. DoJob(m_nJobTop);
  156. m_nJobTop ++;
  157. }
  158. // restore proxy autodetection
  159. if (fQueryOK)
  160. {
  161. option.dwOption = dwConnFlags;
  162. InternetSetOption(NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, sizeof(list));
  163. }
  164. }
  165. else
  166. {
  167. // Normal case
  168. // Start
  169. ResetEvent(m_hEventDone);
  170. SetEvent(m_hEventJob);
  171. // Wait for all jobs to complete
  172. if (fWaitComplete)
  173. {
  174. while(0 < m_cnJobsToComplete)
  175. InfiniteWaitForSingleObject(m_hEventDone);
  176. }
  177. }
  178. }
  179. DWORD WINAPI CDownload::StaticThreadProc(IN LPVOID lpParameter)
  180. {
  181. // just call member function so we can access members
  182. CDownload* pThis = static_cast<CDownload*>(lpParameter);
  183. pThis->ThreadProc();
  184. return 0;
  185. }
  186. void CDownload::ThreadProc()
  187. {
  188. LOG_block("CDownload::ThreadProc");
  189. HANDLE ahevents[2] = { (HANDLE)m_hEventJob, (HANDLE)m_hEventExit };
  190. while(true)
  191. {
  192. // wait for job or exit
  193. DWORD dwRet = WaitForMultipleObjects(2, ahevents, FALSE, INFINITE);
  194. if (WAIT_OBJECT_0+1 == dwRet)
  195. {
  196. return; // called to exit
  197. }
  198. else if (WAIT_OBJECT_0 == dwRet)
  199. {
  200. int nJob = m_lock.Increment(&m_nJobTop) - 1;
  201. if (nJob < m_cnJobsTotal) // Job is waiting
  202. {
  203. DoJob(nJob);
  204. // Increment complete counter
  205. m_lock.Decrement(&m_cnJobsToComplete);
  206. SetEvent(m_hEventDone);
  207. }
  208. else
  209. {
  210. ResetEvent(m_hEventJob); // queue is empty
  211. }
  212. }
  213. }
  214. }
  215. void CDownload::DoJob(int nJob)
  216. {
  217. LOG_block("CDownload::DoJob");
  218. DWORD dwStartTime;
  219. if (NULL != m_pSink)
  220. {
  221. m_pSink->JobStarted(nJob);// Notify start
  222. if (NO_ERROR != m_pJobsQueue[nJob].dwResult) // this will inforce abort on all jobs
  223. {
  224. LOG_error("Job result is set to %d", m_pJobsQueue[nJob].dwResult);
  225. m_pSink->JobDone(nJob);
  226. return;
  227. }
  228. if (m_pSink->WantToAbortJob(nJob)) // this will inforce abort on all jobs
  229. {
  230. LOG_error("Job %d aborted", nJob);
  231. m_pJobsQueue[nJob].dwResult = ERROR_OPERATION_ABORTED;
  232. m_pSink->JobDone(nJob);
  233. return;
  234. }
  235. dwStartTime = GetTickCount(); // for progress
  236. }
  237. //Make Full path
  238. TCHAR szFullSourcePath[MAX_PATH];
  239. lstrcpy(szFullSourcePath, m_szRootPath);
  240. lstrcat(szFullSourcePath, _T("/"));
  241. lstrcat(szFullSourcePath, m_pJobsQueue[nJob].pszSrvFile);
  242. // Do one of two copy functions
  243. if (NULL != m_pJobsQueue[nJob].pszDstFile)
  244. {
  245. m_pJobsQueue[nJob].dwResult = DoCopyToFile(nJob, szFullSourcePath, m_pJobsQueue[nJob].pszDstFile); // to file
  246. }
  247. else
  248. {
  249. m_pJobsQueue[nJob].dwResult = DoCopyToBuf(nJob, szFullSourcePath, *(m_pJobsQueue[nJob].pDstBuf)); // to buffer
  250. }
  251. if (NULL != m_pSink)
  252. {
  253. if (NO_ERROR == m_pJobsQueue[nJob].dwResult)
  254. m_pSink->JobDownloadTime(nJob, GetTickCount() - dwStartTime); // only if success
  255. m_pSink->JobDone(nJob);
  256. }
  257. }
  258. DWORD CDownload::DoCopyToFile(
  259. IN int nJob, // for notifications
  260. IN LPCTSTR szSourceFile,
  261. IN LPCTSTR szDestFile
  262. ) {
  263. LOG_block("CDownload::DoCopyToFile");
  264. LOG_out("source %s, destination %s", szSourceFile, szDestFile);
  265. // If destination file exist let see if we even need to download - compare it's date and size with server
  266. FILETIME ftLocal = {0};
  267. SYSTEMTIME stLocal = {0};
  268. auto_hfile hFileOut = CreateFile(szDestFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  269. if (hFileOut.valid())
  270. {
  271. GetFileTime(hFileOut, NULL, NULL, &ftLocal);
  272. FileTimeToSystemTime(&ftLocal, &stLocal);
  273. }
  274. auto_hinet hInetFile;
  275. DWORD dwServerFileSize;
  276. SYSTEMTIME stServer;
  277. DWORD dwError = OpenInetFile(szSourceFile, hFileOut.valid() ? &stLocal: 0, hInetFile, &dwServerFileSize, &stServer);
  278. if (NO_ERROR != dwError)
  279. {
  280. if (ERROR_ALREADY_EXISTS != dwError)
  281. LOG_error("OpenInetFile failed %d", dwError);
  282. return dwError;
  283. }
  284. if (NULL != m_pSink)
  285. m_pSink->JobDownloadSize(nJob, dwServerFileSize);// Notify start
  286. // get server file time to compare with existing local and timestamp it if replaced
  287. FILETIME ftServer = {0};
  288. SystemTimeToFileTime(&stServer, &ftServer);
  289. // If destination file exist let see if we even need to download - compare it's date and size with server
  290. if (hFileOut.valid())
  291. {
  292. //#ifdef _WUV3TEST
  293. DWORD dwLocalFileSize = GetFileSize(hFileOut, NULL);
  294. LOG_out("'%s' \t server (%d bytes) %2d/%02d %2d:%02d:%02d:%03d \t local (%d bytes) %2d/%02d %2d:%02d:%02d:%03d", szSourceFile,
  295. dwServerFileSize, (int)stServer.wMonth, (int)stServer.wDay, (int)stServer.wHour, (int)stServer.wMinute, (int)stServer.wSecond, (int)stServer.wMilliseconds,
  296. dwLocalFileSize, (int)stLocal.wMonth, (int)stLocal.wDay, (int)stLocal.wHour, (int)stLocal.wMinute, (int)stLocal.wSecond, (int)stLocal.wMilliseconds
  297. );
  298. //#endif
  299. hFileOut.release();
  300. }
  301. // coping through 8K buffer
  302. byte_buffer bufTmp;
  303. bufTmp.resize(min(dwServerFileSize, 8 * 1024)); // use 8K buffer
  304. if (!bufTmp.valid())
  305. {
  306. LOG_error("Out of memory");
  307. return ERROR_NOT_ENOUGH_MEMORY;
  308. }
  309. hFileOut = CreateFile(szDestFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  310. return_error_if_false(hFileOut.valid());
  311. dwError = NO_ERROR;
  312. DWORD dwFileWritten = 0;
  313. while(dwFileWritten < dwServerFileSize)
  314. {
  315. DWORD dwReadStartTime;
  316. if (NULL != m_pSink)
  317. {
  318. if (m_pSink->WantToAbortJob(nJob))
  319. {
  320. LOG_error("Job %d aborted", nJob);
  321. dwError = ERROR_OPERATION_ABORTED;
  322. break;
  323. }
  324. dwReadStartTime = GetTickCount(); // for progress
  325. }
  326. DWORD dwNeedToRead = dwServerFileSize - dwFileWritten;
  327. dwNeedToRead = min(dwNeedToRead, bufTmp.size());
  328. DWORD dwReadSize;
  329. if (!InternetReadFile(hInetFile, bufTmp, dwNeedToRead, &dwReadSize))
  330. {
  331. dwError = GetLastError();
  332. LOG_error("InternetReadFile(hInetFile... failed %d", dwError);
  333. break;
  334. }
  335. DWORD dwWritten;
  336. if (!WriteFile(hFileOut, bufTmp, dwReadSize, &dwWritten, NULL))
  337. {
  338. dwError = GetLastError();
  339. LOG_error("WriteFile(hFileOut... failed %d", dwError);
  340. break;
  341. }
  342. dwFileWritten += dwReadSize;
  343. if (NULL != m_pSink)
  344. m_pSink->BlockDownloaded(nJob, dwReadSize, GetTickCount() - dwReadStartTime);
  345. }
  346. if(dwError)
  347. {
  348. hFileOut.release();
  349. DeleteFile(szDestFile); // file is not complete
  350. return dwError;
  351. }
  352. // timestamp destination file
  353. SetFileTime(hFileOut, NULL, NULL, &ftServer);
  354. return NO_ERROR;
  355. }
  356. DWORD CDownload::DoCopyToBuf(
  357. IN int nJob, // for notifications
  358. IN LPCTSTR szSourceFile,
  359. IN byte_buffer& bufDest
  360. ) {
  361. LOG_block("CDownload::DoCopyToBuf");
  362. LOG_out("source %s", szSourceFile);
  363. auto_hinet hInetFile;
  364. DWORD dwServerFileSize;
  365. DWORD dwError = OpenInetFile(szSourceFile, NULL, hInetFile, &dwServerFileSize, NULL);
  366. if (NO_ERROR != dwError)
  367. {
  368. if (ERROR_ALREADY_EXISTS != dwError)
  369. LOG_error("OpenInetFile failed %d", dwError);
  370. return dwError;
  371. }
  372. if (NULL != m_pSink)
  373. m_pSink->JobDownloadSize(nJob, dwServerFileSize);// Notify start
  374. bufDest.resize(dwServerFileSize);
  375. if (!bufDest.valid())
  376. {
  377. LOG_error("Out of memory");
  378. return ERROR_NOT_ENOUGH_MEMORY;
  379. }
  380. DWORD dwReadSize;
  381. return_error_if_false(InternetReadFile(hInetFile, bufDest, dwServerFileSize, &dwReadSize));
  382. return NO_ERROR;
  383. }
  384. // commont part for both downloads
  385. DWORD CDownload::OpenInetFile(
  386. LPCTSTR szFullSourcePath,
  387. SYSTEMTIME* pstIfModifiedSince,
  388. auto_hinet& hInetFile,
  389. DWORD* pdwServerFileSize,
  390. SYSTEMTIME* pstServer
  391. ) {
  392. LOG_block("CDownload::OpenInetFile");
  393. hInetFile = HttpOpenRequest(m_hConnection, NULL, szFullSourcePath, _T("HTTP/1.0"), NULL, NULL, INTERNET_FLAG_DONT_CACHE|INTERNET_FLAG_KEEP_CONNECTION, 1);
  394. return_error_if_false(hInetFile.valid());
  395. if (pstIfModifiedSince) // local file exist co send If-Modified-Since: header
  396. {
  397. TCHAR szHttpTime[INTERNET_RFC1123_BUFSIZE + 1];
  398. DWORD dwSize = sizeof(szHttpTime);
  399. return_error_if_false(InternetTimeFromSystemTime(pstIfModifiedSince, INTERNET_RFC1123_FORMAT, szHttpTime, dwSize));
  400. //prepare and set the header
  401. static const TCHAR szFormat[] = _T("If-Modified-Since: %s\r\n");
  402. TCHAR szHeader[INTERNET_RFC1123_BUFSIZE + sizeOfArray(szFormat)];
  403. wsprintf(szHeader, szFormat, szHttpTime);
  404. return_error_if_false(HttpAddRequestHeaders(hInetFile, szHeader, -1L, HTTP_ADDREQ_FLAG_ADD));
  405. }
  406. return_error_if_false(HttpSendRequest(hInetFile, NULL, 0, NULL, 0));
  407. DWORD dwStatus;
  408. DWORD dwLength = sizeof(dwStatus);
  409. return_error_if_false(HttpQueryInfo(hInetFile, HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_STATUS_CODE, &dwStatus, &dwLength, NULL));
  410. if (HTTP_STATUS_NOT_MODIFIED == dwStatus)
  411. {
  412. LOG_out("file '%s' is the same as on server", szFullSourcePath);
  413. return ERROR_ALREADY_EXISTS;
  414. }
  415. else if (HTTP_STATUS_OK != dwStatus)
  416. {
  417. LOG_error("file '%s' is not found", szFullSourcePath);
  418. return ERROR_FILE_NOT_FOUND;
  419. }
  420. dwLength = sizeof(DWORD);
  421. return_error_if_false(HttpQueryInfo(hInetFile, HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_CONTENT_LENGTH, pdwServerFileSize, &dwLength, NULL));
  422. if (0 == *pdwServerFileSize)
  423. { // 0 length files are not supported
  424. LOG_error("file '%s' is 0 length", szFullSourcePath);
  425. return ERROR_FILE_NOT_FOUND;
  426. }
  427. if (pstServer)
  428. {
  429. dwLength = sizeof(SYSTEMTIME);
  430. return_error_if_false(HttpQueryInfo(hInetFile, HTTP_QUERY_FLAG_SYSTEMTIME | HTTP_QUERY_LAST_MODIFIED, pstServer, (ULONG *)&dwLength, NULL));
  431. }
  432. return NO_ERROR;
  433. }
  434. // wait forever until hHandle signals
  435. static void InfiniteWaitForSingleObject(
  436. HANDLE hHandle
  437. ) {
  438. while (WAIT_OBJECT_0 + 1 == MsgWaitForMultipleObjects(1, &hHandle, FALSE, INFINITE, QS_ALLINPUT))
  439. {
  440. // Process messasges
  441. MSG msg;
  442. while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
  443. {
  444. TranslateMessage(&msg);
  445. DispatchMessage(&msg);
  446. }
  447. }
  448. }