| /* | |
| * 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 <stdarg.h> | |
| #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 (strlenW(file->info.RemoteName) > 7 && !memicmpW(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 = sizeof(host)/sizeof(host[0]); | |
| 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; | |
| } | |
| } |