comparison ui/downloader_win.cpp @ 15:95e1b6edf2fc

Implement more downloader functionality for Windows
author Andre Heinecke <aheinecke@intevation.de>
date Wed, 19 Feb 2014 10:45:06 +0000
parents 7e2f14c7aba2
children c12825a651ed
comparison
equal deleted inserted replaced
14:58d51a91d448 15:95e1b6edf2fc
17 17
18 #include <windows.h> 18 #include <windows.h>
19 #include <winhttp.h> 19 #include <winhttp.h>
20 20
21 #include <QDebug> 21 #include <QDebug>
22 #include <QDateTime>
23 #include <QSaveFile>
22 24
23 #define DEBUG if (1) qDebug() << __PRETTY_FUNCTION__ 25 #define DEBUG if (1) qDebug() << __PRETTY_FUNCTION__
24 26
27 #define MAX_SW_SIZE 10485760
28 #define MAX_LIST_SIZE 1048576
29
30 /** @brief Qt wrapper around FormatMessage
31 *
32 * @returns The error message of the error that occurred
33 */
25 const QString getLastErrorMsg() { 34 const QString getLastErrorMsg() {
26 LPWSTR bufPtr = NULL; 35 LPWSTR bufPtr = NULL;
27 DWORD err = GetLastError(); 36 DWORD err = GetLastError();
28 FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | 37 FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
29 FORMAT_MESSAGE_FROM_SYSTEM | 38 FORMAT_MESSAGE_FROM_SYSTEM |
30 FORMAT_MESSAGE_IGNORE_INSERTS, 39 FORMAT_MESSAGE_IGNORE_INSERTS,
31 NULL, err, 0, (LPWSTR)&bufPtr, 0, NULL); 40 NULL, err, 0, (LPWSTR)&bufPtr, 0, NULL);
41 if (!bufPtr) {
42 HMODULE hWinhttp = GetModuleHandleW(L"winhttp");
43 if (hWinhttp) {
44 FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
45 FORMAT_MESSAGE_FROM_HMODULE |
46 FORMAT_MESSAGE_IGNORE_INSERTS,
47 hWinhttp, HRESULT_CODE(err), 0,
48 (LPWSTR)&bufPtr, 0, NULL);
49 }
50 }
32 const QString result = 51 const QString result =
33 (bufPtr) ? QString::fromUtf16((const ushort*)bufPtr).trimmed() : 52 (bufPtr) ? QString::fromUtf16((const ushort*)bufPtr).trimmed() :
34 QString("Unknown Error %1").arg(err); 53 QString("Unknown Error %1").arg(err);
35 LocalFree(bufPtr); 54 LocalFree(bufPtr);
36 return result; 55 return result;
37 } 56 }
38 57
39 /** @brief open a session with appropiate proxy settings 58
59 /** @brief open a session with appropriate proxy settings
40 * 60 *
41 * @param[inout] *pHSession pointer to a HInternet structure 61 * @param[inout] *pHSession pointer to a HInternet structure
42 * 62 *
43 * On error call getLastError to get extended error information. 63 * On error call getLastError to get extended error information.
44 * 64 *
54 if (!pHSession) { 74 if (!pHSession) {
55 SetLastError(ERROR_INVALID_PARAMETER); 75 SetLastError(ERROR_INVALID_PARAMETER);
56 return false; 76 return false;
57 } 77 }
58 78
59 qDebug() << "2";
60 memset(&proxyConfig, 0, sizeof (WINHTTP_CURRENT_USER_IE_PROXY_CONFIG)); 79 memset(&proxyConfig, 0, sizeof (WINHTTP_CURRENT_USER_IE_PROXY_CONFIG));
61 80
62 if (WinHttpGetIEProxyConfigForCurrentUser(&proxyConfig)) { 81 if (WinHttpGetIEProxyConfigForCurrentUser(&proxyConfig)) {
63 if (proxyConfig.fAutoDetect) { 82 if (proxyConfig.fAutoDetect) {
64 // TODO Handle this 83 // TODO Handle this
142 */ 161 */
143 162
144 bool createRequest(HINTERNET hSession, HINTERNET hConnect, 163 bool createRequest(HINTERNET hSession, HINTERNET hConnect,
145 HINTERNET *pHRequest, LPCWSTR requestType, LPCWSTR resource) 164 HINTERNET *pHRequest, LPCWSTR requestType, LPCWSTR resource)
146 { 165 {
166 DWORD dwSSLFlag;
147 DEBUG; 167 DEBUG;
148 if (!hSession || !hConnect || !pHRequest) { 168 if (!hSession || !hConnect || !pHRequest) {
149 SetLastError(ERROR_INVALID_PARAMETER); 169 SetLastError(ERROR_INVALID_PARAMETER);
150 return false; 170 return false;
151 } 171 }
152 172
153 *pHRequest = WinHttpOpenRequest(hConnect, requestType, resource, 173 *pHRequest = WinHttpOpenRequest(hConnect, requestType, resource,
154 NULL, WINHTTP_NO_REFERER, 174 NULL, WINHTTP_NO_REFERER,
155 WINHTTP_DEFAULT_ACCEPT_TYPES, 175 WINHTTP_DEFAULT_ACCEPT_TYPES,
156 WINHTTP_FLAG_SECURE); 176 WINHTTP_FLAG_SECURE);
177
178 dwSSLFlag = SECURITY_FLAG_IGNORE_UNKNOWN_CA;
179 dwSSLFlag |= SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
180 dwSSLFlag |= SECURITY_FLAG_IGNORE_CERT_CN_INVALID;
181 dwSSLFlag |= SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE;
182
183 WinHttpSetOption(*pHRequest, WINHTTP_OPTION_SECURITY_FLAGS,
184 &dwSSLFlag, sizeof(dwSSLFlag));
185
157 return *pHRequest; 186 return *pHRequest;
158 } 187 }
159 188
189 bool Downloader::verifyCertificate(HINTERNET hRequest)
190 {
191 CERT_CONTEXT *certContext = NULL;
192 DWORD certContextLen = sizeof(CERT_CONTEXT);
193 bool retval = false;
194
195 if (!WinHttpQueryOption(hRequest,
196 WINHTTP_OPTION_SERVER_CERT_CONTEXT,
197 &certContext,
198 &certContextLen)) {
199 DEBUG << "Unable to get server certificate";
200 return false;
201 }
202
203 QByteArray serverCert ((const char *) certContext->pbCertEncoded,
204 certContext->cbCertEncoded);
205
206 retval = (serverCert == mCert);
207
208 if (!retval) {
209 DEBUG << "Certificate is not the same as the pinned one!"
210 << "Base64 cert: " << serverCert.toBase64();
211 }
212
213 CertFreeCertificateContext(certContext);
214 return retval;
215 }
216
217 QDateTime Downloader::getLastModifiedHeader(HINTERNET hSession,
218 HINTERNET hConnect, LPCWSTR resource)
219 {
220 HINTERNET hRequest = NULL;
221 SYSTEMTIME lMod;
222 DWORD sizeOfSystemtime = sizeof (SYSTEMTIME);
223 QDateTime retval;
224 DWORD err = 0;
225
226 memset(&lMod, 0, sizeof (SYSTEMTIME));
227
228 if (!hSession || !hConnect || !resource) {
229 SetLastError(ERROR_INVALID_PARAMETER);
230 return retval;
231 }
232
233 if (!createRequest(hSession, hConnect, &hRequest, L"HEAD",
234 resource)) {
235 err = GetLastError();
236 goto cleanup;
237 }
238
239 if (!WinHttpSendRequest(hRequest,
240 WINHTTP_NO_ADDITIONAL_HEADERS,
241 0, WINHTTP_NO_REQUEST_DATA, 0,
242 0, 0)) {
243 err = GetLastError();
244 goto cleanup;
245 }
246
247
248 if (!WinHttpReceiveResponse(hRequest, NULL)) {
249 err = GetLastError();
250 goto cleanup;
251 }
252
253 if (!verifyCertificate(hRequest)) {
254 DEBUG << "Certificate verification failed";
255 // TODO error out
256 }
257
258 if (!(WinHttpQueryHeaders(hRequest,
259 WINHTTP_QUERY_LAST_MODIFIED |
260 WINHTTP_QUERY_FLAG_SYSTEMTIME,
261 NULL,
262 &lMod,
263 &sizeOfSystemtime,
264 WINHTTP_NO_HEADER_INDEX))) {
265 err = GetLastError();
266 goto cleanup;
267 }
268
269 retval = QDateTime(QDate(lMod.wYear, lMod.wMonth, lMod.wDay),
270 QTime(lMod.wHour, lMod.wMinute, lMod.wSecond,
271 lMod.wMilliseconds),
272 Qt::UTC);
273 cleanup:
274 if (hRequest) {
275 WinHttpCloseHandle(hRequest);
276 }
277
278 // Close handle might overwrite the last error.
279 SetLastError(err);
280 return retval;
281 }
282
283 bool Downloader::downloadFile(HINTERNET hSession, HINTERNET hConnect,
284 LPCWSTR resource, const QString &filename, DWORD maxSize)
285 {
286 HINTERNET hRequest = NULL;
287 bool retval = false;
288 DWORD bytesAvailable = 0,
289 err = 0,
290 bytesRead = 0,
291 totalDownloaded = 0;
292
293 QSaveFile outputFile(filename);
294
295 if (!hSession || !hConnect || !resource) {
296 SetLastError(ERROR_INVALID_PARAMETER);
297 return retval;
298 }
299
300 if (!createRequest(hSession, hConnect, &hRequest, L"GET",
301 resource)) {
302 err = GetLastError();
303 goto cleanup;
304 }
305
306 if (!WinHttpSendRequest(hRequest,
307 WINHTTP_NO_ADDITIONAL_HEADERS,
308 0, WINHTTP_NO_REQUEST_DATA, 0,
309 0, 0)) {
310 err = GetLastError();
311 goto cleanup;
312 }
313
314
315 if (!WinHttpReceiveResponse(hRequest, NULL)) {
316 err = GetLastError();
317 goto cleanup;
318 }
319
320 if (!verifyCertificate(hRequest)) {
321 DEBUG << "Certificate verification failed";
322 // TODO error out
323 }
324
325 // Open / Create the file to write to.
326 if (!outputFile.open(QIODevice::WriteOnly)) {
327 DEBUG << "Failed to open file";
328 err = GetLastError();
329 goto cleanup;
330 }
331
332 do
333 {
334 char outBuf[8192]; // 8KB is the internal buffer size of winhttp
335 memset(outBuf, 0, sizeof(outBuf));
336 bytesRead = 0;
337
338 if (!WinHttpQueryDataAvailable(hRequest, &bytesAvailable)) {
339 DEBUG << "Querying for available data failed";
340 retval = false;
341 err = GetLastError();
342 break;
343 }
344
345 if (!bytesAvailable) {
346 // Might indicate that we are done.
347 break;
348 }
349
350 if (bytesAvailable > maxSize) {
351 DEBUG << "File to large";
352 retval = false;
353 break;
354 }
355
356 if (!WinHttpReadData(hRequest, (LPVOID)outBuf,
357 sizeof(outBuf), &bytesRead)) {
358 DEBUG << "Error reading data";
359 err = GetLastError();
360 break;
361 } else {
362 if (bytesRead) {
363 DEBUG << "Downloaded: " << bytesRead << "B";
364
365 // Write data to file.
366 if (outputFile.write(outBuf, bytesRead) !=
367 bytesRead) {
368 err = GetLastError();
369 DEBUG << "Error writing to file.";
370 retval = false;
371 }
372 // Completed a read / write cycle. If not error follows
373 // the download was successful.
374 retval = true;
375 } else {
376 // Should not happen as we queried for available
377 // bytes before and the function did not return an
378 // error.
379 DEBUG << "Unable to read available data";
380 retval = false;
381 break;
382 }
383 }
384 totalDownloaded += bytesRead;
385
386 if (totalDownloaded > maxSize) {
387 DEBUG << "Downloaded too much data. Breaking.";
388 retval = false;
389 break;
390 }
391 } while (bytesAvailable > 0);
392
393 cleanup:
394
395 if (retval) {
396 // Actually save the file to disk / move to homedir
397 retval = outputFile.commit();
398 }
399
400 if (hRequest) {
401 WinHttpCloseHandle(hRequest);
402 }
403
404 // Close handle might overwrite the last error.
405 SetLastError(err);
406 return retval;
407 }
408
160 void Downloader::run() { 409 void Downloader::run() {
161 BOOL bResults = FALSE; 410 bool results = false;
162 HINTERNET hSession = NULL, 411 HINTERNET hSession = NULL,
163 hConnect = NULL, 412 hConnect = NULL;
164 hRequest = NULL; 413 wchar_t wUrl[mUrl.size() + 1];
165 414 QDateTime lastModifiedSoftware;
166 SYSTEMTIME lastModified; 415 QDateTime lastModifiedList;
167 DWORD sizeOfSystemtime = sizeof (SYSTEMTIME); 416
168 417 int rc = 0;
169 memset(&lastModified, 0, sizeof (SYSTEMTIME)); 418
170 419 memset(wUrl, 0, sizeof (wchar_t) * (mUrl.size() + 1));
420
421 rc = mUrl.toWCharArray(wUrl);
422
423 if (rc != mUrl.size()) {
424 DEBUG << "Problem converting to wchar array";
425 return;
426 }
427
428 // Should not be necessary because we initialized the memory
429 wUrl[rc] = '\0';
430
431 // Initialize connection
171 if (!openSession(&hSession)) { 432 if (!openSession(&hSession)) {
172 DEBUG << "Failed to open session: " << getLastErrorMsg(); 433 DEBUG << "Failed to open session: " << getLastErrorMsg();
173 return; 434 return;
174 } 435 }
175 436 if (!initializeConnection(hSession, &hConnect, wUrl)) {
176 if (!initializeConnection(hSession, &hConnect, L"www.intevation.de")) {
177 DEBUG << "Failed to initialize connection: " << getLastErrorMsg(); 437 DEBUG << "Failed to initialize connection: " << getLastErrorMsg();
178 goto cleanup; 438 goto cleanup;
179 } 439 }
180 440
181 if (!createRequest(hSession, hConnect, &hRequest, L"GET", L"/")) { 441
182 DEBUG << "Failed to create the request: " << getLastErrorMsg(); 442 lastModifiedSoftware = getLastModifiedHeader(hSession, hConnect,
183 goto cleanup; 443 L"/incoming/aheinecke/test");
184 } 444
185 445 lastModifiedList = getLastModifiedHeader(hSession, hConnect,
186 if (hRequest) { 446 L"/incoming/aheinecke/test");
187 DEBUG << "Doing Request"; 447
188 bResults = WinHttpSendRequest(hRequest, 448 if (!lastModifiedList.isValid() || !lastModifiedSoftware.isValid()) {
189 WINHTTP_NO_ADDITIONAL_HEADERS, 449 DEBUG << "Could not read headers: " << getLastErrorMsg();
190 0, WINHTTP_NO_REQUEST_DATA, 0, 450 goto cleanup;
191 0, 0); 451 }
192 } else { 452
193 DEBUG << "Error: " << GetLastError(); 453 if (!mLastModSW.isValid() || lastModifiedSoftware > mLastModSW) {
194 } 454 QString dataDirectory = getDataDirectory();
195 455
196 456 if (dataDirectory.isEmpty()) {
197 if (bResults) { 457 DEBUG << "Failed to get data directory";
198 DEBUG << "Recieving Response"; 458 goto cleanup;
199 bResults = WinHttpReceiveResponse(hRequest, NULL); 459 }
200 } else { 460
201 DEBUG << "Error: " << GetLastError(); 461 QString fileName = dataDirectory.append("/SW-")
202 } 462 .append(lastModifiedSoftware.toString("yyyymmddHHmmss"))
203 463 .append(".exe");
204 if (bResults) { 464
205 DEBUG << "Querying Headers"; 465 DEBUG << "Filename: " << fileName;
206 bResults = WinHttpQueryHeaders(hRequest, 466
207 WINHTTP_QUERY_LAST_MODIFIED | 467 if (!downloadFile(hSession, hConnect, L"/incoming/aheinecke/test",
208 WINHTTP_QUERY_FLAG_SYSTEMTIME, 468 fileName, MAX_SW_SIZE)) {
209 NULL, 469 DEBUG << "Error downloading File: " << getLastErrorMsg();
210 &lastModified, 470 goto cleanup;
211 &sizeOfSystemtime, 471 }
212 WINHTTP_NO_HEADER_INDEX); 472
213 } else { 473 emit newSoftwareAvailable(fileName, lastModifiedSoftware);
214 DWORD errCode = GetLastError(); 474 } else if (!mLastModList.isValid() || lastModifiedList > mLastModList) {
215 switch (errCode) { 475 QString dataDirectory = getDataDirectory();
216 case ERROR_WINHTTP_HEADER_NOT_FOUND: 476
217 DEBUG << "Header not found"; 477 if (dataDirectory.isEmpty()) {
218 break; 478 DEBUG << "Failed to get data directory";
219 case ERROR_WINHTTP_INCORRECT_HANDLE_STATE: 479 goto cleanup;
220 DEBUG << "Incorrect handle state"; 480 }
221 break; 481
222 case ERROR_WINHTTP_INCORRECT_HANDLE_TYPE: 482 QString fileName = dataDirectory.append("/list-")
223 DEBUG << "Incorrect handle type"; 483 .append(lastModifiedSoftware.toString("yyyymmddHHmmss"))
224 break; 484 .append(".txt");
225 case ERROR_WINHTTP_INTERNAL_ERROR: 485
226 DEBUG << "Internal error"; 486 DEBUG << "Filename: " << fileName;
227 break; 487
228 case ERROR_NOT_ENOUGH_MEMORY: 488 if (!downloadFile(hSession, hConnect, L"/incoming/aheinecke/test",
229 DEBUG << "OOM"; 489 fileName, MAX_LIST_SIZE)) {
230 break; 490 DEBUG << "Error downloading File: " << getLastErrorMsg();
231 default: 491 goto cleanup;
232 DEBUG << "Error: " << getLastErrorMsg(); 492 }
233 } 493
234 } 494 emit newListAvailable(fileName, lastModifiedList);
235 495 }
236 DEBUG << "Last modified year: " << lastModified.wYear; 496
237 497 DEBUG << "SW date: " << lastModifiedSoftware;
238 498 DEBUG << "List date: " << lastModifiedList;
239 if (!bResults) { 499
500 /*if (!WinHttpQueryDataAvailable(hRequest, &dataAvaiable)) {
501 DEBUG << "Failed to query data Available: " << getLastErrorMsg();
502 goto cleanup;
503 }*/
504
505 if (!results) {
240 // Report any errors. 506 // Report any errors.
241 DEBUG << "Error" << GetLastError(); 507 DEBUG << "Error" << GetLastError();
242 emit error(tr("Unknown Problem when connecting"), Unknown); 508 emit error(tr("Unknown Problem when connecting"), ErrUnknown);
243 } 509 }
244 cleanup: 510 cleanup:
245 if (hRequest) {
246 WinHttpCloseHandle(hRequest);
247 }
248
249 if (hConnect) { 511 if (hConnect) {
250 WinHttpCloseHandle(hConnect); 512 WinHttpCloseHandle(hConnect);
251
252 } 513 }
253 514
254 if (hSession) { 515 if (hSession) {
255 WinHttpCloseHandle(hSession); 516 WinHttpCloseHandle(hSession);
256 } 517 }

http://wald.intevation.org/projects/trustbridge/