Process Hacker
updater.c
Go to the documentation of this file.
1 /*
2  * Process Hacker Plugins -
3  * Update Checker Plugin
4  *
5  * Copyright (C) 2011-2015 dmex
6  *
7  * This file is part of Process Hacker.
8  *
9  * Process Hacker is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * Process Hacker is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #include "updater.h"
24 #include <verify.h>
25 
26 static HANDLE UpdateDialogThreadHandle = NULL;
27 static HWND UpdateDialogHandle = NULL;
28 static PH_EVENT InitializedEvent = PH_EVENT_INIT;
29 
30 static mxml_type_t QueryXmlDataCallback(
31  _In_ mxml_node_t *node
32  )
33 {
34  return MXML_OPAQUE;
35 }
36 
37 static BOOLEAN ParseVersionString(
38  _Inout_ PPH_UPDATER_CONTEXT Context
39  )
40 {
41  PH_STRINGREF sr, majorPart, minorPart, revisionPart;
42  ULONG64 majorInteger = 0, minorInteger = 0, revisionInteger = 0;
43 
44  PhInitializeStringRef(&sr, Context->Version->Buffer);
45  PhInitializeStringRef(&revisionPart, Context->RevVersion->Buffer);
46 
47  if (PhSplitStringRefAtChar(&sr, '.', &majorPart, &minorPart))
48  {
49  PhStringToInteger64(&majorPart, 10, &majorInteger);
50  PhStringToInteger64(&minorPart, 10, &minorInteger);
51  PhStringToInteger64(&revisionPart, 10, &revisionInteger);
52 
53  Context->MajorVersion = (ULONG)majorInteger;
54  Context->MinorVersion = (ULONG)minorInteger;
55  Context->RevisionVersion = (ULONG)revisionInteger;
56 
57  return TRUE;
58  }
59 
60  return FALSE;
61 }
62 
63 static BOOLEAN ReadRequestString(
64  _In_ HINTERNET Handle,
65  _Out_ _Deref_post_z_cap_(*DataLength) PSTR *Data,
66  _Out_ ULONG *DataLength
67  )
68 {
69  PSTR data;
70  ULONG allocatedLength;
71  ULONG dataLength;
72  ULONG returnLength;
73  BYTE buffer[PAGE_SIZE];
74 
75  allocatedLength = sizeof(buffer);
76  data = (PSTR)PhAllocate(allocatedLength);
77  dataLength = 0;
78 
79  // Zero the buffer
80  memset(buffer, 0, PAGE_SIZE);
81 
82  while (WinHttpReadData(Handle, buffer, PAGE_SIZE, &returnLength))
83  {
84  if (returnLength == 0)
85  break;
86 
87  if (allocatedLength < dataLength + returnLength)
88  {
89  allocatedLength *= 2;
90  data = (PSTR)PhReAllocate(data, allocatedLength);
91  }
92 
93  // Copy the returned buffer into our pointer
94  memcpy(data + dataLength, buffer, returnLength);
95  // Zero the returned buffer for the next loop
96  //memset(buffer, 0, returnLength);
97 
98  dataLength += returnLength;
99  }
100 
101  if (allocatedLength < dataLength + 1)
102  {
103  allocatedLength++;
104  data = (PSTR)PhReAllocate(data, allocatedLength);
105  }
106 
107  // Ensure that the buffer is null-terminated.
108  data[dataLength] = 0;
109 
110  *DataLength = dataLength;
111  *Data = data;
112 
113  return TRUE;
114 }
115 
116 static PPH_UPDATER_CONTEXT CreateUpdateContext(
117  VOID
118  )
119 {
120  PPH_UPDATER_CONTEXT context;
121 
122  context = (PPH_UPDATER_CONTEXT)PhAllocate(sizeof(PH_UPDATER_CONTEXT));
123  memset(context, 0, sizeof(PH_UPDATER_CONTEXT));
124 
125  return context;
126 }
127 
128 static VOID FreeUpdateContext(
129  _In_ _Post_invalid_ PPH_UPDATER_CONTEXT Context
130  )
131 {
132  if (!Context)
133  return;
134 
135  Context->HaveData = FALSE;
136  Context->UpdaterState = PhUpdateMaximum;
137 
138  Context->MinorVersion = 0;
139  Context->MajorVersion = 0;
140  Context->RevisionVersion = 0;
141  Context->CurrentMinorVersion = 0;
142  Context->CurrentMajorVersion = 0;
143  Context->CurrentRevisionVersion = 0;
144 
145  PhClearReference(&Context->UserAgent);
146  PhClearReference(&Context->Version);
147  PhClearReference(&Context->RevVersion);
148  PhClearReference(&Context->RelDate);
149  PhClearReference(&Context->Size);
150  PhClearReference(&Context->Hash);
151  PhClearReference(&Context->ReleaseNotesUrl);
152  PhClearReference(&Context->SetupFilePath);
153  PhClearReference(&Context->SetupFileDownloadUrl);
154 
155  if (Context->FontHandle)
156  {
157  DeleteObject(Context->FontHandle);
158  Context->FontHandle = NULL;
159  }
160 
161  if (Context->IconBitmap)
162  {
163  DeleteObject(Context->IconBitmap);
164  Context->IconBitmap = NULL;
165  }
166 
167  if (Context->IconHandle)
168  {
169  DestroyIcon(Context->IconHandle);
170  Context->IconHandle = NULL;
171  }
172 
173  PhFree(Context);
174 }
175 
176 static BOOLEAN QueryUpdateData(
177  _Inout_ PPH_UPDATER_CONTEXT Context
178  )
179 {
180  BOOLEAN isSuccess = FALSE;
181  HINTERNET httpSessionHandle = NULL;
182  HINTERNET httpConnectionHandle = NULL;
183  HINTERNET httpRequestHandle = NULL;
184  WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig = { 0 };
185  mxml_node_t* xmlNode = NULL;
186  ULONG xmlStringBufferLength = 0;
187  PSTR xmlStringBuffer = NULL;
188 
189  // Get the current Process Hacker version
191  &Context->CurrentMajorVersion,
192  &Context->CurrentMinorVersion,
193  NULL,
194  &Context->CurrentRevisionVersion
195  );
196 
197  __try
198  {
199  // Create a user agent string.
200  Context->UserAgent = PhFormatString(
201  L"PH_%lu.%lu_%lu",
202  Context->CurrentMajorVersion,
203  Context->CurrentMinorVersion,
204  Context->CurrentRevisionVersion
205  );
206  if (PhIsNullOrEmptyString(Context->UserAgent))
207  __leave;
208 
209  // Query the current system proxy
210  WinHttpGetIEProxyConfigForCurrentUser(&proxyConfig);
211 
212  // Open the HTTP session with the system proxy configuration if available
213  if (!(httpSessionHandle = WinHttpOpen(
214  Context->UserAgent->Buffer,
215  proxyConfig.lpszProxy != NULL ? WINHTTP_ACCESS_TYPE_NAMED_PROXY : WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
216  proxyConfig.lpszProxy,
217  proxyConfig.lpszProxyBypass,
218  0
219  )))
220  {
221  __leave;
222  }
223 
224  if (!(httpConnectionHandle = WinHttpConnect(
225  httpSessionHandle,
226  L"processhacker.sourceforge.net",
227  INTERNET_DEFAULT_HTTP_PORT,
228  0
229  )))
230  {
231  __leave;
232  }
233 
234  if (!(httpRequestHandle = WinHttpOpenRequest(
235  httpConnectionHandle,
236  NULL,
237  L"/update.php",
238  NULL,
239  WINHTTP_NO_REFERER,
240  WINHTTP_DEFAULT_ACCEPT_TYPES,
241  0 // WINHTTP_FLAG_REFRESH
242  )))
243  {
244  __leave;
245  }
246 
247  if (!WinHttpSendRequest(
248  httpRequestHandle,
249  WINHTTP_NO_ADDITIONAL_HEADERS, 0,
250  WINHTTP_NO_REQUEST_DATA, 0,
251  WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0
252  ))
253  {
254  __leave;
255  }
256 
257  if (!WinHttpReceiveResponse(httpRequestHandle, NULL))
258  __leave;
259 
260  // Read the resulting xml into our buffer.
261  if (!ReadRequestString(httpRequestHandle, &xmlStringBuffer, &xmlStringBufferLength))
262  __leave;
263 
264  // Check the buffer for valid data.
265  if (xmlStringBuffer == NULL || xmlStringBuffer[0] == '\0')
266  __leave;
267 
268  // Load our XML
269  xmlNode = mxmlLoadString(NULL, xmlStringBuffer, QueryXmlDataCallback);
270  if (xmlNode == NULL || xmlNode->type != MXML_ELEMENT)
271  __leave;
272 
273  // Find the version node
274  Context->Version = PhGetOpaqueXmlNodeText(
275  mxmlFindElement(xmlNode->child, xmlNode, "ver", NULL, NULL, MXML_DESCEND)
276  );
277  if (PhIsNullOrEmptyString(Context->Version))
278  __leave;
279 
280  // Find the revision node
281  Context->RevVersion = PhGetOpaqueXmlNodeText(
282  mxmlFindElement(xmlNode->child, xmlNode, "rev", NULL, NULL, MXML_DESCEND)
283  );
284  if (PhIsNullOrEmptyString(Context->RevVersion))
285  __leave;
286 
287  // Find the release date node
288  Context->RelDate = PhGetOpaqueXmlNodeText(
289  mxmlFindElement(xmlNode->child, xmlNode, "reldate", NULL, NULL, MXML_DESCEND)
290  );
291  if (PhIsNullOrEmptyString(Context->RelDate))
292  __leave;
293 
294  // Find the size node
295  Context->Size = PhGetOpaqueXmlNodeText(
296  mxmlFindElement(xmlNode->child, xmlNode, "size", NULL, NULL, MXML_DESCEND)
297  );
298  if (PhIsNullOrEmptyString(Context->Size))
299  __leave;
300 
301  //Find the hash node
302  Context->Hash = PhGetOpaqueXmlNodeText(
303  mxmlFindElement(xmlNode->child, xmlNode, "sha1", NULL, NULL, MXML_DESCEND)
304  );
305  if (PhIsNullOrEmptyString(Context->Hash))
306  __leave;
307 
308  // Find the release notes URL
309  Context->ReleaseNotesUrl = PhGetOpaqueXmlNodeText(
310  mxmlFindElement(xmlNode->child, xmlNode, "relnotes", NULL, NULL, MXML_DESCEND)
311  );
312  if (PhIsNullOrEmptyString(Context->ReleaseNotesUrl))
313  __leave;
314 
315  // Find the installer download URL
316  Context->SetupFileDownloadUrl = PhGetOpaqueXmlNodeText(
317  mxmlFindElement(xmlNode->child, xmlNode, "setupurl", NULL, NULL, MXML_DESCEND)
318  );
319  if (PhIsNullOrEmptyString(Context->SetupFileDownloadUrl))
320  __leave;
321 
322  if (!ParseVersionString(Context))
323  __leave;
324 
325  isSuccess = TRUE;
326  }
327  __finally
328  {
329  if (httpRequestHandle)
330  WinHttpCloseHandle(httpRequestHandle);
331 
332  if (httpConnectionHandle)
333  WinHttpCloseHandle(httpConnectionHandle);
334 
335  if (httpSessionHandle)
336  WinHttpCloseHandle(httpSessionHandle);
337 
338  if (xmlNode)
339  mxmlDelete(xmlNode);
340 
341  if (xmlStringBuffer)
342  PhFree(xmlStringBuffer);
343  }
344 
345  return isSuccess;
346 }
347 
348 static NTSTATUS UpdateCheckSilentThread(
349  _In_ PVOID Parameter
350  )
351 {
352  PPH_UPDATER_CONTEXT context = NULL;
353  ULONGLONG currentVersion = 0;
354  ULONGLONG latestVersion = 0;
355 
356  context = CreateUpdateContext();
357 
358  __try
359  {
360  if (!QueryUpdateData(context))
361  __leave;
362 
363  currentVersion = MAKEDLLVERULL(
364  context->CurrentMajorVersion,
365  context->CurrentMinorVersion,
366  0,
367  context->CurrentRevisionVersion
368  );
369 
370 #ifdef DEBUG_UPDATE
371  latestVersion = MAKEDLLVERULL(
372  9999,
373  9999,
374  0,
375  9999
376  );
377 #else
378  latestVersion = MAKEDLLVERULL(
379  context->MajorVersion,
380  context->MinorVersion,
381  0,
382  context->RevisionVersion
383  );
384 #endif
385 
386  // Compare the current version against the latest available version
387  if (currentVersion < latestVersion)
388  {
389  // Don't spam the user the second they open PH, delay dialog creation for 3 seconds.
390  Sleep(3000);
391 
392  if (!UpdateDialogHandle)
393  {
394  // We have data we're going to cache and pass into the dialog
395  context->HaveData = TRUE;
396 
397  // Show the dialog asynchronously on a new thread.
398  ShowUpdateDialog(context);
399  }
400  }
401  }
402  __finally
403  {
404  // Check the dialog doesn't own the window context...
405  if (context->HaveData == FALSE)
406  FreeUpdateContext(context);
407  }
408 
409  return STATUS_SUCCESS;
410 }
411 
412 static NTSTATUS UpdateCheckThread(
413  _In_ PVOID Parameter
414  )
415 {
416  PPH_UPDATER_CONTEXT context = NULL;
417  ULONGLONG currentVersion = 0;
418  ULONGLONG latestVersion = 0;
419 
420  context = (PPH_UPDATER_CONTEXT)Parameter;
421 
422  // Check if we have cached update data
423  if (!context->HaveData)
424  context->HaveData = QueryUpdateData(context);
425 
426  // sanity check
427  if (!context->HaveData)
428  {
429  PostMessage(context->DialogHandle, PH_UPDATEISERRORED, 0, 0);
430  return STATUS_SUCCESS;
431  }
432 
433  currentVersion = MAKEDLLVERULL(
434  context->CurrentMajorVersion,
435  context->CurrentMinorVersion,
436  0,
437  context->CurrentRevisionVersion
438  );
439 
440 #ifdef DEBUG_UPDATE
441  latestVersion = MAKEDLLVERULL(
442  9999,
443  9999,
444  0,
445  9999
446  );
447 #else
448  latestVersion = MAKEDLLVERULL(
449  context->MajorVersion,
450  context->MinorVersion,
451  0,
452  context->RevisionVersion
453  );
454 #endif
455 
456  if (currentVersion == latestVersion)
457  {
458  // User is running the latest version
459  PostMessage(context->DialogHandle, PH_UPDATEISCURRENT, 0, 0);
460  }
461  else if (currentVersion > latestVersion)
462  {
463  // User is running a newer version
464  PostMessage(context->DialogHandle, PH_UPDATENEWER, 0, 0);
465  }
466  else
467  {
468  // User is running an older version
469  PostMessage(context->DialogHandle, PH_UPDATEAVAILABLE, 0, 0);
470  }
471 
472  return STATUS_SUCCESS;
473 }
474 
475 static NTSTATUS UpdateDownloadThread(
476  _In_ PVOID Parameter
477  )
478 {
479  BOOLEAN downloadSuccess = FALSE;
480  BOOLEAN hashSuccess = FALSE;
481  BOOLEAN verifySuccess = FALSE;
482  HANDLE tempFileHandle = NULL;
483  HINTERNET httpSessionHandle = NULL;
484  HINTERNET httpConnectionHandle = NULL;
485  HINTERNET httpRequestHandle = NULL;
486  PPH_STRING setupTempPath = NULL;
487  PPH_STRING downloadHostPath = NULL;
488  PPH_STRING downloadUrlPath = NULL;
489  URL_COMPONENTS httpUrlComponents = { sizeof(URL_COMPONENTS) };
490  WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig = { 0 };
491 
492  PPH_UPDATER_CONTEXT context = (PPH_UPDATER_CONTEXT)Parameter;
493 
494  __try
495  {
496  // Allocate the GetTempPath buffer
497  setupTempPath = PhCreateStringEx(NULL, GetTempPath(0, NULL) * sizeof(WCHAR));
498  if (PhIsNullOrEmptyString(setupTempPath))
499  __leave;
500 
501  // Get the temp path
502  if (GetTempPath((ULONG)setupTempPath->Length / sizeof(WCHAR), setupTempPath->Buffer) == 0)
503  __leave;
504  if (PhIsNullOrEmptyString(setupTempPath))
505  __leave;
506 
507  // Append the tempath to our string: %TEMP%processhacker-%lu.%lu-setup.exe
508  // Example: C:\\Users\\dmex\\AppData\\Temp\\processhacker-2.90-setup.exe
509  context->SetupFilePath = PhFormatString(
510  L"%sprocesshacker-%lu.%lu-setup.exe",
511  setupTempPath->Buffer,
512  context->MajorVersion,
513  context->MinorVersion
514  );
515  if (PhIsNullOrEmptyString(context->SetupFilePath))
516  __leave;
517 
518  // Create output file
520  &tempFileHandle,
521  context->SetupFilePath->Buffer,
522  FILE_GENERIC_READ | FILE_GENERIC_WRITE,
523  FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | FILE_ATTRIBUTE_TEMPORARY,
524  FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
527  )))
528  {
529  __leave;
530  }
531 
532  // Set lengths to non-zero enabling these params to be cracked.
533  httpUrlComponents.dwSchemeLength = (ULONG)-1;
534  httpUrlComponents.dwHostNameLength = (ULONG)-1;
535  httpUrlComponents.dwUrlPathLength = (ULONG)-1;
536 
537  if (!WinHttpCrackUrl(
538  context->SetupFileDownloadUrl->Buffer,
539  (ULONG)context->SetupFileDownloadUrl->Length,
540  0,
541  &httpUrlComponents
542  ))
543  {
544  __leave;
545  }
546 
547  // Create the Host string.
548  downloadHostPath = PhCreateStringEx(
549  httpUrlComponents.lpszHostName,
550  httpUrlComponents.dwHostNameLength * sizeof(WCHAR)
551  );
552  if (PhIsNullOrEmptyString(downloadHostPath))
553  __leave;
554 
555  // Create the Path string.
556  downloadUrlPath = PhCreateStringEx(
557  httpUrlComponents.lpszUrlPath,
558  httpUrlComponents.dwUrlPathLength * sizeof(WCHAR)
559  );
560  if (PhIsNullOrEmptyString(downloadUrlPath))
561  __leave;
562 
563  SetDlgItemText(context->DialogHandle, IDC_STATUS, L"Connecting...");
564 
565  // Query the current system proxy
566  WinHttpGetIEProxyConfigForCurrentUser(&proxyConfig);
567 
568  // Open the HTTP session with the system proxy configuration if available
569  if (!(httpSessionHandle = WinHttpOpen(
570  context->UserAgent->Buffer,
571  proxyConfig.lpszProxy != NULL ? WINHTTP_ACCESS_TYPE_NAMED_PROXY : WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
572  proxyConfig.lpszProxy,
573  proxyConfig.lpszProxyBypass,
574  0
575  )))
576  {
577  __leave;
578  }
579 
580  if (!(httpConnectionHandle = WinHttpConnect(
581  httpSessionHandle,
582  downloadHostPath->Buffer,
583  INTERNET_DEFAULT_HTTP_PORT,
584  0
585  )))
586  {
587  __leave;
588  }
589 
590  if (!(httpRequestHandle = WinHttpOpenRequest(
591  httpConnectionHandle,
592  NULL,
593  downloadUrlPath->Buffer,
594  NULL,
595  WINHTTP_NO_REFERER,
596  WINHTTP_DEFAULT_ACCEPT_TYPES,
597  WINHTTP_FLAG_REFRESH
598  )))
599  {
600  __leave;
601  }
602 
603  SetDlgItemText(context->DialogHandle, IDC_STATUS, L"Sending request...");
604 
605  if (!WinHttpSendRequest(
606  httpRequestHandle,
607  WINHTTP_NO_ADDITIONAL_HEADERS,
608  0,
609  WINHTTP_NO_REQUEST_DATA,
610  0,
611  WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH,
612  0
613  ))
614  {
615  __leave;
616  }
617 
618  SetDlgItemText(context->DialogHandle, IDC_STATUS, L"Waiting for response...");
619 
620  if (WinHttpReceiveResponse(httpRequestHandle, NULL))
621  {
622  ULONG bytesDownloaded = 0;
623  ULONG downloadedBytes = 0;
624  ULONG contentLengthSize = sizeof(ULONG);
625  ULONG contentLength = 0;
626  BYTE buffer[PAGE_SIZE];
627  BYTE hashBuffer[20];
628 
629  PH_HASH_CONTEXT hashContext;
630  IO_STATUS_BLOCK isb;
631 
632  if (!WinHttpQueryHeaders(
633  httpRequestHandle,
634  WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER,
635  WINHTTP_HEADER_NAME_BY_INDEX,
636  &contentLength,
637  &contentLengthSize,
638  0
639  ))
640  {
641  __leave;
642  }
643 
644  // Initialize hash algorithm.
645  PhInitializeHash(&hashContext, Sha1HashAlgorithm);
646 
647  // Zero the buffer.
648  memset(buffer, 0, PAGE_SIZE);
649 
650  // Download the data.
651  while (WinHttpReadData(httpRequestHandle, buffer, PAGE_SIZE, &bytesDownloaded))
652  {
653  // If we get zero bytes, the file was uploaded or there was an error
654  if (bytesDownloaded == 0)
655  break;
656 
657  // If the dialog was closed, just cleanup and exit
658  //if (context->UpdaterState == PhUpdateMaximum)
659  if (!UpdateDialogThreadHandle)
660  __leave;
661 
662  // Update the hash of bytes we downloaded.
663  PhUpdateHash(&hashContext, buffer, bytesDownloaded);
664 
665  // Write the downloaded bytes to disk.
666  if (!NT_SUCCESS(NtWriteFile(
667  tempFileHandle,
668  NULL,
669  NULL,
670  NULL,
671  &isb,
672  buffer,
673  bytesDownloaded,
674  NULL,
675  NULL
676  )))
677  {
678  __leave;
679  }
680 
681  downloadedBytes += (DWORD)isb.Information;
682 
683  // Check the number of bytes written are the same we downloaded.
684  if (bytesDownloaded != isb.Information)
685  __leave;
686 
687  // Update the GUI progress.
688  // TODO: Update on GUI thread.
689  {
690  //int percent = MulDiv(100, downloadedBytes, contentLength);
691  FLOAT percent = ((FLOAT)downloadedBytes / contentLength * 100);
692  PPH_STRING totalDownloaded = PhFormatSize(downloadedBytes, -1);
693  PPH_STRING totalLength = PhFormatSize(contentLength, -1);
694 
695  PPH_STRING dlLengthString = PhFormatString(
696  L"%s of %s (%.0f%%)",
697  totalDownloaded->Buffer,
698  totalLength->Buffer,
699  percent
700  );
701 
702  // Update the progress bar position
703  SendMessage(context->ProgressHandle, PBM_SETPOS, (ULONG)percent, 0);
704  Static_SetText(context->StatusHandle, dlLengthString->Buffer);
705 
706  PhDereferenceObject(dlLengthString);
707  PhDereferenceObject(totalDownloaded);
708  PhDereferenceObject(totalLength);
709  }
710  }
711 
712  // Compute hash result (will fail if file not downloaded correctly).
713  if (PhFinalHash(&hashContext, &hashBuffer, 20, NULL))
714  {
715  // Allocate our hash string, hex the final hash result in our hashBuffer.
716  PPH_STRING hexString = PhBufferToHexString(hashBuffer, 20);
717 
718  if (PhEqualString(hexString, context->Hash, TRUE))
719  {
720  hashSuccess = TRUE;
721  }
722 
723  PhDereferenceObject(hexString);
724  }
725  }
726 
727  downloadSuccess = TRUE;
728  }
729  __finally
730  {
731  if (tempFileHandle)
732  NtClose(tempFileHandle);
733 
734  if (httpRequestHandle)
735  WinHttpCloseHandle(httpRequestHandle);
736 
737  if (httpConnectionHandle)
738  WinHttpCloseHandle(httpConnectionHandle);
739 
740  if (httpSessionHandle)
741  WinHttpCloseHandle(httpSessionHandle);
742 
743  PhClearReference(&setupTempPath);
744  PhClearReference(&downloadHostPath);
745  PhClearReference(&downloadUrlPath);
746  }
747 
748  if (context->SetupFilePath && PhVerifyFile(context->SetupFilePath->Buffer, NULL) == VrTrusted)
749  {
750  verifySuccess = TRUE;
751  }
752 
753  if (downloadSuccess && hashSuccess && verifySuccess)
754  {
755  PostMessage(context->DialogHandle, PH_UPDATESUCCESS, 0, 0);
756  }
757  else if (downloadSuccess)
758  {
759  PostMessage(context->DialogHandle, PH_UPDATEFAILURE, verifySuccess, hashSuccess);
760  }
761  else
762  {
763  PostMessage(context->DialogHandle, PH_UPDATEISERRORED, 0, 0);
764  }
765 
766  return STATUS_SUCCESS;
767 }
768 
769 static INT_PTR CALLBACK UpdaterWndProc(
770  _In_ HWND hwndDlg,
771  _In_ UINT uMsg,
772  _In_ WPARAM wParam,
773  _In_ LPARAM lParam
774  )
775 {
776  PPH_UPDATER_CONTEXT context = NULL;
777 
778  if (uMsg == WM_INITDIALOG)
779  {
780  context = (PPH_UPDATER_CONTEXT)lParam;
781  SetProp(hwndDlg, L"Context", (HANDLE)context);
782  }
783  else
784  {
785  context = (PPH_UPDATER_CONTEXT)GetProp(hwndDlg, L"Context");
786 
787  if (uMsg == WM_NCDESTROY)
788  {
789  RemoveProp(hwndDlg, L"Context");
790  FreeUpdateContext(context);
791  context = NULL;
792  }
793  }
794 
795  if (context == NULL)
796  return FALSE;
797 
798  switch (uMsg)
799  {
800  case WM_INITDIALOG:
801  {
802  LOGFONT headerFont;
803  HWND parentWindow = GetParent(hwndDlg);
804 
805  memset(&headerFont, 0, sizeof(LOGFONT));
806  headerFont.lfHeight = -15;
807  headerFont.lfWeight = FW_MEDIUM;
808  headerFont.lfQuality = CLEARTYPE_QUALITY | ANTIALIASED_QUALITY;
809 
810  context->DialogHandle = hwndDlg;
811  context->StatusHandle = GetDlgItem(hwndDlg, IDC_STATUS);
812  context->ProgressHandle = GetDlgItem(hwndDlg, IDC_PROGRESS);
813 
814  // Create the font handle
815  context->FontHandle = CreateFontIndirect(&headerFont);
816 
817  // Load the Process Hacker icon.
818  context->IconHandle = (HICON)LoadImage(
819  GetModuleHandle(NULL),
820  MAKEINTRESOURCE(PHAPP_IDI_PROCESSHACKER),
821  IMAGE_ICON,
822  32,
823  32,
824  0
825  );
826 
827  context->IconBitmap = PhIconToBitmap(context->IconHandle, 32, 32);
828 
829  // Set the window icons
830  if (context->IconHandle)
831  SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)context->IconHandle);
832  // Set the text font
833  if (context->FontHandle)
834  SendMessage(GetDlgItem(hwndDlg, IDC_MESSAGE), WM_SETFONT, (WPARAM)context->FontHandle, FALSE);
835  // Set the window image
836  if (context->IconBitmap)
837  SendMessage(GetDlgItem(hwndDlg, IDC_UPDATEICON), STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)context->IconBitmap);
838 
839  // Center the update window on PH if it's visible else we center on the desktop.
840  PhCenterWindow(hwndDlg, (IsWindowVisible(parentWindow) && !IsIconic(parentWindow)) ? parentWindow : NULL);
841 
842  // Show new version info (from the background update check)
843  if (context->HaveData)
844  {
845  HANDLE updateCheckThread = NULL;
846 
847  // Create the update check thread.
848  if (updateCheckThread = PhCreateThread(0, UpdateCheckThread, context))
849  NtClose(updateCheckThread);
850  }
851  }
852  break;
853  case WM_SHOWDIALOG:
854  {
855  if (IsIconic(hwndDlg))
856  ShowWindow(hwndDlg, SW_RESTORE);
857  else
858  ShowWindow(hwndDlg, SW_SHOW);
859 
860  SetForegroundWindow(hwndDlg);
861  }
862  break;
863  case WM_CTLCOLORBTN:
864  case WM_CTLCOLORDLG:
865  case WM_CTLCOLORSTATIC:
866  {
867  HDC hDC = (HDC)wParam;
868  HWND hwndChild = (HWND)lParam;
869 
870  // Check for our static label and change the color.
871  if (GetDlgCtrlID(hwndChild) == IDC_MESSAGE)
872  {
873  SetTextColor(hDC, RGB(19, 112, 171));
874  }
875 
876  // Set a transparent background for the control backcolor.
877  SetBkMode(hDC, TRANSPARENT);
878 
879  // set window background color.
880  return (INT_PTR)GetSysColorBrush(COLOR_WINDOW);
881  }
882  break;
883  case WM_COMMAND:
884  {
885  switch (GET_WM_COMMAND_ID(wParam, lParam))
886  {
887  case IDCANCEL:
888  case IDOK:
889  {
890  PostQuitMessage(0);
891  }
892  break;
893  case IDC_DOWNLOAD:
894  {
895  switch (context->UpdaterState)
896  {
897  case PhUpdateDefault:
898  {
899  HANDLE updateCheckThread = NULL;
900 
901  SetDlgItemText(hwndDlg, IDC_MESSAGE, L"Checking for new releases...");
902  SetDlgItemText(hwndDlg, IDC_RELDATE, L"");
903  Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), FALSE);
904 
905  if (updateCheckThread = PhCreateThread(0, UpdateCheckThread, context))
906  NtClose(updateCheckThread);
907  }
908  break;
909  case PhUpdateDownload:
910  {
911  if (PhInstalledUsingSetup())
912  {
913  HANDLE downloadThreadHandle = NULL;
914 
915  // Disable the download button
916  Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), FALSE);
917 
918  // Reset the progress bar (might be a download retry)
919  SendDlgItemMessage(hwndDlg, IDC_PROGRESS, PBM_SETPOS, 0, 0);
920 
922  SendDlgItemMessage(hwndDlg, IDC_PROGRESS, PBM_SETSTATE, PBST_NORMAL, 0);
923 
924  // Start our Downloader thread
925  if (downloadThreadHandle = PhCreateThread(0, (PUSER_THREAD_START_ROUTINE)UpdateDownloadThread, context))
926  NtClose(downloadThreadHandle);
927  }
928  else
929  {
930  // Let the user handle non-setup installation, show the homepage and close this dialog.
931  PhShellExecute(hwndDlg, L"http://processhacker.sourceforge.net/downloads.php", NULL);
932  PostQuitMessage(0);
933  }
934  }
935  break;
936  case PhUpdateInstall:
937  {
938  SHELLEXECUTEINFO info = { sizeof(SHELLEXECUTEINFO) };
939 
940  if (PhIsNullOrEmptyString(context->SetupFilePath))
941  break;
942 
943  info.lpFile = context->SetupFilePath->Buffer;
944  info.lpVerb = PhElevated ? NULL : L"runas";
945  info.nShow = SW_SHOW;
946  info.hwnd = hwndDlg;
947 
949 
950  if (!ShellExecuteEx(&info))
951  {
952  // Install failed, cancel the shutdown.
954 
955  // Set button text for next action
956  Button_SetText(GetDlgItem(hwndDlg, IDC_DOWNLOAD), L"Retry");
957  }
958  else
959  {
961  }
962  }
963  break;
964  }
965  }
966  break;
967  }
968  break;
969  }
970  break;
971  case PH_UPDATEAVAILABLE:
972  {
973  // Set updater state
974  context->UpdaterState = PhUpdateDownload;
975 
976  // Set the UI text
977  SetDlgItemText(hwndDlg, IDC_MESSAGE, PhaFormatString(
978  L"Process Hacker %lu.%lu (r%lu)",
979  context->MajorVersion,
980  context->MinorVersion,
981  context->RevisionVersion
982  )->Buffer);
983  SetDlgItemText(hwndDlg, IDC_RELDATE, PhaFormatString(
984  L"Released: %s",
985  context->RelDate->Buffer
986  )->Buffer);
987  SetDlgItemText(hwndDlg, IDC_STATUS, PhaFormatString(
988  L"Size: %s",
989  context->Size->Buffer
990  )->Buffer);
991  Button_SetText(GetDlgItem(hwndDlg, IDC_DOWNLOAD), L"Download");
992 
993  // Enable the controls
994  Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), TRUE);
995  Control_Visible(GetDlgItem(hwndDlg, IDC_PROGRESS), TRUE);
996  Control_Visible(GetDlgItem(hwndDlg, IDC_INFOSYSLINK), TRUE);
997  }
998  break;
999  case PH_UPDATEISCURRENT:
1000  {
1001  // Set updater state
1002  context->UpdaterState = PhUpdateMaximum;
1003 
1004  // Set the UI text
1005  SetDlgItemText(hwndDlg, IDC_MESSAGE, L"You're running the latest version.");
1006  SetDlgItemText(hwndDlg, IDC_RELDATE, PhaFormatString(
1007  L"Stable release build: v%lu.%lu (r%lu)",
1008  context->CurrentMajorVersion,
1009  context->CurrentMinorVersion,
1010  context->CurrentRevisionVersion
1011  )->Buffer);
1012 
1013  // Disable the download button
1014  Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), FALSE);
1015  // Enable the changelog link
1016  Control_Visible(GetDlgItem(hwndDlg, IDC_INFOSYSLINK), TRUE);
1017  }
1018  break;
1019  case PH_UPDATENEWER:
1020  {
1021  context->UpdaterState = PhUpdateMaximum;
1022 
1023  // Set the UI text
1024  SetDlgItemText(hwndDlg, IDC_MESSAGE, L"You're running a newer version!");
1025  SetDlgItemText(hwndDlg, IDC_RELDATE, PhaFormatString(
1026  L"SVN release build: v%lu.%lu (r%lu)",
1027  context->CurrentMajorVersion,
1028  context->CurrentMinorVersion,
1029  context->CurrentRevisionVersion
1030  )->Buffer);
1031 
1032  // Disable the download button
1033  Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), FALSE);
1034  // Disable the changelog link
1035  Control_Visible(GetDlgItem(hwndDlg, IDC_INFOSYSLINK), FALSE);
1036  }
1037  break;
1038  case PH_UPDATESUCCESS:
1039  {
1040  context->UpdaterState = PhUpdateInstall;
1041 
1042  // If PH is not elevated, set the UAC shield for the install button as the setup requires elevation.
1043  if (!PhElevated)
1044  SendMessage(GetDlgItem(hwndDlg, IDC_DOWNLOAD), BCM_SETSHIELD, 0, TRUE);
1045 
1046  // Set the download result, don't include hash status since it succeeded.
1047  SetDlgItemText(hwndDlg, IDC_STATUS, L"Click Install to continue update...");
1048 
1049  // Set button text for next action
1050  Button_SetText(GetDlgItem(hwndDlg, IDC_DOWNLOAD), L"Install");
1051  // Enable the Download/Install button so the user can install the update
1052  Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), TRUE);
1053  }
1054  break;
1055  case PH_UPDATEFAILURE:
1056  {
1057  context->UpdaterState = PhUpdateDefault;
1058 
1059  if (WindowsVersion > WINDOWS_XP)
1060  SendDlgItemMessage(hwndDlg, IDC_PROGRESS, PBM_SETSTATE, PBST_ERROR, 0);
1061 
1062  SetDlgItemText(hwndDlg, IDC_MESSAGE, L"Please check for updates again...");
1063  SetDlgItemText(hwndDlg, IDC_RELDATE, L"An error was encountered while checking for updates.");
1064 
1065  if ((BOOLEAN)wParam)
1066  SetDlgItemText(hwndDlg, IDC_STATUS, L"Hash check failed.");
1067  else if ((BOOLEAN)lParam)
1068  SetDlgItemText(hwndDlg, IDC_STATUS, L"Signature check failed.");
1069 
1070  // Set button text for next action
1071  Button_SetText(GetDlgItem(hwndDlg, IDC_DOWNLOAD), L"Retry");
1072  // Enable the Install button
1073  Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), TRUE);
1074  // Hash failed, reset state to downloading so user can redownload the file.
1075  }
1076  break;
1077  case PH_UPDATEISERRORED:
1078  {
1079  context->UpdaterState = PhUpdateDefault;
1080 
1081  SetDlgItemText(hwndDlg, IDC_MESSAGE, L"Please check for updates again...");
1082  SetDlgItemText(hwndDlg, IDC_RELDATE, L"An error was encountered while checking for updates.");
1083 
1084  Button_SetText(GetDlgItem(hwndDlg, IDC_DOWNLOAD), L"Retry");
1085  Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), TRUE);
1086  }
1087  break;
1088  case WM_NOTIFY:
1089  {
1090  switch (((LPNMHDR)lParam)->code)
1091  {
1092  case NM_CLICK: // Mouse
1093  case NM_RETURN: // Keyboard
1094  {
1095  // Launch the ReleaseNotes URL (if it exists) with the default browser
1096  if (!PhIsNullOrEmptyString(context->ReleaseNotesUrl))
1097  PhShellExecute(hwndDlg, context->ReleaseNotesUrl->Buffer, NULL);
1098  }
1099  break;
1100  }
1101  }
1102  break;
1103  }
1104 
1105  return FALSE;
1106 }
1107 
1108 static NTSTATUS ShowUpdateDialogThread(
1109  _In_ PVOID Parameter
1110  )
1111 {
1112  BOOL result;
1113  MSG message;
1114  PH_AUTO_POOL autoPool;
1115  PPH_UPDATER_CONTEXT context = NULL;
1116 
1117  if (Parameter)
1118  context = (PPH_UPDATER_CONTEXT)Parameter;
1119  else
1120  context = CreateUpdateContext();
1121 
1122  PhInitializeAutoPool(&autoPool);
1123 
1124  UpdateDialogHandle = CreateDialogParam(
1126  MAKEINTRESOURCE(IDD_UPDATE),
1129  (LPARAM)context
1130  );
1131 
1132  PhSetEvent(&InitializedEvent);
1133 
1134  while (result = GetMessage(&message, NULL, 0, 0))
1135  {
1136  if (result == -1)
1137  break;
1138 
1139  if (!IsDialogMessage(UpdateDialogHandle, &message))
1140  {
1141  TranslateMessage(&message);
1142  DispatchMessage(&message);
1143  }
1144 
1145  PhDrainAutoPool(&autoPool);
1146  }
1147 
1148  PhDeleteAutoPool(&autoPool);
1149  PhResetEvent(&InitializedEvent);
1150 
1151  if (UpdateDialogHandle)
1152  {
1153  DestroyWindow(UpdateDialogHandle);
1154  UpdateDialogHandle = NULL;
1155  }
1156 
1157  if (UpdateDialogThreadHandle)
1158  {
1159  NtClose(UpdateDialogThreadHandle);
1160  UpdateDialogThreadHandle = NULL;
1161  }
1162 
1163  return STATUS_SUCCESS;
1164 }
1165 
1167  _In_opt_ PPH_UPDATER_CONTEXT Context
1168  )
1169 {
1170  if (!UpdateDialogThreadHandle)
1171  {
1172  if (!(UpdateDialogThreadHandle = PhCreateThread(0, ShowUpdateDialogThread, Context)))
1173  {
1174  PhShowStatus(PhMainWndHandle, L"Unable to create the updater window.", 0, GetLastError());
1175  return;
1176  }
1177 
1178  PhWaitForEvent(&InitializedEvent, NULL);
1179  }
1180 
1181  PostMessage(UpdateDialogHandle, WM_SHOWDIALOG, 0, 0);
1182 }
1183 
1185  VOID
1186  )
1187 {
1188  HANDLE silentCheckThread = NULL;
1189 
1190  if (silentCheckThread = PhCreateThread(0, UpdateCheckSilentThread, NULL))
1191  NtClose(silentCheckThread);
1192 }