Fix crash when clicking merge after switch
[TortoiseGit.git] / src / TortoiseProc / GravatarPictureBox.cpp
blob8044a68be0915f1cfc13fb22a960819f05250e7c
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2013-2014 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "stdafx.h"
20 #include "GravatarPictureBox.h"
21 #include "Picture.h"
22 #include "MessageBox.h"
23 #include "UnicodeUtils.h"
24 #include "Git.h"
25 #include "LoglistCommonResource.h"
26 #include "resource.h"
28 static CString CalcMD5(CString text)
30 HCRYPTPROV hProv = 0;
31 if (!CryptAcquireContext(&hProv, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
32 return _T("");
34 HCRYPTHASH hHash = 0;
35 if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash))
37 CryptReleaseContext(hProv, 0);
38 return _T("");
41 CStringA textA = CUnicodeUtils::GetUTF8(text);
42 if (!CryptHashData(hHash, (LPBYTE)textA.GetBuffer(), textA.GetLength(), 0))
44 CryptDestroyHash(hHash);
45 CryptReleaseContext(hProv, 0);
46 return _T("");
49 CString hash;
50 BYTE rgbHash[16];
51 DWORD cbHash = _countof(rgbHash);
52 if (!CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0))
54 CryptDestroyHash(hHash);
55 CryptReleaseContext(hProv, 0);
56 return _T("");
58 for (DWORD i = 0; i < cbHash; i++)
60 BYTE hi = rgbHash[i] >> 4;
61 BYTE lo = rgbHash[i] & 0xf;
62 hash.AppendChar(hi + (hi > 9 ? 87 : 48));
63 hash.AppendChar(lo + (lo > 9 ? 87 : 48));
66 CryptDestroyHash(hHash);
67 CryptReleaseContext(hProv, 0);
68 return hash;
71 BEGIN_MESSAGE_MAP(CGravatar, CStatic)
72 ON_WM_PAINT()
73 END_MESSAGE_MAP()
75 CGravatar::CGravatar()
76 : CStatic()
77 , m_gravatarEvent(INVALID_HANDLE_VALUE)
78 , m_gravatarThread(nullptr)
79 , m_gravatarExit(nullptr)
80 , m_bEnableGravatar(false)
82 m_gravatarLock.Init();
85 CGravatar::~CGravatar()
87 SafeTerminateGravatarThread();
88 m_gravatarLock.Term();
89 if (m_gravatarEvent)
90 CloseHandle(m_gravatarEvent);
93 void CGravatar::Init()
95 if (m_bEnableGravatar)
97 if (m_gravatarEvent == INVALID_HANDLE_VALUE)
98 m_gravatarEvent = ::CreateEvent(nullptr, FALSE, TRUE, nullptr);
99 if (m_gravatarThread == nullptr)
101 m_gravatarExit = new bool(false);
102 m_gravatarThread = AfxBeginThread([] (LPVOID lpVoid) -> UINT { ((CGravatar *)lpVoid)->GravatarThread(); return 0; }, this, THREAD_PRIORITY_BELOW_NORMAL);
103 if (m_gravatarThread == nullptr)
105 CMessageBox::Show(nullptr, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
106 delete m_gravatarExit;
107 m_gravatarExit = nullptr;
108 return;
114 void CGravatar::LoadGravatar(CString email)
116 if (m_gravatarThread == nullptr)
117 return;
119 if (email.IsEmpty())
121 m_gravatarLock.Lock();
122 m_email.Empty();
123 if (!m_filename.IsEmpty())
125 m_filename.Empty();
126 m_gravatarLock.Unlock();
127 Invalidate();
129 else
130 m_gravatarLock.Unlock();
131 return;
134 email.Trim();
135 email.MakeLower();
136 m_gravatarLock.Lock();
137 bool diff = m_email != email;
138 m_email = email;
139 m_gravatarLock.Unlock();
140 if (diff)
141 ::SetEvent(m_gravatarEvent);
144 void CGravatar::GravatarThread()
146 bool *gravatarExit = m_gravatarExit;
147 CString gravatarBaseUrl = CRegString(_T("Software\\TortoiseGit\\GravatarUrl"), _T("http://www.gravatar.com/avatar/%HASH%?d=identicon"));
149 CString hostname;
150 CString baseUrlPath;
151 URL_COMPONENTS urlComponents = {0};
152 urlComponents.dwStructSize = sizeof(urlComponents);
153 urlComponents.lpszHostName = hostname.GetBufferSetLength(INTERNET_MAX_HOST_NAME_LENGTH);
154 urlComponents.dwHostNameLength = INTERNET_MAX_HOST_NAME_LENGTH;
155 urlComponents.lpszUrlPath = baseUrlPath.GetBufferSetLength(INTERNET_MAX_PATH_LENGTH);
156 urlComponents.dwUrlPathLength = INTERNET_MAX_PATH_LENGTH;
157 if (!InternetCrackUrl(gravatarBaseUrl, gravatarBaseUrl.GetLength(), 0, &urlComponents))
159 m_filename.Empty();
160 delete gravatarExit;
161 return;
163 hostname.ReleaseBuffer();
164 baseUrlPath.ReleaseBuffer();
166 HINTERNET hOpenHandle = InternetOpen(L"TortoiseGit", INTERNET_OPEN_TYPE_PRECONFIG, nullptr, nullptr, 0);
167 bool isHttps = urlComponents.nScheme == INTERNET_SCHEME_HTTPS;
168 HINTERNET hConnectHandle = InternetConnect(hOpenHandle, hostname, urlComponents.nPort, nullptr, nullptr, isHttps ? INTERNET_SCHEME_HTTP : urlComponents.nScheme, 0, 0);
169 if (!hConnectHandle)
171 InternetCloseHandle(hOpenHandle);
172 m_filename.Empty();
173 delete gravatarExit;
174 return;
177 while (!*gravatarExit)
179 ::WaitForSingleObject(m_gravatarEvent, INFINITE);
180 while (!*gravatarExit)
182 m_gravatarLock.Lock();
183 CString email = m_email;
184 m_gravatarLock.Unlock();
185 if (email.IsEmpty())
186 break;
188 Sleep(500);
189 if (*gravatarExit)
190 break;
191 m_gravatarLock.Lock();
192 bool diff = email != m_email;
193 m_gravatarLock.Unlock();
194 if (diff)
195 continue;
197 CString md5 = CalcMD5(email);
198 if (md5.IsEmpty())
199 continue;
201 CString gravatarUrl = baseUrlPath;
202 gravatarUrl.Replace(_T("%HASH%"), md5);
203 CString tempFile;
204 GetTempPath(tempFile);
205 tempFile += md5;
206 if (PathFileExists(tempFile))
208 m_gravatarLock.Lock();
209 if (m_email == email)
211 m_filename = tempFile;
212 m_email.Empty();
214 else
215 m_filename.Empty();
216 m_gravatarLock.Unlock();
218 else
220 BOOL ret = DownloadToFile(gravatarExit, hConnectHandle, isHttps, gravatarUrl, tempFile);
221 if (ret)
223 DeleteFile(tempFile);
224 if (*gravatarExit)
225 break;
226 m_gravatarLock.Lock();
227 m_filename.Empty();
228 m_gravatarLock.Unlock();
230 if (*gravatarExit)
231 break;
232 m_gravatarLock.Lock();
233 if (m_email == email && !ret)
235 HANDLE hFile = CreateFile(tempFile, FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
236 FILETIME creationTime = {};
237 GetFileTime(hFile, &creationTime, nullptr, nullptr);
238 uint64_t delta = 7 * 24 * 60 * 60 * 10000000LL;
239 DWORD low = creationTime.dwLowDateTime;
240 creationTime.dwLowDateTime += delta & 0xffffffff;
241 creationTime.dwHighDateTime += delta >> 32;
242 if (creationTime.dwLowDateTime < low)
243 creationTime.dwHighDateTime++;
244 SetFileTime(hFile, &creationTime, nullptr, nullptr);
245 CloseHandle(hFile);
246 m_filename = tempFile;
247 m_email.Empty();
249 else
250 m_filename.Empty();
251 m_gravatarLock.Unlock();
253 if (*gravatarExit)
254 break;
255 Invalidate();
258 InternetCloseHandle(hConnectHandle);
259 InternetCloseHandle(hOpenHandle);
260 delete gravatarExit;
263 BOOL CGravatar::DownloadToFile(bool *gravatarExit, const HINTERNET hConnectHandle, bool isHttps, const CString& urlpath, const CString& dest)
265 HINTERNET hResourceHandle = HttpOpenRequest(hConnectHandle, nullptr, urlpath, nullptr, nullptr, nullptr, INTERNET_FLAG_KEEP_CONNECTION | (isHttps ? INTERNET_FLAG_SECURE : 0), 0);
266 if (!hResourceHandle)
267 return -1;
269 resend:
270 if (*gravatarExit)
272 InternetCloseHandle(hResourceHandle);
273 return INET_E_DOWNLOAD_FAILURE;
276 BOOL httpsendrequest = HttpSendRequest(hResourceHandle, nullptr, 0, nullptr, 0);
278 DWORD dwError = InternetErrorDlg(GetSafeHwnd(), hResourceHandle, ERROR_SUCCESS, FLAGS_ERROR_UI_FILTER_FOR_ERRORS | FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS | FLAGS_ERROR_UI_FLAGS_GENERATE_DATA, nullptr);
280 if (dwError == ERROR_INTERNET_FORCE_RETRY)
281 goto resend;
282 else if (!httpsendrequest || *gravatarExit)
284 InternetCloseHandle(hResourceHandle);
285 return INET_E_DOWNLOAD_FAILURE;
288 DWORD statusCode = 0;
289 DWORD length = sizeof(statusCode);
290 if (!HttpQueryInfo(hResourceHandle, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, (LPVOID)&statusCode, &length, NULL) || statusCode != 200)
292 InternetCloseHandle(hResourceHandle);
293 if (statusCode == 404)
294 return ERROR_FILE_NOT_FOUND;
295 else if (statusCode == 403)
296 return ERROR_ACCESS_DENIED;
297 return INET_E_DOWNLOAD_FAILURE;
300 CFile destinationFile;
301 if (!destinationFile.Open(dest, CFile::modeCreate | CFile::modeWrite))
303 InternetCloseHandle(hResourceHandle);
304 return ERROR_ACCESS_DENIED;
307 DWORD downloadedSum = 0; // sum of bytes downloaded so far
308 while (!*gravatarExit)
310 DWORD size; // size of the data available
311 if (!InternetQueryDataAvailable(hResourceHandle, &size, 0, 0))
313 InternetCloseHandle(hResourceHandle);
314 return INET_E_DOWNLOAD_FAILURE;
317 DWORD downloaded; // size of the downloaded data
318 LPTSTR lpszData = new TCHAR[size + 1];
319 if (!InternetReadFile(hResourceHandle, (LPVOID)lpszData, size, &downloaded))
321 delete[] lpszData;
322 InternetCloseHandle(hResourceHandle);
323 return INET_E_DOWNLOAD_FAILURE;
326 if (downloaded == 0)
328 delete[] lpszData;
329 break;
332 lpszData[downloaded] = '\0';
333 destinationFile.Write(lpszData, downloaded);
334 delete[] lpszData;
336 downloadedSum += downloaded;
338 destinationFile.Close();
339 InternetCloseHandle(hResourceHandle);
340 if (downloadedSum == 0)
341 return INET_E_DOWNLOAD_FAILURE;
343 return 0;
346 void CGravatar::SafeTerminateGravatarThread()
348 if (m_gravatarExit != nullptr)
349 *m_gravatarExit = true;
350 if (m_gravatarThread)
352 ::SetEvent(m_gravatarEvent);
353 ::WaitForSingleObject(m_gravatarThread, 1000);
354 m_gravatarThread = nullptr;
358 void CGravatar::OnPaint()
360 CPaintDC dc(this);
361 RECT rect;
362 GetClientRect(&rect);
363 dc.FillSolidRect(&rect, GetSysColor(COLOR_BTNFACE));
364 m_gravatarLock.Lock();
365 CString filename = m_filename;
366 m_gravatarLock.Unlock();
367 if (filename.IsEmpty()) return;
368 CPicture picture;
369 picture.Load(filename.GetString());
370 picture.Show(dc, rect);