1 /* 2 * Queue Manager (BITS) File 3 * 4 * Copyright 2007, 2008 Google (Roy Shea, Dan Hipschman) 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Lesser General Public 8 * License as published by the Free Software Foundation; either 9 * version 2.1 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public 17 * License along with this library; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 19 */ 20 21 #include <stdarg.h> 22 23 #include "windef.h" 24 #include "winbase.h" 25 #include "winuser.h" 26 #include "winreg.h" 27 #include "winhttp.h" 28 #define COBJMACROS 29 #include "qmgr.h" 30 #include "wine/debug.h" 31 32 WINE_DEFAULT_DEBUG_CHANNEL(qmgr); 33 34 static inline BackgroundCopyFileImpl *impl_from_IBackgroundCopyFile2( 35 IBackgroundCopyFile2 *iface) 36 { 37 return CONTAINING_RECORD(iface, BackgroundCopyFileImpl, IBackgroundCopyFile2_iface); 38 } 39 40 static HRESULT WINAPI BackgroundCopyFile_QueryInterface( 41 IBackgroundCopyFile2 *iface, 42 REFIID riid, 43 void **obj) 44 { 45 BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface); 46 47 TRACE("(%p)->(%s %p)\n", file, debugstr_guid(riid), obj); 48 49 if (IsEqualGUID(riid, &IID_IUnknown) || 50 IsEqualGUID(riid, &IID_IBackgroundCopyFile) || 51 IsEqualGUID(riid, &IID_IBackgroundCopyFile2)) 52 { 53 *obj = iface; 54 } 55 else 56 { 57 *obj = NULL; 58 return E_NOINTERFACE; 59 } 60 61 IBackgroundCopyFile2_AddRef(iface); 62 return S_OK; 63 } 64 65 static ULONG WINAPI BackgroundCopyFile_AddRef( 66 IBackgroundCopyFile2 *iface) 67 { 68 BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface); 69 ULONG ref = InterlockedIncrement(&file->ref); 70 TRACE("(%p)->(%d)\n", file, ref); 71 return ref; 72 } 73 74 static ULONG WINAPI BackgroundCopyFile_Release( 75 IBackgroundCopyFile2 *iface) 76 { 77 BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface); 78 ULONG ref = InterlockedDecrement(&file->ref); 79 80 TRACE("(%p)->(%d)\n", file, ref); 81 82 if (ref == 0) 83 { 84 IBackgroundCopyJob3_Release(&file->owner->IBackgroundCopyJob3_iface); 85 HeapFree(GetProcessHeap(), 0, file->info.LocalName); 86 HeapFree(GetProcessHeap(), 0, file->info.RemoteName); 87 HeapFree(GetProcessHeap(), 0, file); 88 } 89 90 return ref; 91 } 92 93 /* Get the remote name of a background copy file */ 94 static HRESULT WINAPI BackgroundCopyFile_GetRemoteName( 95 IBackgroundCopyFile2 *iface, 96 LPWSTR *pVal) 97 { 98 BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface); 99 100 TRACE("(%p)->(%p)\n", file, pVal); 101 102 return return_strval(file->info.RemoteName, pVal); 103 } 104 105 static HRESULT WINAPI BackgroundCopyFile_GetLocalName( 106 IBackgroundCopyFile2 *iface, 107 LPWSTR *pVal) 108 { 109 BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface); 110 111 TRACE("(%p)->(%p)\n", file, pVal); 112 113 return return_strval(file->info.LocalName, pVal); 114 } 115 116 static HRESULT WINAPI BackgroundCopyFile_GetProgress( 117 IBackgroundCopyFile2 *iface, 118 BG_FILE_PROGRESS *pVal) 119 { 120 BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface); 121 122 TRACE("(%p)->(%p)\n", file, pVal); 123 124 EnterCriticalSection(&file->owner->cs); 125 *pVal = file->fileProgress; 126 LeaveCriticalSection(&file->owner->cs); 127 128 return S_OK; 129 } 130 131 static HRESULT WINAPI BackgroundCopyFile_GetFileRanges( 132 IBackgroundCopyFile2 *iface, 133 DWORD *RangeCount, 134 BG_FILE_RANGE **Ranges) 135 { 136 BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface); 137 FIXME("(%p)->(%p %p)\n", file, RangeCount, Ranges); 138 return E_NOTIMPL; 139 } 140 141 static HRESULT WINAPI BackgroundCopyFile_SetRemoteName( 142 IBackgroundCopyFile2 *iface, 143 LPCWSTR Val) 144 { 145 BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface); 146 FIXME("(%p)->(%s)\n", file, debugstr_w(Val)); 147 return E_NOTIMPL; 148 } 149 150 static const IBackgroundCopyFile2Vtbl BackgroundCopyFile2Vtbl = 151 { 152 BackgroundCopyFile_QueryInterface, 153 BackgroundCopyFile_AddRef, 154 BackgroundCopyFile_Release, 155 BackgroundCopyFile_GetRemoteName, 156 BackgroundCopyFile_GetLocalName, 157 BackgroundCopyFile_GetProgress, 158 BackgroundCopyFile_GetFileRanges, 159 BackgroundCopyFile_SetRemoteName 160 }; 161 162 HRESULT BackgroundCopyFileConstructor(BackgroundCopyJobImpl *owner, 163 LPCWSTR remoteName, LPCWSTR localName, 164 BackgroundCopyFileImpl **file) 165 { 166 BackgroundCopyFileImpl *This; 167 168 TRACE("(%s, %s, %p)\n", debugstr_w(remoteName), debugstr_w(localName), file); 169 170 This = HeapAlloc(GetProcessHeap(), 0, sizeof *This); 171 if (!This) 172 return E_OUTOFMEMORY; 173 174 This->info.RemoteName = strdupW(remoteName); 175 if (!This->info.RemoteName) 176 { 177 HeapFree(GetProcessHeap(), 0, This); 178 return E_OUTOFMEMORY; 179 } 180 181 This->info.LocalName = strdupW(localName); 182 if (!This->info.LocalName) 183 { 184 HeapFree(GetProcessHeap(), 0, This->info.RemoteName); 185 HeapFree(GetProcessHeap(), 0, This); 186 return E_OUTOFMEMORY; 187 } 188 189 This->IBackgroundCopyFile2_iface.lpVtbl = &BackgroundCopyFile2Vtbl; 190 This->ref = 1; 191 192 This->fileProgress.BytesTotal = BG_SIZE_UNKNOWN; 193 This->fileProgress.BytesTransferred = 0; 194 This->fileProgress.Completed = FALSE; 195 This->owner = owner; 196 This->read_size = 0; 197 This->tempFileName[0] = 0; 198 IBackgroundCopyJob3_AddRef(&owner->IBackgroundCopyJob3_iface); 199 200 *file = This; 201 return S_OK; 202 } 203 204 static HRESULT hresult_from_http_response(DWORD code) 205 { 206 switch (code) 207 { 208 case 200: return S_OK; 209 case 400: return BG_E_HTTP_ERROR_400; 210 case 401: return BG_E_HTTP_ERROR_401; 211 case 404: return BG_E_HTTP_ERROR_404; 212 case 407: return BG_E_HTTP_ERROR_407; 213 case 414: return BG_E_HTTP_ERROR_414; 214 case 501: return BG_E_HTTP_ERROR_501; 215 case 503: return BG_E_HTTP_ERROR_503; 216 case 504: return BG_E_HTTP_ERROR_504; 217 case 505: return BG_E_HTTP_ERROR_505; 218 default: 219 FIXME("unhandled response code %u\n", code); 220 return S_OK; 221 } 222 } 223 224 static void CALLBACK progress_callback_http(HINTERNET handle, DWORD_PTR context, DWORD status, 225 LPVOID buf, DWORD buflen) 226 { 227 BackgroundCopyFileImpl *file = (BackgroundCopyFileImpl *)context; 228 BackgroundCopyJobImpl *job = file->owner; 229 230 TRACE("%p, %p, %x, %p, %u\n", handle, file, status, buf, buflen); 231 232 switch (status) 233 { 234 case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: 235 { 236 DWORD code, len, size; 237 238 size = sizeof(code); 239 if (WinHttpQueryHeaders(handle, WINHTTP_QUERY_STATUS_CODE|WINHTTP_QUERY_FLAG_NUMBER, 240 NULL, &code, &size, NULL)) 241 { 242 if ((job->error.code = hresult_from_http_response(code))) 243 { 244 EnterCriticalSection(&job->cs); 245 246 job->error.context = BG_ERROR_CONTEXT_REMOTE_FILE; 247 if (job->error.file) IBackgroundCopyFile2_Release(job->error.file); 248 job->error.file = &file->IBackgroundCopyFile2_iface; 249 IBackgroundCopyFile2_AddRef(job->error.file); 250 251 LeaveCriticalSection(&job->cs); 252 transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR); 253 } 254 else 255 { 256 EnterCriticalSection(&job->cs); 257 258 job->error.context = 0; 259 if (job->error.file) 260 { 261 IBackgroundCopyFile2_Release(job->error.file); 262 job->error.file = NULL; 263 } 264 265 LeaveCriticalSection(&job->cs); 266 } 267 } 268 size = sizeof(len); 269 if (WinHttpQueryHeaders(handle, WINHTTP_QUERY_CONTENT_LENGTH|WINHTTP_QUERY_FLAG_NUMBER, 270 NULL, &len, &size, NULL)) 271 { 272 file->fileProgress.BytesTotal = len; 273 } 274 break; 275 } 276 case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: 277 { 278 file->read_size = buflen; 279 break; 280 } 281 case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: 282 { 283 WINHTTP_ASYNC_RESULT *result = (WINHTTP_ASYNC_RESULT *)buf; 284 job->error.code = HRESULT_FROM_WIN32(result->dwError); 285 transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR); 286 break; 287 } 288 default: break; 289 } 290 291 SetEvent(job->wait); 292 } 293 294 static DWORD wait_for_completion(BackgroundCopyJobImpl *job) 295 { 296 HANDLE handles[2] = {job->wait, job->cancel}; 297 DWORD error = ERROR_SUCCESS; 298 299 switch (WaitForMultipleObjects(2, handles, FALSE, INFINITE)) 300 { 301 case WAIT_OBJECT_0: 302 break; 303 304 case WAIT_OBJECT_0 + 1: 305 error = ERROR_CANCELLED; 306 transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_CANCELLED); 307 break; 308 309 default: 310 error = GetLastError(); 311 transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR); 312 break; 313 } 314 315 return error; 316 } 317 318 static UINT target_from_index(UINT index) 319 { 320 switch (index) 321 { 322 case 0: return WINHTTP_AUTH_TARGET_SERVER; 323 case 1: return WINHTTP_AUTH_TARGET_PROXY; 324 default: 325 ERR("unhandled index %u\n", index); 326 break; 327 } 328 return 0; 329 } 330 331 static UINT scheme_from_index(UINT index) 332 { 333 switch (index) 334 { 335 case 0: return WINHTTP_AUTH_SCHEME_BASIC; 336 case 1: return WINHTTP_AUTH_SCHEME_NTLM; 337 case 2: return WINHTTP_AUTH_SCHEME_PASSPORT; 338 case 3: return WINHTTP_AUTH_SCHEME_DIGEST; 339 case 4: return WINHTTP_AUTH_SCHEME_NEGOTIATE; 340 default: 341 ERR("unhandled index %u\n", index); 342 break; 343 } 344 return 0; 345 } 346 347 static BOOL set_request_credentials(HINTERNET req, BackgroundCopyJobImpl *job) 348 { 349 UINT i, j; 350 351 for (i = 0; i < BG_AUTH_TARGET_PROXY; i++) 352 { 353 UINT target = target_from_index(i); 354 for (j = 0; j < BG_AUTH_SCHEME_PASSPORT; j++) 355 { 356 UINT scheme = scheme_from_index(j); 357 const WCHAR *username = job->http_options.creds[i][j].Credentials.Basic.UserName; 358 const WCHAR *password = job->http_options.creds[i][j].Credentials.Basic.Password; 359 360 if (!username) continue; 361 if (!WinHttpSetCredentials(req, target, scheme, username, password, NULL)) return FALSE; 362 } 363 } 364 return TRUE; 365 } 366 367 static BOOL transfer_file_http(BackgroundCopyFileImpl *file, URL_COMPONENTSW *uc, 368 const WCHAR *tmpfile) 369 { 370 BackgroundCopyJobImpl *job = file->owner; 371 HANDLE handle; 372 HINTERNET ses, con = NULL, req = NULL; 373 DWORD flags = (uc->nScheme == INTERNET_SCHEME_HTTPS) ? WINHTTP_FLAG_SECURE : 0; 374 char buf[4096]; 375 BOOL ret = FALSE; 376 DWORD written; 377 378 transitionJobState(job, BG_JOB_STATE_QUEUED, BG_JOB_STATE_CONNECTING); 379 380 if (!(ses = WinHttpOpen(NULL, 0, NULL, NULL, WINHTTP_FLAG_ASYNC))) return FALSE; 381 WinHttpSetStatusCallback(ses, progress_callback_http, WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS, 0); 382 if (!WinHttpSetOption(ses, WINHTTP_OPTION_CONTEXT_VALUE, &file, sizeof(file))) goto done; 383 384 if (!(con = WinHttpConnect(ses, uc->lpszHostName, uc->nPort, 0))) goto done; 385 if (!(req = WinHttpOpenRequest(con, NULL, uc->lpszUrlPath, NULL, NULL, NULL, flags))) goto done; 386 if (!set_request_credentials(req, job)) goto done; 387 388 if (!(WinHttpSendRequest(req, job->http_options.headers, ~0u, NULL, 0, 0, (DWORD_PTR)file))) goto done; 389 if (wait_for_completion(job) || FAILED(job->error.code)) goto done; 390 391 if (!(WinHttpReceiveResponse(req, NULL))) goto done; 392 if (wait_for_completion(job) || FAILED(job->error.code)) goto done; 393 394 transitionJobState(job, BG_JOB_STATE_CONNECTING, BG_JOB_STATE_TRANSFERRING); 395 396 handle = CreateFileW(tmpfile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 397 if (handle == INVALID_HANDLE_VALUE) goto done; 398 399 for (;;) 400 { 401 file->read_size = 0; 402 if (!(ret = WinHttpReadData(req, buf, sizeof(buf), NULL))) break; 403 if (wait_for_completion(job) || FAILED(job->error.code)) 404 { 405 ret = FALSE; 406 break; 407 } 408 if (!file->read_size) break; 409 if (!(ret = WriteFile(handle, buf, file->read_size, &written, NULL))) break; 410 411 EnterCriticalSection(&job->cs); 412 file->fileProgress.BytesTransferred += file->read_size; 413 job->jobProgress.BytesTransferred += file->read_size; 414 LeaveCriticalSection(&job->cs); 415 } 416 417 CloseHandle(handle); 418 419 done: 420 WinHttpCloseHandle(req); 421 WinHttpCloseHandle(con); 422 WinHttpCloseHandle(ses); 423 if (!ret && !transitionJobState(job, BG_JOB_STATE_CONNECTING, BG_JOB_STATE_ERROR)) 424 transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR); 425 426 SetEvent(job->done); 427 return ret; 428 } 429 430 static DWORD CALLBACK progress_callback_local(LARGE_INTEGER totalSize, LARGE_INTEGER totalTransferred, 431 LARGE_INTEGER streamSize, LARGE_INTEGER streamTransferred, 432 DWORD streamNum, DWORD reason, HANDLE srcFile, 433 HANDLE dstFile, LPVOID obj) 434 { 435 BackgroundCopyFileImpl *file = obj; 436 BackgroundCopyJobImpl *job = file->owner; 437 ULONG64 diff; 438 439 EnterCriticalSection(&job->cs); 440 diff = (file->fileProgress.BytesTotal == BG_SIZE_UNKNOWN 441 ? totalTransferred.QuadPart 442 : totalTransferred.QuadPart - file->fileProgress.BytesTransferred); 443 file->fileProgress.BytesTotal = totalSize.QuadPart; 444 file->fileProgress.BytesTransferred = totalTransferred.QuadPart; 445 job->jobProgress.BytesTransferred += diff; 446 LeaveCriticalSection(&job->cs); 447 448 return (job->state == BG_JOB_STATE_TRANSFERRING 449 ? PROGRESS_CONTINUE 450 : PROGRESS_CANCEL); 451 } 452 453 static BOOL transfer_file_local(BackgroundCopyFileImpl *file, const WCHAR *tmpname) 454 { 455 static const WCHAR fileW[] = {'f','i','l','e',':','/','/',0}; 456 BackgroundCopyJobImpl *job = file->owner; 457 const WCHAR *ptr; 458 BOOL ret; 459 460 transitionJobState(job, BG_JOB_STATE_QUEUED, BG_JOB_STATE_TRANSFERRING); 461 462 if (strlenW(file->info.RemoteName) > 7 && !memicmpW(file->info.RemoteName, fileW, 7)) 463 ptr = file->info.RemoteName + 7; 464 else 465 ptr = file->info.RemoteName; 466 467 if (!(ret = CopyFileExW(ptr, tmpname, progress_callback_local, file, NULL, 0))) 468 { 469 WARN("Local file copy failed: error %u\n", GetLastError()); 470 transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR); 471 } 472 473 SetEvent(job->done); 474 return ret; 475 } 476 477 BOOL processFile(BackgroundCopyFileImpl *file, BackgroundCopyJobImpl *job) 478 { 479 static const WCHAR prefix[] = {'B','I','T', 0}; 480 WCHAR tmpDir[MAX_PATH], tmpName[MAX_PATH]; 481 WCHAR host[MAX_PATH]; 482 URL_COMPONENTSW uc; 483 BOOL ret; 484 485 if (!GetTempPathW(MAX_PATH, tmpDir)) 486 { 487 ERR("Couldn't create temp file name: %d\n", GetLastError()); 488 /* Guessing on what state this should give us */ 489 transitionJobState(job, BG_JOB_STATE_QUEUED, BG_JOB_STATE_TRANSIENT_ERROR); 490 return FALSE; 491 } 492 493 if (!GetTempFileNameW(tmpDir, prefix, 0, tmpName)) 494 { 495 ERR("Couldn't create temp file: %d\n", GetLastError()); 496 /* Guessing on what state this should give us */ 497 transitionJobState(job, BG_JOB_STATE_QUEUED, BG_JOB_STATE_TRANSIENT_ERROR); 498 return FALSE; 499 } 500 501 EnterCriticalSection(&job->cs); 502 file->fileProgress.BytesTotal = BG_SIZE_UNKNOWN; 503 file->fileProgress.BytesTransferred = 0; 504 file->fileProgress.Completed = FALSE; 505 LeaveCriticalSection(&job->cs); 506 507 TRACE("Transferring: %s -> %s -> %s\n", 508 debugstr_w(file->info.RemoteName), 509 debugstr_w(tmpName), 510 debugstr_w(file->info.LocalName)); 511 512 uc.dwStructSize = sizeof(uc); 513 uc.nScheme = 0; 514 uc.lpszScheme = NULL; 515 uc.dwSchemeLength = 0; 516 uc.lpszUserName = NULL; 517 uc.dwUserNameLength = 0; 518 uc.lpszPassword = NULL; 519 uc.dwPasswordLength = 0; 520 uc.lpszHostName = host; 521 uc.dwHostNameLength = sizeof(host)/sizeof(host[0]); 522 uc.nPort = 0; 523 uc.lpszUrlPath = NULL; 524 uc.dwUrlPathLength = ~0u; 525 uc.lpszExtraInfo = NULL; 526 uc.dwExtraInfoLength = 0; 527 ret = WinHttpCrackUrl(file->info.RemoteName, 0, 0, &uc); 528 if (!ret) 529 { 530 TRACE("WinHttpCrackUrl failed, trying local file copy\n"); 531 if (!transfer_file_local(file, tmpName)) WARN("local transfer failed\n"); 532 } 533 else if (!transfer_file_http(file, &uc, tmpName)) WARN("HTTP transfer failed\n"); 534 535 if (transitionJobState(job, BG_JOB_STATE_CONNECTING, BG_JOB_STATE_QUEUED) || 536 transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_QUEUED)) 537 { 538 lstrcpyW(file->tempFileName, tmpName); 539 540 EnterCriticalSection(&job->cs); 541 file->fileProgress.Completed = TRUE; 542 job->jobProgress.FilesTransferred++; 543 LeaveCriticalSection(&job->cs); 544 545 return TRUE; 546 } 547 else 548 { 549 DeleteFileW(tmpName); 550 return FALSE; 551 } 552 } 553