diff --git a/CHANGELOG.md b/CHANGELOG.md index bd2533241..b0f2bdc04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,18 @@ # Changelog +## Unreleased + +**Features**: + +- Honor `http_proxy` environment variables. ([#1111](https://github.com/getsentry/sentry-native/pull/1111)) + ## 0.7.18 **Features**: - 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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 01585ad9f..5bc81c0d7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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`. diff --git a/examples/example.c b/examples/example.c index 6e48ed8a5..f609fc3d2 100644 --- a/examples/example.c +++ b/examples/example.c @@ -271,10 +271,22 @@ 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:password@127.0.0.1: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:password@127.0.0.1:1080"); + } + + if (has_arg(argc, argv, "proxy-from-env")) { + sentry_options_set_read_proxy_from_environment(options, true); + } sentry_init(options); diff --git a/include/sentry.h b/include/sentry.h index 9dd77e13e..2641308c1 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1013,6 +1013,17 @@ SENTRY_API void sentry_options_set_http_proxy_n( SENTRY_API const char *sentry_options_get_http_proxy( const sentry_options_t *opts); +/** + * Sets whether to read the proxy settings from the environment. + */ +SENTRY_API void sentry_options_set_read_proxy_from_environment( + sentry_options_t *opts, int val); +/** + * Returns whether to read the proxy settings from the environment. + */ +SENTRY_API int sentry_options_get_read_proxy_from_environment( + const sentry_options_t *opts); + /** * Configures the path to a file containing ssl certificates for * verification. diff --git a/src/sentry_core.c b/src/sentry_core.c index f37a281e1..0f49bf4bb 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -138,6 +138,10 @@ sentry_init(sentry_options_t *options) "the provided DSN \"%s\" is not valid", raw_dsn ? raw_dsn : ""); } + if (options->read_proxy_from_environment) { + sentry__set_proxy_from_environment(options); + } + if (transport) { if (sentry__transport_startup(transport, options) != 0) { SENTRY_WARN("failed to initialize transport"); diff --git a/src/sentry_options.c b/src/sentry_options.c index 60db87a28..887f24047 100644 --- a/src/sentry_options.c +++ b/src/sentry_options.c @@ -276,6 +276,29 @@ sentry_options_get_http_proxy(const sentry_options_t *opts) return sentry_options_get_proxy(opts); } +void +sentry_options_set_read_proxy_from_environment(sentry_options_t *opts, int val) +{ + opts->read_proxy_from_environment = !!val; +} + +int +sentry_options_get_read_proxy_from_environment(const sentry_options_t *opts) +{ + return opts->read_proxy_from_environment; +} + +void +sentry__set_proxy_from_environment(sentry_options_t *opts) +{ + const char *https_proxy = getenv("https_proxy"); + if (https_proxy) { + sentry_options_set_proxy(opts, https_proxy); + } else { + sentry_options_set_proxy(opts, getenv("http_proxy")); + } +} + void sentry_options_set_ca_certs(sentry_options_t *opts, const char *path) { diff --git a/src/sentry_options.h b/src/sentry_options.h index 521e9e5e1..952df362d 100644 --- a/src/sentry_options.h +++ b/src/sentry_options.h @@ -49,6 +49,7 @@ typedef struct sentry_options_s { bool require_user_consent; bool symbolize_stacktraces; bool system_crash_reporter_enabled; + bool read_proxy_from_environment; sentry_attachment_t *attachments; sentry_run_t *run; @@ -80,4 +81,8 @@ typedef struct sentry_options_s { */ sentry_options_t *sentry__options_incref(sentry_options_t *options); +/** + * Sets the proxy value by reading it from the environment. + */ +void sentry__set_proxy_from_environment(sentry_options_t *opts); #endif diff --git a/src/transports/sentry_transport_winhttp.c b/src/transports/sentry_transport_winhttp.c index b72751160..0cbe5d024 100644 --- a/src/transports/sentry_transport_winhttp.c +++ b/src/transports/sentry_transport_winhttp.c @@ -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; @@ -51,10 +53,47 @@ 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 TODO replace with `sentry__url_parse` +void +set_proxy_credentials(winhttp_bgworker_state_t *state, const char *proxy) +{ + const char *at_sign = strchr(proxy, '@'); + if (at_sign) { + const char *credentials = proxy + 7; // Skip "http://" + char *colon = strchr(credentials, ':'); + if (colon && colon < at_sign) { + size_t user_len = colon - credentials; + size_t pass_len = at_sign - colon - 1; + char *user = (char *)malloc(user_len + 1); + char *pass = (char *)malloc(pass_len + 1); + strncpy(user, credentials, user_len); + user[user_len] = '\0'; + strncpy(pass, colon + 1, pass_len); + pass[pass_len] = '\0'; + + // Convert user and pass to LPCWSTR + int user_wlen = MultiByteToWideChar(CP_UTF8, 0, user, -1, NULL, 0); + int pass_wlen = MultiByteToWideChar(CP_UTF8, 0, pass, -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, user, -1, user_w, user_wlen); + MultiByteToWideChar(CP_UTF8, 0, pass, -1, pass_w, pass_wlen); + + state->proxy_username = user_w; + state->proxy_password = pass_w; + + sentry_free(user); + sentry_free(pass); + } + } +} + static int sentry__winhttp_transport_start( const sentry_options_t *opts, void *transport_state) @@ -71,7 +110,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; + 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); @@ -103,6 +147,7 @@ sentry__winhttp_transport_start( SENTRY_WARN("`WinHttpOpen` failed"); return 1; } + return sentry__bgworker_start(bgworker); } @@ -209,6 +254,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); diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index 58af7a839..e146ff550 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -625,7 +625,8 @@ 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"])]) +def test_capture_proxy(cmake, httpserver, run_args, proxy_status, proxy_auth): if not shutil.which("mitmdump"): pytest.skip("mitmdump is not installed") @@ -635,12 +636,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") @@ -651,12 +658,14 @@ def test_capture_proxy(cmake, httpserver, run_args, proxy_status): shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True) httpserver.expect_request("/api/123456/envelope/").respond_with_data("OK") - + current_run_arg = run_args[0] + if proxy_auth == ["on"]: + current_run_arg += "-auth" 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)), )