Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: honor http_proxy environment variables #1111

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

**Features**:

- Honor `http_proxy` environment variables. ([#1111](https://github.com/getsentry/sentry-native/pull/1111))

## 0.7.19

**Fixes**:
Expand All @@ -12,7 +18,7 @@

- Add support for Xbox Series X/S. ([#1100](https://github.com/getsentry/sentry-native/pull/1100))
- Add option to set debug log level. ([#1107](https://github.com/getsentry/sentry-native/pull/1107))
- Add `traces_sampler` ([#1108](https://github.com/getsentry/sentry-native/pull/1108))
- Add `traces_sampler`. ([#1108](https://github.com/getsentry/sentry-native/pull/1108))
- Provide support for C++17 compilers when using the `crashpad` backend. ([#1110](https://github.com/getsentry/sentry-native/pull/1110), [crashpad#116](https://github.com/getsentry/crashpad/pull/116), [mini_chromium#1](https://github.com/getsentry/mini_chromium/pull/1))

## 0.7.17
Expand Down
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,10 @@ The example currently supports the following commands:
- `override-sdk-name`: Changes the SDK name via the options at runtime.
- `stack-overflow`: Provokes a stack-overflow.
- `http-proxy`: Uses a localhost `HTTP` proxy on port 8080.
- `http-proxy-auth`: Uses a localhost `HTTP` proxy on port 8080 with `user:password` as authentication.
- `socks5-proxy`: Uses a localhost `SOCKS5` proxy on port 1080.
- `socks5-proxy-auth`: Uses a localhost `SOCKS5` proxy on port 1080 with `user:password` as authentication.
- `proxy-from-env`: Reads the proxy settings from the environment variables `https_proxy` and `http_proxy` (in this order of precedence).
- `capture-transaction`: Captures a transaction.
- `traces-sampler`: Installs a traces sampler callback function when used alongside `capture-transaction`.

Expand Down
8 changes: 8 additions & 0 deletions examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,18 @@ main(int argc, char **argv)
if (has_arg(argc, argv, "http-proxy")) {
sentry_options_set_proxy(options, "http://127.0.0.1:8080");
}
if (has_arg(argc, argv, "http-proxy-auth")) {
sentry_options_set_proxy(
options, "http://user:[email protected]:8080");
}

if (has_arg(argc, argv, "socks5-proxy")) {
sentry_options_set_proxy(options, "socks5://127.0.0.1:1080");
}
if (has_arg(argc, argv, "socks5-proxy-auth")) {
sentry_options_set_proxy(
options, "socks5://user:[email protected]:1080");
}

sentry_init(options);

Expand Down
27 changes: 16 additions & 11 deletions src/sentry_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ is_scheme_valid(const char *scheme_name)
}

int
sentry__url_parse(sentry_url_t *url_out, const char *url)
sentry__url_parse(sentry_url_t *url_out, const char *url, bool requires_path)
{
bool has_username;
int result = 0;
Expand Down Expand Up @@ -145,8 +145,20 @@ sentry__url_parse(sentry_url_t *url_out, const char *url)
ptr = tmp;
}

if (url_out->port == 0) {
if (sentry__string_eq(url_out->scheme, "https")) {
url_out->port = 443;
} else if (sentry__string_eq(url_out->scheme, "http")) {
url_out->port = 80;
}
}

if (!*ptr) {
goto error;
if (requires_path) {
goto error;
}
result = 0;
goto cleanup;
}

/* end of netloc */
Expand Down Expand Up @@ -177,14 +189,6 @@ sentry__url_parse(sentry_url_t *url_out, const char *url)
url_out->fragment = sentry__string_clone_n_unchecked(ptr, tmp - ptr);
}

if (url_out->port == 0) {
if (sentry__string_eq(url_out->scheme, "https")) {
url_out->port = 443;
} else if (sentry__string_eq(url_out->scheme, "http")) {
url_out->port = 80;
}
}

result = 0;
goto cleanup;

Expand Down Expand Up @@ -228,7 +232,8 @@ sentry__dsn_new_n(const char *raw_dsn, size_t raw_dsn_len)
dsn->refcount = 1;

dsn->raw = sentry__string_clone_n(raw_dsn, raw_dsn_len);
if (!dsn->raw || !dsn->raw[0] || sentry__url_parse(&url, dsn->raw) != 0) {
if (!dsn->raw || !dsn->raw[0]
|| sentry__url_parse(&url, dsn->raw, true) != 0) {
goto exit;
}

Expand Down
4 changes: 3 additions & 1 deletion src/sentry_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ typedef struct {
/**
* Parse the given `url` into the pre-allocated `url_out` parameter.
* Returns 0 on success.
* `requires_path` flags whether the url needs a / after the host(:port) section
*/
int sentry__url_parse(sentry_url_t *url_out, const char *url);
int sentry__url_parse(
sentry_url_t *url_out, const char *url, bool requires_path);

/**
* This will free all the internal members of `url`, but not `url` itself, as
Expand Down
39 changes: 39 additions & 0 deletions src/transports/sentry_transport_winhttp.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ typedef struct {
sentry_dsn_t *dsn;
wchar_t *user_agent;
wchar_t *proxy;
wchar_t *proxy_username;
wchar_t *proxy_password;
sentry_rate_limiter_t *ratelimiter;
HINTERNET session;
HINTERNET connect;
Expand Down Expand Up @@ -51,10 +53,35 @@ sentry__winhttp_bgworker_state_free(void *_state)
sentry__dsn_decref(state->dsn);
sentry__rate_limiter_free(state->ratelimiter);
sentry_free(state->user_agent);
sentry_free(state->proxy_username);
sentry_free(state->proxy_password);
sentry_free(state->proxy);
sentry_free(state);
}

// Function to extract and set credentials
static void
set_proxy_credentials(winhttp_bgworker_state_t *state, const char *proxy)
{
sentry_url_t url;
sentry__url_parse(&url, proxy, false);
if (url.username && url.password) {
// Convert user and pass to LPCWSTR
int user_wlen
= MultiByteToWideChar(CP_UTF8, 0, url.username, -1, NULL, 0);
int pass_wlen
= MultiByteToWideChar(CP_UTF8, 0, url.password, -1, NULL, 0);
wchar_t *user_w = (wchar_t *)malloc(user_wlen * sizeof(wchar_t));
wchar_t *pass_w = (wchar_t *)malloc(pass_wlen * sizeof(wchar_t));
MultiByteToWideChar(CP_UTF8, 0, url.username, -1, user_w, user_wlen);
MultiByteToWideChar(CP_UTF8, 0, url.password, -1, pass_w, pass_wlen);

state->proxy_username = user_w;
state->proxy_password = pass_w;
}
sentry__url_cleanup(&url);
}

static int
sentry__winhttp_transport_start(
const sentry_options_t *opts, void *transport_state)
Expand All @@ -71,7 +98,12 @@ sentry__winhttp_transport_start(
// ensure the proxy starts with `http://`, otherwise ignore it
if (opts->proxy && strstr(opts->proxy, "http://") == opts->proxy) {
const char *ptr = opts->proxy + 7;
JoshuaMoelans marked this conversation as resolved.
Show resolved Hide resolved
const char *at_sign = strchr(ptr, '@');
const char *slash = strchr(ptr, '/');
if (at_sign && (!slash || at_sign < slash)) {
ptr = at_sign + 1;
set_proxy_credentials(state, opts->proxy);
}
if (slash) {
char *copy = sentry__string_clone_n(ptr, slash - ptr);
state->proxy = sentry__string_to_wstr(copy);
Expand Down Expand Up @@ -103,6 +135,7 @@ sentry__winhttp_transport_start(
SENTRY_WARN("`WinHttpOpen` failed");
return 1;
}

return sentry__bgworker_start(bgworker);
}

Expand Down Expand Up @@ -209,6 +242,12 @@ sentry__winhttp_send_task(void *_envelope, void *_state)
SENTRY_DEBUGF(
"sending request using winhttp to \"%s\":\n%S", req->url, headers);

if (state->proxy_username && state->proxy_password) {
WinHttpSetCredentials(state->request, WINHTTP_AUTH_TARGET_PROXY,
WINHTTP_AUTH_SCHEME_BASIC, state->proxy_username,
state->proxy_password, 0);
}

if (WinHttpSendRequest(state->request, headers, (DWORD)-1,
(LPVOID)req->body, (DWORD)req->body_len, (DWORD)req->body_len, 0)) {
WinHttpReceiveResponse(state->request, NULL);
Expand Down
44 changes: 39 additions & 5 deletions tests/test_integration_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,12 @@ def test_capture_minidump(cmake, httpserver):
],
)
@pytest.mark.parametrize("proxy_status", [(["off"]), (["on"])])
def test_capture_proxy(cmake, httpserver, run_args, proxy_status):
@pytest.mark.parametrize("proxy_auth", [(["off"]), (["on"])])
@pytest.mark.parametrize("proxy_IPv", [(["4"]), (["6"])])
@pytest.mark.parametrize("proxy_from_env", [(["proxy-from-env"]), ([""])])
Comment on lines 627 to +630
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is becoming exponentially larger. It might make sense to split apart some test cases to not cover the full matrix at once. There are still some untested cases:

  • What if proxy-from-env but the env. variable is empty? -> expected is that if there was a manually set proxy, it gets used.
  • Test whetherproxy-from-env takes precedence over manually set proxy if both are given.

def test_capture_proxy(
cmake, httpserver, run_args, proxy_status, proxy_auth, proxy_from_env, proxy_IPv
):
if not shutil.which("mitmdump"):
pytest.skip("mitmdump is not installed")

Expand All @@ -635,12 +640,18 @@ def test_capture_proxy(cmake, httpserver, run_args, proxy_status):
if proxy_status == ["on"]:
# start mitmdump from terminal
if run_args == ["http-proxy"]:
proxy_process = subprocess.Popen(["mitmdump"])
proxy_command = ["mitmdump"]
if proxy_auth == ["on"]:
proxy_command.append("--proxyauth=user:password")
proxy_process = subprocess.Popen(proxy_command)
time.sleep(5) # Give mitmdump some time to start
if not is_proxy_running("localhost", 8080):
pytest.fail("mitmdump (HTTP) did not start correctly")
elif run_args == ["socks5-proxy"]:
proxy_process = subprocess.Popen(["mitmdump", "--mode", "socks5"])
proxy_command = ["mitmdump", "--mode", "socks5"]
if proxy_auth == ["on"]:
proxy_command.append("--proxyauth=user:password")
proxy_process = subprocess.Popen(proxy_command)
time.sleep(5) # Give mitmdump some time to start
if not is_proxy_running("localhost", 1080):
pytest.fail("mitmdump (SOCKS5) did not start correctly")
Expand All @@ -650,13 +661,34 @@ def test_capture_proxy(cmake, httpserver, run_args, proxy_status):
# make sure we are isolated from previous runs
shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True)

httpserver.expect_request("/api/123456/envelope/").respond_with_data("OK")
# if proxy_from_env is set, set the proxy environment variables
if proxy_from_env == ["proxy-from-env"]:
auth = ""
host = "localhost"
if proxy_IPv == ["6"]:
host = "[::1]"
if proxy_auth == ["on"]:
auth = "user:password@"
if run_args == ["http-proxy"]:
os.environ["http_proxy"] = f"http://{auth}{host}:8080"
os.environ["https_proxy"] = f"http://{auth}{host}:8080"
elif run_args == ["socks5-proxy"]:
os.environ["http_proxy"] = f"socks5://{auth}{host}:1080"
os.environ["https_proxy"] = f"socks5://{auth}{host}:1080"

httpserver.expect_request("/api/123456/envelope/").respond_with_data("OK")
current_run_arg = run_args[0]
if proxy_auth == ["on"]:
current_run_arg += "-auth"
if proxy_from_env == ["proxy-from-env"]:
current_run_arg = (
"" # overwrite args if proxy-from-env is set (e.g. don't manually set)
)
run(
tmp_path,
"sentry_example",
["log", "start-session", "capture-event"]
+ run_args, # only passes if given proxy is running
+ [current_run_arg], # only passes if given proxy is running
check=True,
env=dict(os.environ, SENTRY_DSN=make_dsn(httpserver)),
)
Expand All @@ -670,3 +702,5 @@ def test_capture_proxy(cmake, httpserver, run_args, proxy_status):
if proxy_process:
proxy_process.terminate()
proxy_process.wait()
if proxy_from_env == ["proxy-from-env"]:
del os.environ["http_proxy"] # remove the proxy environment variables
47 changes: 34 additions & 13 deletions tests/unit/test_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,24 @@ SENTRY_TEST(iso_time)
TEST_CHECK_INT_EQUAL(roundtrip, usec);
}

static void
check_url(const sentry_url_t *url)
{
TEST_CHECK_STRING_EQUAL(url->scheme, "http");
TEST_CHECK_STRING_EQUAL(url->host, "example.com");
TEST_CHECK_INT_EQUAL(url->port, 80);
TEST_CHECK_STRING_EQUAL(url->username, "username");
TEST_CHECK_STRING_EQUAL(url->password, "password");
}

SENTRY_TEST(url_parsing_complete)
{
sentry_url_t url;
TEST_CHECK_INT_EQUAL(
sentry__url_parse(
&url, "http://username:[email protected]/foo/bar?x=y#z"),
&url, "http://username:[email protected]/foo/bar?x=y#z", true),
0);
TEST_CHECK_STRING_EQUAL(url.scheme, "http");
TEST_CHECK_STRING_EQUAL(url.host, "example.com");
TEST_CHECK_INT_EQUAL(url.port, 80);
TEST_CHECK_STRING_EQUAL(url.username, "username");
TEST_CHECK_STRING_EQUAL(url.password, "password");
check_url(&url);
TEST_CHECK_STRING_EQUAL(url.path, "/foo/bar");
TEST_CHECK_STRING_EQUAL(url.query, "x=y");
TEST_CHECK_STRING_EQUAL(url.fragment, "z");
Expand All @@ -49,13 +55,10 @@ SENTRY_TEST(url_parsing_partial)
{
sentry_url_t url;
TEST_CHECK_INT_EQUAL(
sentry__url_parse(&url, "http://username:[email protected]/foo/bar"),
sentry__url_parse(
&url, "http://username:[email protected]/foo/bar", true),
0);
TEST_CHECK_STRING_EQUAL(url.scheme, "http");
TEST_CHECK_STRING_EQUAL(url.host, "example.com");
TEST_CHECK_INT_EQUAL(url.port, 80);
TEST_CHECK_STRING_EQUAL(url.username, "username");
TEST_CHECK_STRING_EQUAL(url.password, "password");
check_url(&url);
TEST_CHECK_STRING_EQUAL(url.path, "/foo/bar");
TEST_CHECK(url.query == NULL);
TEST_CHECK(url.fragment == NULL);
Expand All @@ -65,7 +68,25 @@ SENTRY_TEST(url_parsing_partial)
SENTRY_TEST(url_parsing_invalid)
{
sentry_url_t url;
TEST_CHECK_INT_EQUAL(sentry__url_parse(&url, "http:"), 1);
TEST_CHECK_INT_EQUAL(sentry__url_parse(&url, "http:", true), 1);
}

SENTRY_TEST(url_parsing_no_path)
{
sentry_url_t url;
TEST_CHECK_INT_EQUAL(
sentry__url_parse(&url, "http://username:[email protected]", false),
0);
check_url(&url);
sentry__url_cleanup(&url);
}

SENTRY_TEST(url_parsing_with_path)
{
sentry_url_t url;
TEST_CHECK_INT_EQUAL(
sentry__url_parse(&url, "http://username:[email protected]", true),
1);
}

SENTRY_TEST(dsn_parsing_complete)
Expand Down
Loading