Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 34 additions & 10 deletions src/_native/bits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <typeinfo>

#include "helpers.h"
#include "proxy.h"

#ifdef BITS_INJECT_ERROR
static HRESULT _inject_hr[]
Expand Down Expand Up @@ -205,7 +206,18 @@ PyObject *bits_serialize_job(PyObject *, PyObject *args, PyObject *kwargs) {
}


static HRESULT _job_setproxy(IBackgroundCopyJob *job) {
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) {
Expand Down Expand Up @@ -257,39 +269,50 @@ static HRESULT _job_setcredentials(
return hr;
}

// (conn, name, url, path, [username], [password]) -> job
// (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", NULL};
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;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&O&O&O&|O&O&:bits_begin", keywords,
PyObject *proxy_settings_obj = Py_None;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&O&O&O&|O&O&O:bits_begin", keywords,
from_capsule<IBackgroundCopyManager>, &bcm, as_utf16, &name, as_utf16, &url, as_utf16, &path,
as_utf16, &username, as_utf16, &password
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))) {
if (FAILED(hr = _job_setproxy(job, &proxy_settings))) {
error_from_bits_hr(bcm, hr, "Setting proxy");
goto done;
}
// Setting proxy credentials to NULL will automatically infer credentials
// if needed. It's a good default (provided users have not configured a
// malicious proxy server, which we can't do anything about here anyway).
if (FAILED(hr = _job_setcredentials(job, BG_AUTH_TARGET_PROXY, NULL, NULL))) {
// 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;
}
Expand Down Expand Up @@ -317,6 +340,7 @@ PyObject *bits_begin(PyObject *, PyObject *args, PyObject *kwargs) {
if (job) {
job->Release();
}
proxy_settings_clear(&proxy_settings);
PyMem_Free(path);
PyMem_Free(url);
PyMem_Free(name);
Expand Down
56 changes: 56 additions & 0 deletions src/_native/proxy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#include <Python.h>

#include "helpers.h"
#include "proxy.h"

int proxy_settings_parse(PyObject *obj, ProxySettings *settings) {
settings->mode = PROXY_MODE_AUTO;
settings->proxy_list = NULL;
settings->username = NULL;
settings->password = NULL;

if (!obj || Py_Is(obj, Py_GetConstantBorrowed(Py_CONSTANT_NONE))) {
return 1;
}

int mode;
if (!PyArg_ParseTuple(
obj,
"iO&O&O&:proxy_settings",
&mode,
as_utf16, &settings->proxy_list,
as_utf16, &settings->username,
as_utf16, &settings->password
)) {
// PyArg_ParseTuple calls cleanup for successful O& converters when a
// later conversion fails, so these pointers no longer own memory.
settings->proxy_list = NULL;
settings->username = NULL;
settings->password = NULL;
return 0;
}

if (mode < PROXY_MODE_AUTO || mode > PROXY_MODE_OVERRIDE) {
PyErr_SetString(PyExc_ValueError, "invalid proxy mode");
proxy_settings_clear(settings);
return 0;
}
settings->mode = (ProxyMode)mode;

if (settings->mode == PROXY_MODE_OVERRIDE && !settings->proxy_list) {
PyErr_SetString(PyExc_ValueError, "proxy override requires a proxy list");
proxy_settings_clear(settings);
return 0;
}
return 1;
}

void proxy_settings_clear(ProxySettings *settings) {
PyMem_Free(settings->proxy_list);
PyMem_Free(settings->username);
PyMem_Free(settings->password);
settings->mode = PROXY_MODE_AUTO;
settings->proxy_list = NULL;
settings->username = NULL;
settings->password = NULL;
}
19 changes: 19 additions & 0 deletions src/_native/proxy.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include <Python.h>

enum ProxyMode {
PROXY_MODE_AUTO = 0,
PROXY_MODE_DIRECT = 1,
PROXY_MODE_OVERRIDE = 2,
};

struct ProxySettings {
ProxyMode mode;
wchar_t *proxy_list;
wchar_t *username;
wchar_t *password;
};

int proxy_settings_parse(PyObject *obj, ProxySettings *settings);
void proxy_settings_clear(ProxySettings *settings);
70 changes: 61 additions & 9 deletions src/_native/winhttp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <netlistmgr.h>

#include "helpers.h"
#include "proxy.h"

#pragma comment(lib, "winhttp.lib")

Expand Down Expand Up @@ -191,7 +192,12 @@ extern "C" {
#define CHECK_WINHTTP(x) if (!x) { winhttp_error(); goto exit; }


static bool winhttp_apply_proxy(HINTERNET hSession, HINTERNET hRequest, const wchar_t *url) {
static bool winhttp_apply_proxy(
HINTERNET hSession,
HINTERNET hRequest,
const wchar_t *url,
const ProxySettings *settings
) {
bool result = false;
WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxy_config = { 0 };
WINHTTP_AUTOPROXY_OPTIONS proxy_opt = {
Expand All @@ -200,6 +206,42 @@ static bool winhttp_apply_proxy(HINTERNET hSession, HINTERNET hRequest, const wc
};
WINHTTP_PROXY_INFO proxy_info = { 0 };

if (settings->mode == PROXY_MODE_DIRECT) {
// The session may have inherited a static WinHTTP proxy, so explicitly
// override it rather than relying on the absence of request settings.
proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NO_PROXY;
CHECK_WINHTTP(WinHttpSetOption(
hRequest,
WINHTTP_OPTION_PROXY,
&proxy_info,
sizeof(proxy_info)
));
result = true;
goto exit;
}

if (settings->mode == PROXY_MODE_OVERRIDE) {
proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
proxy_info.lpszProxy = settings->proxy_list;

CHECK_WINHTTP(WinHttpSetCredentials(
hRequest,
WINHTTP_AUTH_TARGET_PROXY,
settings->username ? WINHTTP_AUTH_SCHEME_BASIC : WINHTTP_AUTH_SCHEME_NEGOTIATE,
settings->username,
settings->username ? settings->password : NULL,
NULL
));
CHECK_WINHTTP(WinHttpSetOption(
hRequest,
WINHTTP_OPTION_PROXY,
&proxy_info,
sizeof(proxy_info)
));
result = true;
goto exit;
}

// First load the global-ish config settings
if (!WinHttpGetIEProxyConfigForCurrentUser(&proxy_config)) {
if (GetLastError() != ERROR_FILE_NOT_FOUND) {
Expand Down Expand Up @@ -248,10 +290,10 @@ static bool winhttp_apply_proxy(HINTERNET hSession, HINTERNET hRequest, const wc

result = true;
exit:
if (proxy_info.lpszProxy) {
if (settings->mode == PROXY_MODE_AUTO && proxy_info.lpszProxy) {
GlobalFree((HGLOBAL)proxy_info.lpszProxy);
}
if (proxy_info.lpszProxyBypass) {
if (settings->mode == PROXY_MODE_AUTO && proxy_info.lpszProxyBypass) {
GlobalFree((HGLOBAL)proxy_info.lpszProxyBypass);
}
if (proxy_opt.lpszAutoConfigUrl) {
Expand All @@ -262,21 +304,26 @@ static bool winhttp_apply_proxy(HINTERNET hSession, HINTERNET hRequest, const wc


PyObject *winhttp_urlopen(PyObject *, PyObject *args, PyObject *kwargs) {
static const char * keywords[] = {"url", "method", "headers", "accepts", "chunksize", "on_progress", "on_cred_request", NULL};
static const char * keywords[] = {
"url", "method", "headers", "accepts", "chunksize",
"on_progress", "on_cred_request", "proxy_settings", NULL
};
wchar_t *url = NULL;
wchar_t *method = NULL;
wchar_t *headers = NULL;
wchar_t *accepts = NULL;
PyObject *on_progress = NULL;
PyObject *on_cred_request = NULL;
PyObject *proxy_settings_obj = Py_None;

PyObject *result = NULL;
URL_COMPONENTS url_parts = { sizeof(URL_COMPONENTS) };
HINTERNET hSession = NULL;
HINTERNET hConnection = NULL;
HINTERNET hRequest = NULL;
DWORD opt = 0;
LPCWSTR *accepts_array;
LPCWSTR *accepts_array = NULL;
ProxySettings proxy_settings = {};

Py_ssize_t chunksize = 65536;
DWORD status_code = 0;
Expand All @@ -289,10 +336,14 @@ PyObject *winhttp_urlopen(PyObject *, PyObject *args, PyObject *kwargs) {
wchar_t *user = NULL;
wchar_t *pass = NULL;

if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&O&O&O&|nOO:winhttp_urlopen", keywords,
as_utf16, &url, as_utf16, &method, as_utf16, &headers, as_utf16, &accepts, &chunksize, &on_progress, &on_cred_request)) {
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&O&O&O&|nOOO:winhttp_urlopen", keywords,
as_utf16, &url, as_utf16, &method, as_utf16, &headers, as_utf16, &accepts,
&chunksize, &on_progress, &on_cred_request, &proxy_settings_obj)) {
return NULL;
}
if (!proxy_settings_parse(proxy_settings_obj, &proxy_settings)) {
goto exit;
}

if (on_progress && !PyObject_IsTrue(on_progress)) {
on_progress = NULL;
Expand Down Expand Up @@ -370,7 +421,7 @@ PyObject *winhttp_urlopen(PyObject *, PyObject *args, PyObject *kwargs) {
);
CHECK_WINHTTP(hRequest);

CHECK_WINHTTP(winhttp_apply_proxy(hSession, hRequest, url));
CHECK_WINHTTP(winhttp_apply_proxy(hSession, hRequest, url, &proxy_settings));

opt = WINHTTP_DECOMPRESSION_FLAG_ALL;
CHECK_WINHTTP(WinHttpSetOption(
Expand Down Expand Up @@ -514,6 +565,7 @@ PyObject *winhttp_urlopen(PyObject *, PyObject *args, PyObject *kwargs) {
}
PyMem_Free(user);
PyMem_Free(pass);
proxy_settings_clear(&proxy_settings);
PyMem_Free(hostname);
PyMem_Free(urlpath);
PyMem_Free(accepts_array);
Expand Down Expand Up @@ -645,4 +697,4 @@ PyObject *winhttp_urlunsplit(PyObject *, PyObject *args, PyObject *kwargs) {
}


}
}
Loading