Make sure buffer is large enough for the nul terminator
[TortoiseGit.git] / src / TortoiseProc / GravatarPictureBox.cpp
blob39a058c93d5379506aeeaa896d78da66358f3ca9
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"
27 #include <WinCrypt.h>
29 static CString CalcMD5(CString text)
31 HCRYPTPROV hProv = 0;
32 if (!CryptAcquireContext(&hProv, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
33 return _T("");
35 HCRYPTHASH hHash = 0;
36 if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash))
38 CryptReleaseContext(hProv, 0);
39 return _T("");
42 CStringA textA = CUnicodeUtils::GetUTF8(text);
43 if (!CryptHashData(hHash, (LPBYTE)(LPCSTR)textA, textA.GetLength(), 0))
45 CryptDestroyHash(hHash);
46 CryptReleaseContext(hProv, 0);
47 return _T("");
50 CString hash;
51 BYTE rgbHash[16];
52 DWORD cbHash = _countof(rgbHash);
53 if (!CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0))
55 CryptDestroyHash(hHash);
56 CryptReleaseContext(hProv, 0);
57 return _T("");
59 for (DWORD i = 0; i < cbHash; i++)
61 BYTE hi = rgbHash[i] >> 4;
62 BYTE lo = rgbHash[i] & 0xf;
63 hash.AppendChar(hi + (hi > 9 ? 87 : 48));
64 hash.AppendChar(lo + (lo > 9 ? 87 : 48));
67 CryptDestroyHash(hHash);
68 CryptReleaseContext(hProv, 0);
69 return hash;
72 BEGIN_MESSAGE_MAP(CGravatar, CStatic)
73 ON_WM_PAINT()
74 END_MESSAGE_MAP()
76 CGravatar::CGravatar()
77 : CStatic()
78 , m_gravatarEvent(INVALID_HANDLE_VALUE)
79 , m_gravatarThread(nullptr)
80 , m_gravatarExit(nullptr)
81 , m_bEnableGravatar(false)
83 m_gravatarLock.Init();
86 CGravatar::~CGravatar()
88 SafeTerminateGravatarThread();
89 m_gravatarLock.Term();
90 if (m_gravatarEvent)
91 CloseHandle(m_gravatarEvent);
94 void CGravatar::Init()
96 if (m_bEnableGravatar)
98 if (m_gravatarEvent == INVALID_HANDLE_VALUE)
99 m_gravatarEvent = ::CreateEvent(nullptr, FALSE, TRUE, nullptr);
100 if (m_gravatarThread == nullptr)
102 m_gravatarExit = new bool(false);
103 m_gravatarThread = AfxBeginThread([] (LPVOID lpVoid) -> UINT { ((CGravatar *)lpVoid)->GravatarThread(); return 0; }, this, THREAD_PRIORITY_BELOW_NORMAL);
104 if (m_gravatarThread == nullptr)
106 CMessageBox::Show(nullptr, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
107 delete m_gravatarExit;
108 m_gravatarExit = nullptr;
109 return;
115 void CGravatar::LoadGravatar(CString email)
117 if (m_gravatarThread == nullptr)
118 return;
120 if (email.IsEmpty())
122 m_gravatarLock.Lock();
123 m_email.Empty();
124 if (!m_filename.IsEmpty())
126 m_filename.Empty();
127 m_gravatarLock.Unlock();
128 Invalidate();
130 else
131 m_gravatarLock.Unlock();
132 return;
135 email.Trim();
136 email.MakeLower();
137 m_gravatarLock.Lock();
138 bool diff = m_email != email;
139 m_email = email;
140 m_gravatarLock.Unlock();
141 if (diff)
142 ::SetEvent(m_gravatarEvent);
145 void CGravatar::GravatarThread()
147 bool *gravatarExit = m_gravatarExit;
148 CString gravatarBaseUrl = CRegString(_T("Software\\TortoiseGit\\GravatarUrl"), _T("http://www.gravatar.com/avatar/%HASH%?d=identicon"));
150 CString hostname;
151 CString baseUrlPath;
152 URL_COMPONENTS urlComponents = {0};
153 urlComponents.dwStructSize = sizeof(urlComponents);
154 urlComponents.lpszHostName = hostname.GetBuffer(INTERNET_MAX_HOST_NAME_LENGTH);
155 urlComponents.dwHostNameLength = INTERNET_MAX_HOST_NAME_LENGTH;
156 urlComponents.lpszUrlPath = baseUrlPath.GetBuffer(INTERNET_MAX_PATH_LENGTH);
157 urlComponents.dwUrlPathLength = INTERNET_MAX_PATH_LENGTH;
158 if (!InternetCrackUrl(gravatarBaseUrl, gravatarBaseUrl.GetLength(), 0, &urlComponents))
160 m_filename.Empty();
161 delete gravatarExit;
162 return;
164 hostname.ReleaseBuffer();
165 baseUrlPath.ReleaseBuffer();
167 HINTERNET hOpenHandle = InternetOpen(L"TortoiseGit", INTERNET_OPEN_TYPE_PRECONFIG, nullptr, nullptr, 0);
168 bool isHttps = urlComponents.nScheme == INTERNET_SCHEME_HTTPS;
169 HINTERNET hConnectHandle = InternetConnect(hOpenHandle, hostname, urlComponents.nPort, nullptr, nullptr, isHttps ? INTERNET_SCHEME_HTTP : urlComponents.nScheme, 0, 0);
170 if (!hConnectHandle)
172 InternetCloseHandle(hOpenHandle);
173 m_filename.Empty();
174 delete gravatarExit;
175 return;
178 while (!*gravatarExit)
180 ::WaitForSingleObject(m_gravatarEvent, INFINITE);
181 while (!*gravatarExit)
183 m_gravatarLock.Lock();
184 CString email = m_email;
185 m_gravatarLock.Unlock();
186 if (email.IsEmpty())
187 break;
189 Sleep(500);
190 if (*gravatarExit)
191 break;
192 m_gravatarLock.Lock();
193 bool diff = email != m_email;
194 m_gravatarLock.Unlock();
195 if (diff)
196 continue;
198 CString md5 = CalcMD5(email);
199 if (md5.IsEmpty())
200 continue;
202 CString gravatarUrl = baseUrlPath;
203 gravatarUrl.Replace(_T("%HASH%"), md5);
204 CString tempFile;
205 GetTempPath(tempFile);
206 tempFile += md5;
207 if (PathFileExists(tempFile))
209 m_gravatarLock.Lock();
210 if (m_email == email)
212 m_filename = tempFile;
213 m_email.Empty();
215 else
216 m_filename.Empty();
217 m_gravatarLock.Unlock();
219 else
221 BOOL ret = DownloadToFile(gravatarExit, hConnectHandle, isHttps, gravatarUrl, tempFile);
222 if (ret)
224 DeleteFile(tempFile);
225 if (*gravatarExit)
226 break;
227 m_gravatarLock.Lock();
228 m_filename.Empty();
229 m_gravatarLock.Unlock();
231 if (*gravatarExit)
232 break;
233 m_gravatarLock.Lock();
234 if (m_email == email && !ret)
236 HANDLE hFile = CreateFile(tempFile, FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
237 FILETIME creationTime = {};
238 GetFileTime(hFile, &creationTime, nullptr, nullptr);
239 uint64_t delta = 7 * 24 * 60 * 60 * 10000000LL;
240 DWORD low = creationTime.dwLowDateTime;
241 creationTime.dwLowDateTime += delta & 0xffffffff;
242 creationTime.dwHighDateTime += delta >> 32;
243 if (creationTime.dwLowDateTime < low)
244 creationTime.dwHighDateTime++;
245 SetFileTime(hFile, &creationTime, nullptr, nullptr);
246 CloseHandle(hFile);
247 m_filename = tempFile;
248 m_email.Empty();
250 else
251 m_filename.Empty();
252 m_gravatarLock.Unlock();
254 if (*gravatarExit)
255 break;
256 Invalidate();
259 InternetCloseHandle(hConnectHandle);
260 InternetCloseHandle(hOpenHandle);
261 delete gravatarExit;
264 BOOL CGravatar::DownloadToFile(bool *gravatarExit, const HINTERNET hConnectHandle, bool isHttps, const CString& urlpath, const CString& dest)
266 HINTERNET hResourceHandle = HttpOpenRequest(hConnectHandle, nullptr, urlpath, nullptr, nullptr, nullptr, INTERNET_FLAG_KEEP_CONNECTION | (isHttps ? INTERNET_FLAG_SECURE : 0), 0);
267 if (!hResourceHandle)
268 return -1;
270 resend:
271 if (*gravatarExit)
273 InternetCloseHandle(hResourceHandle);
274 return INET_E_DOWNLOAD_FAILURE;
277 BOOL httpsendrequest = HttpSendRequest(hResourceHandle, nullptr, 0, nullptr, 0);
279 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);
281 if (dwError == ERROR_INTERNET_FORCE_RETRY)
282 goto resend;
283 else if (!httpsendrequest || *gravatarExit)
285 InternetCloseHandle(hResourceHandle);
286 return INET_E_DOWNLOAD_FAILURE;
289 DWORD statusCode = 0;
290 DWORD length = sizeof(statusCode);
291 if (!HttpQueryInfo(hResourceHandle, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, (LPVOID)&statusCode, &length, NULL) || statusCode != 200)
293 InternetCloseHandle(hResourceHandle);
294 if (statusCode == 404)
295 return ERROR_FILE_NOT_FOUND;
296 else if (statusCode == 403)
297 return ERROR_ACCESS_DENIED;
298 return INET_E_DOWNLOAD_FAILURE;
301 CFile destinationFile;
302 if (!destinationFile.Open(dest, CFile::modeCreate | CFile::modeWrite))
304 InternetCloseHandle(hResourceHandle);
305 return ERROR_ACCESS_DENIED;
308 DWORD downloadedSum = 0; // sum of bytes downloaded so far
309 while (!*gravatarExit)
311 DWORD size; // size of the data available
312 if (!InternetQueryDataAvailable(hResourceHandle, &size, 0, 0))
314 InternetCloseHandle(hResourceHandle);
315 return INET_E_DOWNLOAD_FAILURE;
318 DWORD downloaded; // size of the downloaded data
319 LPTSTR lpszData = new TCHAR[size + 1];
320 if (!InternetReadFile(hResourceHandle, (LPVOID)lpszData, size, &downloaded))
322 delete[] lpszData;
323 InternetCloseHandle(hResourceHandle);
324 return INET_E_DOWNLOAD_FAILURE;
327 if (downloaded == 0)
329 delete[] lpszData;
330 break;
333 lpszData[downloaded] = '\0';
334 destinationFile.Write(lpszData, downloaded);
335 delete[] lpszData;
337 downloadedSum += downloaded;
339 destinationFile.Close();
340 InternetCloseHandle(hResourceHandle);
341 if (downloadedSum == 0)
342 return INET_E_DOWNLOAD_FAILURE;
344 return 0;
347 void CGravatar::SafeTerminateGravatarThread()
349 if (m_gravatarExit != nullptr)
350 *m_gravatarExit = true;
351 if (m_gravatarThread)
353 ::SetEvent(m_gravatarEvent);
354 ::WaitForSingleObject(m_gravatarThread, 1000);
355 m_gravatarThread = nullptr;
359 void CGravatar::OnPaint()
361 CPaintDC dc(this);
362 RECT rect;
363 GetClientRect(&rect);
364 dc.FillSolidRect(&rect, GetSysColor(COLOR_BTNFACE));
365 m_gravatarLock.Lock();
366 CString filename = m_filename;
367 m_gravatarLock.Unlock();
368 if (filename.IsEmpty()) return;
369 CPicture picture;
370 picture.Load(filename.GetString());
371 picture.Show(dc, rect);