blob: 3673ede142d74acfddc9365b7b2f11ba3d14dbe6 [file] [log] [blame]
/*
* 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;
}
}