/* * Queue Manager (BITS) File * * Copyright 2007, 2008 Google (Roy Shea, Dan Hipschman) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include "windef.h" #include "winbase.h" #include "winuser.h" #include "winreg.h" #include "winhttp.h" #define COBJMACROS #include "qmgr.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(qmgr); static inline BackgroundCopyFileImpl *impl_from_IBackgroundCopyFile2( IBackgroundCopyFile2 *iface) { return CONTAINING_RECORD(iface, BackgroundCopyFileImpl, IBackgroundCopyFile2_iface); } static HRESULT WINAPI BackgroundCopyFile_QueryInterface( IBackgroundCopyFile2 *iface, REFIID riid, void **obj) { BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface); TRACE("(%p)->(%s %p)\n", file, debugstr_guid(riid), obj); if (IsEqualGUID(riid, &IID_IUnknown) || IsEqualGUID(riid, &IID_IBackgroundCopyFile) || IsEqualGUID(riid, &IID_IBackgroundCopyFile2)) { *obj = iface; } else { *obj = NULL; return E_NOINTERFACE; } IBackgroundCopyFile2_AddRef(iface); return S_OK; } static ULONG WINAPI BackgroundCopyFile_AddRef( IBackgroundCopyFile2 *iface) { BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface); ULONG ref = InterlockedIncrement(&file->ref); TRACE("(%p)->(%d)\n", file, ref); return ref; } static ULONG WINAPI BackgroundCopyFile_Release( IBackgroundCopyFile2 *iface) { BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface); ULONG ref = InterlockedDecrement(&file->ref); TRACE("(%p)->(%d)\n", file, ref); if (ref == 0) { IBackgroundCopyJob3_Release(&file->owner->IBackgroundCopyJob3_iface); HeapFree(GetProcessHeap(), 0, file->info.LocalName); HeapFree(GetProcessHeap(), 0, file->info.RemoteName); HeapFree(GetProcessHeap(), 0, file); } return ref; } /* Get the remote name of a background copy file */ static HRESULT WINAPI BackgroundCopyFile_GetRemoteName( IBackgroundCopyFile2 *iface, LPWSTR *pVal) { BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface); TRACE("(%p)->(%p)\n", file, pVal); return return_strval(file->info.RemoteName, pVal); } static HRESULT WINAPI BackgroundCopyFile_GetLocalName( IBackgroundCopyFile2 *iface, LPWSTR *pVal) { BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface); TRACE("(%p)->(%p)\n", file, pVal); return return_strval(file->info.LocalName, pVal); } static HRESULT WINAPI BackgroundCopyFile_GetProgress( IBackgroundCopyFile2 *iface, BG_FILE_PROGRESS *pVal) { BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface); TRACE("(%p)->(%p)\n", file, pVal); EnterCriticalSection(&file->owner->cs); *pVal = file->fileProgress; LeaveCriticalSection(&file->owner->cs); return S_OK; } static HRESULT WINAPI BackgroundCopyFile_GetFileRanges( IBackgroundCopyFile2 *iface, DWORD *RangeCount, BG_FILE_RANGE **Ranges) { BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface); FIXME("(%p)->(%p %p)\n", file, RangeCount, Ranges); return E_NOTIMPL; } static HRESULT WINAPI BackgroundCopyFile_SetRemoteName( IBackgroundCopyFile2 *iface, LPCWSTR Val) { BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface); FIXME("(%p)->(%s)\n", file, debugstr_w(Val)); return E_NOTIMPL; } static const IBackgroundCopyFile2Vtbl BackgroundCopyFile2Vtbl = { BackgroundCopyFile_QueryInterface, BackgroundCopyFile_AddRef, BackgroundCopyFile_Release, BackgroundCopyFile_GetRemoteName, BackgroundCopyFile_GetLocalName, BackgroundCopyFile_GetProgress, BackgroundCopyFile_GetFileRanges, BackgroundCopyFile_SetRemoteName }; HRESULT BackgroundCopyFileConstructor(BackgroundCopyJobImpl *owner, LPCWSTR remoteName, LPCWSTR localName, BackgroundCopyFileImpl **file) { BackgroundCopyFileImpl *This; TRACE("(%s, %s, %p)\n", debugstr_w(remoteName), debugstr_w(localName), file); This = HeapAlloc(GetProcessHeap(), 0, sizeof *This); if (!This) return E_OUTOFMEMORY; This->info.RemoteName = strdupW(remoteName); if (!This->info.RemoteName) { HeapFree(GetProcessHeap(), 0, This); return E_OUTOFMEMORY; } This->info.LocalName = strdupW(localName); if (!This->info.LocalName) { HeapFree(GetProcessHeap(), 0, This->info.RemoteName); HeapFree(GetProcessHeap(), 0, This); return E_OUTOFMEMORY; } This->IBackgroundCopyFile2_iface.lpVtbl = &BackgroundCopyFile2Vtbl; This->ref = 1; This->fileProgress.BytesTotal = BG_SIZE_UNKNOWN; This->fileProgress.BytesTransferred = 0; This->fileProgress.Completed = FALSE; This->owner = owner; This->read_size = 0; This->tempFileName[0] = 0; IBackgroundCopyJob3_AddRef(&owner->IBackgroundCopyJob3_iface); *file = This; return S_OK; } static HRESULT hresult_from_http_response(DWORD code) { switch (code) { case 200: return S_OK; case 400: return BG_E_HTTP_ERROR_400; case 401: return BG_E_HTTP_ERROR_401; case 404: return BG_E_HTTP_ERROR_404; case 407: return BG_E_HTTP_ERROR_407; case 414: return BG_E_HTTP_ERROR_414; case 501: return BG_E_HTTP_ERROR_501; case 503: return BG_E_HTTP_ERROR_503; case 504: return BG_E_HTTP_ERROR_504; case 505: return BG_E_HTTP_ERROR_505; default: FIXME("unhandled response code %u\n", code); return S_OK; } } static void CALLBACK progress_callback_http(HINTERNET handle, DWORD_PTR context, DWORD status, LPVOID buf, DWORD buflen) { BackgroundCopyFileImpl *file = (BackgroundCopyFileImpl *)context; BackgroundCopyJobImpl *job = file->owner; TRACE("%p, %p, %x, %p, %u\n", handle, file, status, buf, buflen); switch (status) { case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: { DWORD code, len, size; size = sizeof(code); if (WinHttpQueryHeaders(handle, WINHTTP_QUERY_STATUS_CODE|WINHTTP_QUERY_FLAG_NUMBER, NULL, &code, &size, NULL)) { if ((job->error.code = hresult_from_http_response(code))) { EnterCriticalSection(&job->cs); job->error.context = BG_ERROR_CONTEXT_REMOTE_FILE; if (job->error.file) IBackgroundCopyFile2_Release(job->error.file); job->error.file = &file->IBackgroundCopyFile2_iface; IBackgroundCopyFile2_AddRef(job->error.file); LeaveCriticalSection(&job->cs); transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR); } else { EnterCriticalSection(&job->cs); job->error.context = 0; if (job->error.file) { IBackgroundCopyFile2_Release(job->error.file); job->error.file = NULL; } LeaveCriticalSection(&job->cs); } } size = sizeof(len); if (WinHttpQueryHeaders(handle, WINHTTP_QUERY_CONTENT_LENGTH|WINHTTP_QUERY_FLAG_NUMBER, NULL, &len, &size, NULL)) { file->fileProgress.BytesTotal = len; } break; } case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: { file->read_size = buflen; break; } case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: { WINHTTP_ASYNC_RESULT *result = (WINHTTP_ASYNC_RESULT *)buf; job->error.code = HRESULT_FROM_WIN32(result->dwError); transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR); break; } default: break; } SetEvent(job->wait); } static DWORD wait_for_completion(BackgroundCopyJobImpl *job) { HANDLE handles[2] = {job->wait, job->cancel}; DWORD error = ERROR_SUCCESS; switch (WaitForMultipleObjects(2, handles, FALSE, INFINITE)) { case WAIT_OBJECT_0: break; case WAIT_OBJECT_0 + 1: error = ERROR_CANCELLED; transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_CANCELLED); break; default: error = GetLastError(); transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR); break; } return error; } static UINT target_from_index(UINT index) { switch (index) { case 0: return WINHTTP_AUTH_TARGET_SERVER; case 1: return WINHTTP_AUTH_TARGET_PROXY; default: ERR("unhandled index %u\n", index); break; } return 0; } static UINT scheme_from_index(UINT index) { switch (index) { case 0: return WINHTTP_AUTH_SCHEME_BASIC; case 1: return WINHTTP_AUTH_SCHEME_NTLM; case 2: return WINHTTP_AUTH_SCHEME_PASSPORT; case 3: return WINHTTP_AUTH_SCHEME_DIGEST; case 4: return WINHTTP_AUTH_SCHEME_NEGOTIATE; default: ERR("unhandled index %u\n", index); break; } return 0; } static BOOL set_request_credentials(HINTERNET req, BackgroundCopyJobImpl *job) { UINT i, j; for (i = 0; i < BG_AUTH_TARGET_PROXY; i++) { UINT target = target_from_index(i); for (j = 0; j < BG_AUTH_SCHEME_PASSPORT; j++) { UINT scheme = scheme_from_index(j); const WCHAR *username = job->http_options.creds[i][j].Credentials.Basic.UserName; const WCHAR *password = job->http_options.creds[i][j].Credentials.Basic.Password; if (!username) continue; if (!WinHttpSetCredentials(req, target, scheme, username, password, NULL)) return FALSE; } } return TRUE; } static BOOL transfer_file_http(BackgroundCopyFileImpl *file, URL_COMPONENTSW *uc, const WCHAR *tmpfile) { BackgroundCopyJobImpl *job = file->owner; HANDLE handle; HINTERNET ses, con = NULL, req = NULL; DWORD flags = (uc->nScheme == INTERNET_SCHEME_HTTPS) ? WINHTTP_FLAG_SECURE : 0; char buf[4096]; BOOL ret = FALSE; DWORD written; transitionJobState(job, BG_JOB_STATE_QUEUED, BG_JOB_STATE_CONNECTING); if (!(ses = WinHttpOpen(NULL, 0, NULL, NULL, WINHTTP_FLAG_ASYNC))) return FALSE; WinHttpSetStatusCallback(ses, progress_callback_http, WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS, 0); if (!WinHttpSetOption(ses, WINHTTP_OPTION_CONTEXT_VALUE, &file, sizeof(file))) goto done; if (!(con = WinHttpConnect(ses, uc->lpszHostName, uc->nPort, 0))) goto done; if (!(req = WinHttpOpenRequest(con, NULL, uc->lpszUrlPath, NULL, NULL, NULL, flags))) goto done; if (!set_request_credentials(req, job)) goto done; if (!(WinHttpSendRequest(req, job->http_options.headers, ~0u, NULL, 0, 0, (DWORD_PTR)file))) goto done; if (wait_for_completion(job) || FAILED(job->error.code)) goto done; if (!(WinHttpReceiveResponse(req, NULL))) goto done; if (wait_for_completion(job) || FAILED(job->error.code)) goto done; transitionJobState(job, BG_JOB_STATE_CONNECTING, BG_JOB_STATE_TRANSFERRING); handle = CreateFileW(tmpfile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (handle == INVALID_HANDLE_VALUE) goto done; for (;;) { file->read_size = 0; if (!(ret = WinHttpReadData(req, buf, sizeof(buf), NULL))) break; if (wait_for_completion(job) || FAILED(job->error.code)) { ret = FALSE; break; } if (!file->read_size) break; if (!(ret = WriteFile(handle, buf, file->read_size, &written, NULL))) break; EnterCriticalSection(&job->cs); file->fileProgress.BytesTransferred += file->read_size; job->jobProgress.BytesTransferred += file->read_size; LeaveCriticalSection(&job->cs); } CloseHandle(handle); done: WinHttpCloseHandle(req); WinHttpCloseHandle(con); WinHttpCloseHandle(ses); if (!ret && !transitionJobState(job, BG_JOB_STATE_CONNECTING, BG_JOB_STATE_ERROR)) transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR); SetEvent(job->done); return ret; } static DWORD CALLBACK progress_callback_local(LARGE_INTEGER totalSize, LARGE_INTEGER totalTransferred, LARGE_INTEGER streamSize, LARGE_INTEGER streamTransferred, DWORD streamNum, DWORD reason, HANDLE srcFile, HANDLE dstFile, LPVOID obj) { BackgroundCopyFileImpl *file = obj; BackgroundCopyJobImpl *job = file->owner; ULONG64 diff; EnterCriticalSection(&job->cs); diff = (file->fileProgress.BytesTotal == BG_SIZE_UNKNOWN ? totalTransferred.QuadPart : totalTransferred.QuadPart - file->fileProgress.BytesTransferred); file->fileProgress.BytesTotal = totalSize.QuadPart; file->fileProgress.BytesTransferred = totalTransferred.QuadPart; job->jobProgress.BytesTransferred += diff; LeaveCriticalSection(&job->cs); return (job->state == BG_JOB_STATE_TRANSFERRING ? PROGRESS_CONTINUE : PROGRESS_CANCEL); } static BOOL transfer_file_local(BackgroundCopyFileImpl *file, const WCHAR *tmpname) { static const WCHAR fileW[] = {'f','i','l','e',':','/','/',0}; BackgroundCopyJobImpl *job = file->owner; const WCHAR *ptr; BOOL ret; transitionJobState(job, BG_JOB_STATE_QUEUED, BG_JOB_STATE_TRANSFERRING); if (lstrlenW(file->info.RemoteName) > 7 && !_wcsnicmp(file->info.RemoteName, fileW, 7)) ptr = file->info.RemoteName + 7; else ptr = file->info.RemoteName; if (!(ret = CopyFileExW(ptr, tmpname, progress_callback_local, file, NULL, 0))) { WARN("Local file copy failed: error %u\n", GetLastError()); transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR); } SetEvent(job->done); return ret; } BOOL processFile(BackgroundCopyFileImpl *file, BackgroundCopyJobImpl *job) { static const WCHAR prefix[] = {'B','I','T', 0}; WCHAR tmpDir[MAX_PATH], tmpName[MAX_PATH]; WCHAR host[MAX_PATH]; URL_COMPONENTSW uc; BOOL ret; if (!GetTempPathW(MAX_PATH, tmpDir)) { ERR("Couldn't create temp file name: %d\n", GetLastError()); /* Guessing on what state this should give us */ transitionJobState(job, BG_JOB_STATE_QUEUED, BG_JOB_STATE_TRANSIENT_ERROR); return FALSE; } if (!GetTempFileNameW(tmpDir, prefix, 0, tmpName)) { ERR("Couldn't create temp file: %d\n", GetLastError()); /* Guessing on what state this should give us */ transitionJobState(job, BG_JOB_STATE_QUEUED, BG_JOB_STATE_TRANSIENT_ERROR); return FALSE; } EnterCriticalSection(&job->cs); file->fileProgress.BytesTotal = BG_SIZE_UNKNOWN; file->fileProgress.BytesTransferred = 0; file->fileProgress.Completed = FALSE; LeaveCriticalSection(&job->cs); TRACE("Transferring: %s -> %s -> %s\n", debugstr_w(file->info.RemoteName), debugstr_w(tmpName), debugstr_w(file->info.LocalName)); uc.dwStructSize = sizeof(uc); uc.nScheme = 0; uc.lpszScheme = NULL; uc.dwSchemeLength = 0; uc.lpszUserName = NULL; uc.dwUserNameLength = 0; uc.lpszPassword = NULL; uc.dwPasswordLength = 0; uc.lpszHostName = host; uc.dwHostNameLength = ARRAY_SIZE(host); uc.nPort = 0; uc.lpszUrlPath = NULL; uc.dwUrlPathLength = ~0u; uc.lpszExtraInfo = NULL; uc.dwExtraInfoLength = 0; ret = WinHttpCrackUrl(file->info.RemoteName, 0, 0, &uc); if (!ret) { TRACE("WinHttpCrackUrl failed, trying local file copy\n"); if (!transfer_file_local(file, tmpName)) WARN("local transfer failed\n"); } else if (!transfer_file_http(file, &uc, tmpName)) WARN("HTTP transfer failed\n"); if (transitionJobState(job, BG_JOB_STATE_CONNECTING, BG_JOB_STATE_QUEUED) || transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_QUEUED)) { lstrcpyW(file->tempFileName, tmpName); EnterCriticalSection(&job->cs); file->fileProgress.Completed = TRUE; job->jobProgress.FilesTransferred++; LeaveCriticalSection(&job->cs); return TRUE; } else { DeleteFileW(tmpName); return FALSE; } }