GitHub Viewer
#include
#include
#include
#include
#include
#include "helpers.h"
#include "proxy.h"
#ifdef BITS_INJECT_ERROR
static HRESULT _inject_hr[]
#else
const HRESULT _inject_hr[]
#endif
= { S_OK, S_OK, S_OK, S_OK };
static PyObject *
error_from_bits_hr(IBackgroundCopyManager *bcm, HRESULT hr, const char *operation)
{
LPWSTR err;
HRESULT hr2;
if (FAILED(hr2 = _inject_hr[2])
|| FAILED(hr2 = bcm->GetErrorDescription(hr, LANGIDFROMLCID(GetThreadLocale()), &err))) {
err_SetFromWindowsErrWithMessage(hr, operation);
err_SetFromWindowsErrWithMessage(hr2, "Retrieving error message");
return NULL;
}
size_t n = wcslen(err);
while (n > 0 && isspace(err[--n])) {
err[n] = L'\0';
}
err_SetFromWindowsErrWithMessage(hr, operation, err);
CoTaskMemFree(err);
return NULL;
}
static PyObject *
error_from_bits_job(IBackgroundCopyJob *job)
{
IBackgroundCopyError *error = NULL;
BG_ERROR_CONTEXT context;
HRESULT hr, hr_error = S_FALSE;
LPWSTR str_error;
PyObject *exc, *val, *tb;
if (FAILED(hr = _inject_hr[1])
|| FAILED(hr = job->GetError(&error))
|| FAILED(hr = error->GetError(&context, &hr_error))
) {
if (error) {
error->Release();
}
PyErr_SetString(PyExc_OSError, "Unidentified download error");
err_SetFromWindowsErrWithMessage(hr, "Retrieving download error");
return NULL;
}
if (FAILED(hr = _inject_hr[2])
|| FAILED(hr = error->GetErrorDescription(LANGIDFROMLCID(GetThreadLocale()), &str_error))
) {
error->Release();
//PyErr_SetFromWindowsErr(hr_error);
//err_SetFromWindowsErrWithMessage(hr, "Retrieving error message");
err_SetFromWindowsErrWithMessage(hr_error, "Could not retrieve message");
return NULL;
}
size_t n = wcslen(str_error);
while (n > 0 && isspace(str_error[--n])) {
str_error[n] = L'\0';
}
err_SetFromWindowsErrWithMessage(hr_error, "Download error", str_error);
CoTaskMemFree(str_error);
error->Release();
return NULL;
}
static HRESULT get_job_progress(IBackgroundCopyJob *job, int *progress, int *already_complete) {
HRESULT hr;
BG_JOB_STATE job_state;
BG_JOB_PROGRESS job_progress;
if (FAILED(hr = _inject_hr[0]) || FAILED(hr = job->GetState(&job_state))) {
return hr;
}
*already_complete = 0;
switch (job_state) {
case BG_JOB_STATE_QUEUED:
case BG_JOB_STATE_CONNECTING:
case BG_JOB_STATE_CANCELLED:
*progress = 0;
break;
case BG_JOB_STATE_TRANSFERRED:
*progress = 100;
break;
case BG_JOB_STATE_ACKNOWLEDGED:
*progress = 100;
*already_complete = 1;
break;
case BG_JOB_STATE_TRANSFERRING:
case BG_JOB_STATE_SUSPENDED:
if (FAILED(hr = job->GetProgress(&job_progress))) {
return hr;
}
// probably an unnecessary sanity check
if (job_progress.FilesTransferred >= job_progress.FilesTotal
|| job_progress.BytesTransferred >= job_progress.BytesTotal) {
*progress = 100;
} else if (job_progress.BytesTotal == BG_SIZE_UNKNOWN) {
*progress = (job_progress.FilesTransferred * 100) / job_progress.FilesTotal;
} else {
*progress = (job_progress.BytesTransferred * 100) / job_progress.BytesTotal;
}
break;
case BG_JOB_STATE_TRANSIENT_ERROR:
case BG_JOB_STATE_ERROR:
return S_FALSE;
}
if (*progress < 0) {
*progress = 0;
} else if (*progress > 100) {
*progress = 100;
}
return S_OK;
}
extern "C" {
// Returns a capsule containing the BackgroundCopyManager instance
PyObject *bits_connect(PyObject *, PyObject *args, PyObject *kwargs) {
IBackgroundCopyManager *bcm = NULL;
HRESULT hr = CoCreateInstance(
__uuidof(BackgroundCopyManager),
NULL,
CLSCTX_LOCAL_SERVER,
__uuidof(IBackgroundCopyManager),
(void**)&bcm
);
if (FAILED(hr)) {
PyErr_SetFromWindowsErr(hr);
return NULL;
}
return make_capsule(bcm);
}
// (conn, job_id) -> job
PyObject *bits_find_job(PyObject *, PyObject *args, PyObject *kwargs) {
static const char * keywords[] = {"conn", "job_id", NULL};
IBackgroundCopyManager *bcm = NULL;
Py_buffer job_id;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&y*:bits_find_job", keywords,
from_capsule, &bcm, &job_id
)) {
return NULL;
}
if (job_id.len < sizeof(GUID)) {
PyBuffer_Release(&job_id);
PyErr_SetString(PyExc_ValueError, "'job_id' must be a serialized job ID");
return NULL;
}
PyObject *r = NULL;
IBackgroundCopyJob *job = NULL;
GUID job_guid = *(GUID *)job_id.buf;
HRESULT hr;
if (FAILED(hr = _inject_hr[0]) || FAILED(hr = bcm->GetJob(job_guid, &job))) {
error_from_bits_hr(bcm, hr, "Getting background download");
} else {
r = make_capsule(job);
}
PyBuffer_Release(&job_id);
return r;
}
// (conn, job) -> job_id
PyObject *bits_serialize_job(PyObject *, PyObject *args, PyObject *kwargs) {
static const char * keywords[] = {"conn", "job", NULL};
IBackgroundCopyManager *bcm = NULL;
IBackgroundCopyJob* job = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&O&:bits_serialize_job", keywords,
from_capsule, &bcm, from_capsule, &job)) {
return NULL;
}
PyObject *r = NULL;
GUID job_id;
HRESULT hr;
if (FAILED(hr = _inject_hr[0]) || FAILED(hr = job->GetId(&job_id))) {
error_from_bits_hr(bcm, hr, "Getting download job ID");
} else {
r = PyBytes_FromStringAndSize((char *)&job_id, sizeof(job_id));
}
return r;
}
static HRESULT _job_setproxy(IBackgroundCopyJob *job, const ProxySettings *settings) {
if (settings->mode == PROXY_MODE_DIRECT) {
return job->SetProxySettings(BG_JOB_PROXY_USAGE_NO_PROXY, NULL, NULL);
}
if (settings->mode == PROXY_MODE_OVERRIDE) {
return job->SetProxySettings(
BG_JOB_PROXY_USAGE_OVERRIDE,
settings->proxy_list,
NULL
);
}
WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxy_config = { 0 };
if (WinHttpGetIEProxyConfigForCurrentUser(&proxy_config)) {
if (proxy_config.lpszProxy || proxy_config.lpszAutoConfigUrl || proxy_config.fAutoDetect) {
// Global settings are present, and so BITS's default behaviour
// should be fine.
if (proxy_config.lpszProxy) {
GlobalFree(proxy_config.lpszProxy);
}
if (proxy_config.lpszProxyBypass) {
GlobalFree(proxy_config.lpszProxyBypass);
}
if (proxy_config.lpszAutoConfigUrl) {
GlobalFree(proxy_config.lpszAutoConfigUrl);
}
return S_OK;
}
}
// Probably no static settings, so tell BITS to do autodetection.
return job->SetProxySettings(BG_JOB_PROXY_USAGE_AUTODETECT, NULL, NULL);
}
static HRESULT _job_setcredentials(
IBackgroundCopyJob *job,
BG_AUTH_TARGET target,
wchar_t *username,
wchar_t *password
) {
IBackgroundCopyJob2 *job2 = NULL;
HRESULT hr;
BG_AUTH_CREDENTIALS creds = {
.Target = target,
.Scheme = username ? BG_AUTH_SCHEME_BASIC : BG_AUTH_SCHEME_NEGOTIATE,
.Credentials = {
.Basic = {
.UserName = username,
.Password = username ? password : NULL
}
}
};
if (FAILED(hr = _inject_hr[3])
|| FAILED(hr = job->QueryInterface(__uuidof(IBackgroundCopyJob2), (void **)&job2))) {
return hr;
}
hr = job2->SetCredentials(&creds);
job2->Release();
return hr;
}
// (conn, name, url, path, [username], [password], [proxy_settings]) -> job
PyObject *bits_begin(PyObject *, PyObject *args, PyObject *kwargs) {
static const char * keywords[] = {
"conn", "name", "url", "path", "username", "password", "proxy_settings", NULL
};
IBackgroundCopyManager *bcm = NULL;
wchar_t *name = NULL;
wchar_t *url = NULL;
wchar_t *path = NULL;
wchar_t *username = NULL;
wchar_t *password = NULL;
PyObject *proxy_settings_obj = Py_None;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&O&O&O&|O&O&O:bits_begin", keywords,
from_capsule, &bcm, as_utf16, &name, as_utf16, &url, as_utf16, &path,
as_utf16, &username, as_utf16, &password, &proxy_settings_obj
)) {
return NULL;
}
PyObject *r = NULL;
GUID jobId;
IBackgroundCopyJob* job = NULL;
ProxySettings proxy_settings = {};
HRESULT hr;
if (!proxy_settings_parse(proxy_settings_obj, &proxy_settings)) {
goto done;
}
if (FAILED(hr = _inject_hr[0])
|| FAILED(hr = bcm->CreateJob(name, BG_JOB_TYPE_DOWNLOAD, &jobId, &job))) {
error_from_bits_hr(bcm, hr, "Creating download job");
goto done;
}
if (FAILED(hr = _job_setproxy(job, &proxy_settings))) {
error_from_bits_hr(bcm, hr, "Setting proxy");
goto done;
}
// Explicit proxy credentials come from the configured proxy URL. NULL
// automatically infers credentials if needed, which remains the default.
if (FAILED(hr = _job_setcredentials(
job,
BG_AUTH_TARGET_PROXY,
proxy_settings.username,
proxy_settings.password
))) {
error_from_bits_hr(bcm, hr, "Setting proxy credentials");
goto done;
}
if (FAILED(hr = _job_setcredentials(job, BG_AUTH_TARGET_SERVER, username, password))) {
error_from_bits_hr(bcm, hr, "Adding basic credentials to download job");
goto done;
}
if (FAILED(hr = job->AddFile(url, path))) {
error_from_bits_hr(bcm, hr, "Adding file to download job");
goto done;
}
if (FAILED(hr = job->SetPriority(BG_JOB_PRIORITY_FOREGROUND))) {
error_from_bits_hr(bcm, hr, "Setting download job priority");
goto done;
}
if (FAILED(hr = job->Resume())) {
error_from_bits_hr(bcm, hr, "Starting download job");
goto done;
}
job->AddRef();
r = make_capsule(job);
done:
if (job) {
job->Release();
}
proxy_settings_clear(&proxy_settings);
PyMem_Free(path);
PyMem_Free(url);
PyMem_Free(name);
PyMem_Free(username);
PyMem_Free(password);
return r;
}
// (job)
PyObject *bits_cancel(PyObject *, PyObject *args, PyObject *kwargs) {
static const char * keywords[] = {"conn", "job", NULL};
IBackgroundCopyManager *bcm = NULL;
IBackgroundCopyJob* job = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&O&:bits_cancel", keywords,
from_capsule, &bcm, from_capsule, &job)) {
return NULL;
}
HRESULT hr;
if (FAILED(hr = _inject_hr[0]) || FAILED(hr = job->Cancel())) {
return error_from_bits_hr(bcm, hr, "Cancelling download job");
}
Py_RETURN_NONE;
}
// (job) -> int[0..100] or exception
PyObject *bits_get_progress(PyObject *, PyObject *args, PyObject *kwargs) {
static const char * keywords[] = {"conn", "job", NULL};
IBackgroundCopyManager *bcm = NULL;
IBackgroundCopyJob* job = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&O&:bits_get_progress", keywords,
from_capsule, &bcm, from_capsule, &job)) {
return NULL;
}
PyObject *r = NULL;
int progress = 0, already_complete = 0;
HRESULT hr = _inject_hr[0];
if (!FAILED(hr) && hr != S_FALSE) {
hr = get_job_progress(job, &progress, &already_complete);
}
if (hr == S_FALSE) {
return error_from_bits_job(job);
} else if (FAILED(hr)) {
return error_from_bits_hr(bcm, hr, "Getting download progress");
}
if (progress == 100 && !already_complete) {
hr = job->Complete();
if (FAILED(hr)) {
return error_from_bits_hr(bcm, hr, "Completing download job");
}
}
return Py_BuildValue("i", progress);
}
// (conn, job, username, password) -> job
PyObject *bits_retry_with_auth(PyObject *, PyObject *args, PyObject *kwargs) {
static const char * keywords[] = {"conn", "job", "username", "password", NULL};
IBackgroundCopyManager *bcm = NULL;
IBackgroundCopyJob* job = NULL;
wchar_t *username = NULL;
wchar_t *password = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&O&O&O&:bits_retry_with_auth", keywords,
from_capsule, &bcm, from_capsule, &job,
as_utf16, &username, as_utf16, &password
)) {
return NULL;
}
HRESULT hr;
PyObject *r = NULL;
if (FAILED(hr = _job_setcredentials(job, BG_AUTH_TARGET_SERVER, username, password))) {
error_from_bits_hr(bcm, hr, "Adding basic credentials to download job");
goto done;
}
if (FAILED(hr = job->Resume())) {
error_from_bits_hr(bcm, hr, "Starting download job");
goto done;
}
r = Py_GetConstant(Py_CONSTANT_NONE);
done:
PyMem_Free(username);
PyMem_Free(password);
return r;
}
#ifdef BITS_INJECT_ERROR
PyObject *bits_inject_error(PyObject *, PyObject *args, PyObject *kwargs) {
HRESULT hr;
if (!PyArg_ParseTuple(
args, "IIII:bits_inject_error",
// replace HRESULT for primary operation
&_inject_hr[0],
// replace HRESULT for getting error code
&_inject_hr[1],
// replace HRESULT for getting error text
&_inject_hr[2],
// replace HRESULT for adding credentials to job
&_inject_hr[3]
)) {
return NULL;
}
Py_RETURN_NONE;
}
#endif
}