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.
20 #include "GravatarPictureBox.h"
22 #include "MessageBox.h"
23 #include "UnicodeUtils.h"
25 #include "LoglistCommonResource.h"
28 static CString
CalcMD5(CString text
)
31 if (!CryptAcquireContext(&hProv
, nullptr, nullptr, PROV_RSA_FULL
, CRYPT_VERIFYCONTEXT
))
35 if (!CryptCreateHash(hProv
, CALG_MD5
, 0, 0, &hHash
))
37 CryptReleaseContext(hProv
, 0);
41 CStringA textA
= CUnicodeUtils::GetUTF8(text
);
42 if (!CryptHashData(hHash
, (LPBYTE
)textA
.GetBuffer(), textA
.GetLength(), 0))
44 CryptDestroyHash(hHash
);
45 CryptReleaseContext(hProv
, 0);
51 DWORD cbHash
= _countof(rgbHash
);
52 if (!CryptGetHashParam(hHash
, HP_HASHVAL
, rgbHash
, &cbHash
, 0))
54 CryptDestroyHash(hHash
);
55 CryptReleaseContext(hProv
, 0);
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);
71 BEGIN_MESSAGE_MAP(CGravatar
, CStatic
)
75 CGravatar::CGravatar()
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();
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;
114 void CGravatar::LoadGravatar(CString email
)
116 if (m_gravatarThread
== nullptr)
121 m_gravatarLock
.Lock();
123 if (!m_filename
.IsEmpty())
126 m_gravatarLock
.Unlock();
130 m_gravatarLock
.Unlock();
136 m_gravatarLock
.Lock();
137 bool diff
= m_email
!= email
;
139 m_gravatarLock
.Unlock();
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"));
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
))
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);
171 InternetCloseHandle(hOpenHandle
);
177 while (!*gravatarExit
)
179 ::WaitForSingleObject(m_gravatarEvent
, INFINITE
);
180 while (!*gravatarExit
)
182 m_gravatarLock
.Lock();
183 CString email
= m_email
;
184 m_gravatarLock
.Unlock();
191 m_gravatarLock
.Lock();
192 bool diff
= email
!= m_email
;
193 m_gravatarLock
.Unlock();
197 CString md5
= CalcMD5(email
);
201 CString gravatarUrl
= baseUrlPath
;
202 gravatarUrl
.Replace(_T("%HASH%"), md5
);
204 GetTempPath(tempFile
);
206 if (PathFileExists(tempFile
))
208 m_gravatarLock
.Lock();
209 if (m_email
== email
)
211 m_filename
= tempFile
;
216 m_gravatarLock
.Unlock();
220 BOOL ret
= DownloadToFile(gravatarExit
, hConnectHandle
, isHttps
, gravatarUrl
, tempFile
);
223 DeleteFile(tempFile
);
226 m_gravatarLock
.Lock();
228 m_gravatarLock
.Unlock();
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);
246 m_filename
= tempFile
;
251 m_gravatarLock
.Unlock();
258 InternetCloseHandle(hConnectHandle
);
259 InternetCloseHandle(hOpenHandle
);
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
)
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
)
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
))
322 InternetCloseHandle(hResourceHandle
);
323 return INET_E_DOWNLOAD_FAILURE
;
332 lpszData
[downloaded
] = '\0';
333 destinationFile
.Write(lpszData
, downloaded
);
336 downloadedSum
+= downloaded
;
338 destinationFile
.Close();
339 InternetCloseHandle(hResourceHandle
);
340 if (downloadedSum
== 0)
341 return INET_E_DOWNLOAD_FAILURE
;
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()
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;
369 picture
.Load(filename
.GetString());
370 picture
.Show(dc
, rect
);