comparison ui/downloader_win.cpp @ 23:7d07d261b003

Wrap HINTERNET handles in a class to take advantage of scoping
author Andre Heinecke <aheinecke@intevation.de>
date Thu, 20 Feb 2014 15:44:40 +0000
parents fec1a610d43f
children 62cd56cea09b
comparison
equal deleted inserted replaced
22:bc302bbceaf5 23:7d07d261b003
30 30
31 31
32 #define LIST_RESOURCE "/incoming/aheinecke/test" 32 #define LIST_RESOURCE "/incoming/aheinecke/test"
33 #define SW_RESOURCE "/incoming/aheinecke/test" 33 #define SW_RESOURCE "/incoming/aheinecke/test"
34 34
35 /** @brief A wrapper around a HINTERNET structure that handles closing
36 *
37 * Holds a HINTERNET structure and closes it if necessary on destruction.
38 *
39 */
40 class SmartHINTERNET {
41 public:
42 SmartHINTERNET() : handle(NULL) {}
43
44 ~SmartHINTERNET() {
45 if (handle) {
46 WinHttpCloseHandle(handle);
47 }
48 }
49
50 HINTERNET handle;
51 };
35 52
36 /** @brief Qt wrapper around FormatMessage 53 /** @brief Qt wrapper around FormatMessage
37 * 54 *
38 * @returns The error message of the error that occurred 55 * @returns The error message of the error that occurred
39 */ 56 */
222 } 239 }
223 240
224 QDateTime Downloader::getLastModifiedHeader(HINTERNET hSession, 241 QDateTime Downloader::getLastModifiedHeader(HINTERNET hSession,
225 HINTERNET hConnect, LPCWSTR resource) 242 HINTERNET hConnect, LPCWSTR resource)
226 { 243 {
227 HINTERNET hRequest = NULL; 244 SmartHINTERNET sHRequest;
228 SYSTEMTIME lMod; 245 SYSTEMTIME lMod;
229 DWORD sizeOfSystemtime = sizeof (SYSTEMTIME); 246 DWORD sizeOfSystemtime = sizeof (SYSTEMTIME);
230 QDateTime retval; 247 QDateTime retval;
231 DWORD err = 0;
232 248
233 memset(&lMod, 0, sizeof (SYSTEMTIME)); 249 memset(&lMod, 0, sizeof (SYSTEMTIME));
234 250
235 if (!hSession || !hConnect || !resource) { 251 if (!hSession || !hConnect || !resource) {
236 SetLastError(ERROR_INVALID_PARAMETER); 252 SetLastError(ERROR_INVALID_PARAMETER);
237 return retval; 253 return retval;
238 } 254 }
239 255
240 if (!createRequest(hSession, hConnect, &hRequest, L"HEAD", 256 if (!createRequest(hSession, hConnect, &sHRequest.handle, L"HEAD",
241 resource)) { 257 resource)) {
242 err = GetLastError(); 258 return retval;
243 goto cleanup; 259 }
244 } 260
245 261 if (!WinHttpSendRequest(sHRequest.handle,
246 if (!WinHttpSendRequest(hRequest,
247 WINHTTP_NO_ADDITIONAL_HEADERS, 262 WINHTTP_NO_ADDITIONAL_HEADERS,
248 0, WINHTTP_NO_REQUEST_DATA, 0, 263 0, WINHTTP_NO_REQUEST_DATA, 0,
249 0, 0)) { 264 0, 0)) {
250 err = GetLastError(); 265 return retval;
251 goto cleanup; 266 }
252 } 267
253 268
254 269 if (!WinHttpReceiveResponse(sHRequest.handle, NULL)) {
255 if (!WinHttpReceiveResponse(hRequest, NULL)) { 270 return retval;
256 err = GetLastError(); 271 }
257 goto cleanup; 272
258 } 273 if (!verifyCertificate(sHRequest.handle)) {
259
260 if (!verifyCertificate(hRequest)) {
261 DEBUG << "Certificate verification failed"; 274 DEBUG << "Certificate verification failed";
262 goto cleanup; 275 return retval;
263 } 276 }
264 277
265 if (!(WinHttpQueryHeaders(hRequest, 278 if (!(WinHttpQueryHeaders(sHRequest.handle,
266 WINHTTP_QUERY_LAST_MODIFIED | 279 WINHTTP_QUERY_LAST_MODIFIED |
267 WINHTTP_QUERY_FLAG_SYSTEMTIME, 280 WINHTTP_QUERY_FLAG_SYSTEMTIME,
268 NULL, 281 NULL,
269 &lMod, 282 &lMod,
270 &sizeOfSystemtime, 283 &sizeOfSystemtime,
271 WINHTTP_NO_HEADER_INDEX))) { 284 WINHTTP_NO_HEADER_INDEX))) {
272 err = GetLastError(); 285 return retval;
273 goto cleanup;
274 } 286 }
275 287
276 retval = QDateTime(QDate(lMod.wYear, lMod.wMonth, lMod.wDay), 288 retval = QDateTime(QDate(lMod.wYear, lMod.wMonth, lMod.wDay),
277 QTime(lMod.wHour, lMod.wMinute, lMod.wSecond, 289 QTime(lMod.wHour, lMod.wMinute, lMod.wSecond,
278 lMod.wMilliseconds), 290 lMod.wMilliseconds),
279 Qt::UTC); 291 Qt::UTC);
280
281 // TODO remove and replace with a smart handle
282 cleanup:
283 if (hRequest) {
284 WinHttpCloseHandle(hRequest);
285 }
286
287 // Close handle might overwrite the last error.
288 SetLastError(err);
289 return retval; 292 return retval;
290 } 293 }
291 294
292 bool Downloader::downloadFile(HINTERNET hSession, HINTERNET hConnect, 295 bool Downloader::downloadFile(HINTERNET hSession, HINTERNET hConnect,
293 LPCWSTR resource, const QString &fileName, DWORD maxSize) 296 LPCWSTR resource, const QString &fileName, DWORD maxSize)
294 { 297 {
295 HINTERNET hRequest = NULL; 298 SmartHINTERNET sHRequest;
296 bool retval = false; 299 bool retval = false;
297 DWORD bytesAvailable = 0, 300 DWORD bytesAvailable = 0,
298 err = 0,
299 bytesRead = 0, 301 bytesRead = 0,
300 totalDownloaded = 0, 302 totalDownloaded = 0,
301 contentLength = 0, 303 contentLength = 0,
302 sizeOfDWORD = sizeof (DWORD); 304 sizeOfDWORD = sizeof (DWORD);
303 305
306 if (!hSession || !hConnect || !resource) { 308 if (!hSession || !hConnect || !resource) {
307 SetLastError(ERROR_INVALID_PARAMETER); 309 SetLastError(ERROR_INVALID_PARAMETER);
308 return retval; 310 return retval;
309 } 311 }
310 312
311 if (!createRequest(hSession, hConnect, &hRequest, L"GET", 313 if (!createRequest(hSession, hConnect, &sHRequest.handle, L"GET",
312 resource)) { 314 resource)) {
313 err = GetLastError(); 315 return retval;
314 goto cleanup; 316 }
315 } 317
316 318 if (!WinHttpSendRequest(sHRequest.handle,
317 if (!WinHttpSendRequest(hRequest,
318 WINHTTP_NO_ADDITIONAL_HEADERS, 319 WINHTTP_NO_ADDITIONAL_HEADERS,
319 0, WINHTTP_NO_REQUEST_DATA, 0, 320 0, WINHTTP_NO_REQUEST_DATA, 0,
320 0, 0)) { 321 0, 0)) {
321 err = GetLastError(); 322 return retval;
322 goto cleanup; 323 }
323 } 324
324 325
325 326 if (!WinHttpReceiveResponse(sHRequest.handle, NULL)) {
326 if (!WinHttpReceiveResponse(hRequest, NULL)) { 327 return retval;
327 err = GetLastError(); 328 }
328 goto cleanup; 329
329 } 330 if (!verifyCertificate(sHRequest.handle)) {
330
331 if (!verifyCertificate(hRequest)) {
332 DEBUG << "Certificate verification failed"; 331 DEBUG << "Certificate verification failed";
333 goto cleanup; 332 return retval;
334 } 333 }
335 334
336 335
337 if (!(WinHttpQueryHeaders(hRequest, 336 if (!(WinHttpQueryHeaders(sHRequest.handle,
338 WINHTTP_QUERY_CONTENT_LENGTH | 337 WINHTTP_QUERY_CONTENT_LENGTH |
339 WINHTTP_QUERY_FLAG_NUMBER, 338 WINHTTP_QUERY_FLAG_NUMBER,
340 NULL, 339 NULL,
341 &contentLength, 340 &contentLength,
342 &sizeOfDWORD, 341 &sizeOfDWORD,
345 // much we download. 344 // much we download.
346 DEBUG << "No content-length"; 345 DEBUG << "No content-length";
347 } 346 }
348 347
349 if (contentLength > maxSize) { 348 if (contentLength > maxSize) {
350 err = ERROR_INVALID_DATA; 349 return retval;
351 goto cleanup;
352 } 350 }
353 351
354 if (contentLength) { 352 if (contentLength) {
355 QFileInfo finf(fileName); 353 QFileInfo finf(fileName);
356 if (finf.exists() && finf.isReadable() && 354 if (finf.exists() && finf.isReadable() &&
357 finf.size() == contentLength) { 355 finf.size() == contentLength) {
358 // We already have data of the same size 356 // We already have data of the same size
359 // No need to waste bandwidth. 357 // No need to waste bandwidth.
360 DEBUG << "Skipping download because file exists"; 358 DEBUG << "Skipping download because file exists";
361 retval = true; 359 retval = true;
362 goto cleanup; 360 return retval;
363 } 361 }
364 } 362 }
365 363
366 // Open / Create the file to write to. 364 // Open / Create the file to write to.
367 if (!outputFile.open(QIODevice::WriteOnly)) { 365 if (!outputFile.open(QIODevice::WriteOnly)) {
368 DEBUG << "Failed to open file"; 366 DEBUG << "Failed to open file";
369 err = GetLastError(); 367 return retval;
370 goto cleanup;
371 } 368 }
372 369
373 DEBUG << "output file size: " << outputFile.size(); 370 DEBUG << "output file size: " << outputFile.size();
374 do 371 do
375 { 372 {
376 char outBuf[8192]; // 8KB is the internal buffer size of winhttp 373 char outBuf[8192]; // 8KB is the internal buffer size of winhttp
377 memset(outBuf, 0, sizeof(outBuf)); 374 memset(outBuf, 0, sizeof(outBuf));
378 bytesRead = 0; 375 bytesRead = 0;
379 376
380 if (!WinHttpQueryDataAvailable(hRequest, &bytesAvailable)) { 377 if (!WinHttpQueryDataAvailable(sHRequest.handle, &bytesAvailable)) {
381 DEBUG << "Querying for available data failed"; 378 DEBUG << "Querying for available data failed";
382 retval = false; 379 retval = false;
383 err = GetLastError();
384 break; 380 break;
385 } 381 }
386 382
387 if (!bytesAvailable) { 383 if (!bytesAvailable) {
388 // Might indicate that we are done. 384 // Might indicate that we are done.
390 } 386 }
391 387
392 if (bytesAvailable > maxSize) { 388 if (bytesAvailable > maxSize) {
393 DEBUG << "File to large"; 389 DEBUG << "File to large";
394 retval = false; 390 retval = false;
395 err = ERROR_INVALID_DATA; 391 SetLastError(ERROR_INVALID_DATA);
396 break; 392 break;
397 } 393 }
398 394
399 if (!WinHttpReadData(hRequest, (LPVOID)outBuf, 395 if (!WinHttpReadData(sHRequest.handle, (LPVOID)outBuf,
400 sizeof(outBuf), &bytesRead)) { 396 sizeof(outBuf), &bytesRead)) {
401 DEBUG << "Error reading data"; 397 DEBUG << "Error reading data";
402 err = GetLastError();
403 break; 398 break;
404 } else { 399 } else {
405 if (bytesRead) { 400 if (bytesRead) {
406 // Write data to file. 401 // Write data to file.
407 if (outputFile.write(outBuf, bytesRead) != 402 if (outputFile.write(outBuf, bytesRead) !=
408 bytesRead) { 403 bytesRead) {
409 err = GetLastError();
410 DEBUG << "Error writing to file."; 404 DEBUG << "Error writing to file.";
411 retval = false; 405 retval = false;
412 } 406 }
413 // Completed a read / write cycle. If not error follows 407 // Completed a read / write cycle. If not error follows
414 // the download was successful. 408 // the download was successful.
429 retval = false; 423 retval = false;
430 break; 424 break;
431 } 425 }
432 } while (bytesAvailable > 0); 426 } while (bytesAvailable > 0);
433 427
434 // TODO remove and replace with a smart handle
435 cleanup:
436
437 if (retval && outputFile.isOpen()) { 428 if (retval && outputFile.isOpen()) {
438 // Actually save the file to disk / move to homedir 429 // Actually save the file to disk / move to homedir
439 retval = outputFile.commit(); 430 retval = outputFile.commit();
440 } 431 }
441 432
442 if (hRequest) {
443 WinHttpCloseHandle(hRequest);
444 }
445
446 // Close handle might overwrite the last error.
447 SetLastError(err);
448 return retval; 433 return retval;
449 } 434 }
450 435
451 void Downloader::run() { 436 void Downloader::run() {
452 bool results = false; 437 bool results = false;
453 HINTERNET hSession = NULL, 438 SmartHINTERNET sHSession;
454 hConnect = NULL; 439 SmartHINTERNET sHConnect;
455 wchar_t wUrl[mUrl.size() + 1]; 440 wchar_t wUrl[mUrl.size() + 1];
456 QDateTime lastModifiedSoftware; 441 QDateTime lastModifiedSoftware;
457 QDateTime lastModifiedList; 442 QDateTime lastModifiedList;
458 443
459 int rc = 0; 444 int rc = 0;
469 454
470 // Should not be necessary because we initialized the memory 455 // Should not be necessary because we initialized the memory
471 wUrl[rc] = '\0'; 456 wUrl[rc] = '\0';
472 457
473 // Initialize connection 458 // Initialize connection
474 if (!openSession(&hSession)) { 459 if (!openSession(&sHConnect.handle)) {
475 DEBUG << "Failed to open session: " << getLastErrorMsg(); 460 DEBUG << "Failed to open session: " << getLastErrorMsg();
476 return; 461 return;
477 } 462 }
478 if (!initializeConnection(hSession, &hConnect, wUrl)) { 463 if (!initializeConnection(sHConnect.handle, &sHConnect.handle, wUrl)) {
479 DEBUG << "Failed to initialize connection: " << getLastErrorMsg(); 464 DEBUG << "Failed to initialize connection: " << getLastErrorMsg();
480 goto cleanup; 465 return;
481 } 466 }
482 467
483 468
484 lastModifiedSoftware = getLastModifiedHeader(hSession, hConnect, 469 lastModifiedSoftware = getLastModifiedHeader(sHConnect.handle, sHConnect.handle,
485 L""SW_RESOURCE); 470 L""SW_RESOURCE);
486 471
487 lastModifiedList = getLastModifiedHeader(hSession, hConnect, 472 lastModifiedList = getLastModifiedHeader(sHConnect.handle, sHConnect.handle,
488 L""LIST_RESOURCE); 473 L""LIST_RESOURCE);
489 474
490 if (!lastModifiedList.isValid() || !lastModifiedSoftware.isValid()) { 475 if (!lastModifiedList.isValid() || !lastModifiedSoftware.isValid()) {
491 DEBUG << "Could not read headers: " << getLastErrorMsg(); 476 DEBUG << "Could not read headers: " << getLastErrorMsg();
492 goto cleanup; 477 return;
493 } 478 }
494 479
495 if (!mLastModSW.isValid() || lastModifiedSoftware > mLastModSW) { 480 if (!mLastModSW.isValid() || lastModifiedSoftware > mLastModSW) {
496 QString dataDirectory = getDataDirectory(); 481 QString dataDirectory = getDataDirectory();
497 482
498 if (dataDirectory.isEmpty()) { 483 if (dataDirectory.isEmpty()) {
499 DEBUG << "Failed to get data directory"; 484 DEBUG << "Failed to get data directory";
500 goto cleanup; 485 return;
501 } 486 }
502 487
503 QString fileName = dataDirectory.append("/SW-") 488 QString fileName = dataDirectory.append("/SW-")
504 .append(lastModifiedSoftware.toString("yyyymmddHHmmss")) 489 .append(lastModifiedSoftware.toString("yyyymmddHHmmss"))
505 .append(".exe"); 490 .append(".exe");
506 491
507 DEBUG << "fileName: " << fileName; 492 DEBUG << "fileName: " << fileName;
508 493
509 if (!downloadFile(hSession, hConnect, L""SW_RESOURCE, 494 if (!downloadFile(sHConnect.handle, sHConnect.handle, L""SW_RESOURCE,
510 fileName, MAX_SW_SIZE)) { 495 fileName, MAX_SW_SIZE)) {
511 DEBUG << "Error downloading File: " << getLastErrorMsg(); 496 DEBUG << "Error downloading File: " << getLastErrorMsg();
512 goto cleanup; 497 return;
513 } 498 }
514 499
515 emit newSoftwareAvailable(fileName, lastModifiedSoftware); 500 emit newSoftwareAvailable(fileName, lastModifiedSoftware);
516 } else if (!mLastModList.isValid() || lastModifiedList > mLastModList) { 501 } else if (!mLastModList.isValid() || lastModifiedList > mLastModList) {
517 QString dataDirectory = getDataDirectory(); 502 QString dataDirectory = getDataDirectory();
518 503
519 if (dataDirectory.isEmpty()) { 504 if (dataDirectory.isEmpty()) {
520 DEBUG << "Failed to get data directory"; 505 DEBUG << "Failed to get data directory";
521 goto cleanup; 506 return;
522 } 507 }
523 508
524 QString fileName = dataDirectory.append("/list-") 509 QString fileName = dataDirectory.append("/list-")
525 .append(lastModifiedSoftware.toString("yyyymmddHHmmss")) 510 .append(lastModifiedSoftware.toString("yyyymmddHHmmss"))
526 .append(".txt"); 511 .append(".txt");
527 512
528 DEBUG << "fileName: " << fileName; 513 DEBUG << "fileName: " << fileName;
529 514
530 if (!downloadFile(hSession, hConnect, L""LIST_RESOURCE, 515 if (!downloadFile(sHConnect.handle, sHConnect.handle, L""LIST_RESOURCE,
531 fileName, MAX_LIST_SIZE)) { 516 fileName, MAX_LIST_SIZE)) {
532 DEBUG << "Error downloading File: " << getLastErrorMsg(); 517 DEBUG << "Error downloading File: " << getLastErrorMsg();
533 goto cleanup; 518 return;
534 } 519 }
535 520
536 emit newListAvailable(fileName, lastModifiedList); 521 emit newListAvailable(fileName, lastModifiedList);
537 } 522 }
538 523
539 DEBUG << "SW date: " << lastModifiedSoftware; 524 DEBUG << "SW date: " << lastModifiedSoftware;
540 DEBUG << "List date: " << lastModifiedList; 525 DEBUG << "List date: " << lastModifiedList;
541
542 /*if (!WinHttpQueryDataAvailable(hRequest, &dataAvaiable)) {
543 DEBUG << "Failed to query data Available: " << getLastErrorMsg();
544 goto cleanup;
545 }*/
546 526
547 if (!results) { 527 if (!results) {
548 // Report any errors. 528 // Report any errors.
549 DEBUG << "Error" << GetLastError(); 529 DEBUG << "Error" << GetLastError();
550 emit error(tr("Unknown Problem when connecting"), ErrUnknown); 530 emit error(tr("Unknown Problem when connecting"), ErrUnknown);
551 } 531 }
552 532
553 // TODO remove and replace with a smart handle
554 cleanup:
555 if (hConnect) {
556 WinHttpCloseHandle(hConnect);
557 }
558
559 if (hSession) {
560 WinHttpCloseHandle(hSession);
561 }
562 return; 533 return;
563 } 534 }
564 #endif 535 #endif

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