Mercurial > trustbridge
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 } |