Skip to content

Commit f63cff5

Browse files
Don't validate that endpoint is not empty because it doesn't need to be present (#426)
* Don't validate that endpoint is not empty because it doesn't need to be present * Fix parsing of windows pipe urls * implement correct agent host detection from env for telemetry
1 parent 0d5e0d1 commit f63cff5

File tree

7 files changed

+397
-160
lines changed

7 files changed

+397
-160
lines changed

bin_tests/src/bin/crashtracker_bin_test.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,32 @@ mod unix {
2727

2828
pub fn main() -> anyhow::Result<()> {
2929
let mut args = env::args().skip(1);
30-
let output_filename = args.next().context("Unexpected number of arguments")?;
30+
let output_url = args.next().context("Unexpected number of arguments")?;
3131
let receiver_binary = args.next().context("Unexpected number of arguments")?;
3232
let stderr_filename = args.next().context("Unexpected number of arguments")?;
3333
let stdout_filename = args.next().context("Unexpected number of arguments")?;
3434
let timeout = Duration::from_secs(30);
35+
36+
let endpoint = if output_url.is_empty() {
37+
None
38+
} else {
39+
Some(ddcommon::Endpoint {
40+
url: ddcommon::parse_uri(&output_url)?,
41+
api_key: None,
42+
})
43+
};
44+
3545
crashtracker::init(
3646
CrashtrackerConfiguration {
3747
additional_files: vec![],
3848
create_alt_stack: true,
39-
endpoint: Some(ddcommon::Endpoint {
40-
url: ddcommon::parse_uri(&format!("file://{}", output_filename))?,
41-
api_key: None,
42-
}),
4349
resolve_frames: crashtracker::StacktraceCollection::WithoutSymbols,
50+
endpoint,
4451
timeout,
4552
},
4653
Some(CrashtrackerReceiverConfig::new(
4754
vec![],
48-
vec![],
55+
env::vars().collect(),
4956
receiver_binary,
5057
Some(stderr_filename),
5158
Some(stdout_filename),

bin_tests/tests/crashtracker_bin_test.rs

Lines changed: 103 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
#![cfg(unix)]
55

6+
use std::collections::HashMap;
7+
use std::io::{Read, Write};
68
use std::path::Path;
79
use std::process;
810
use std::{fs, path::PathBuf};
@@ -23,32 +25,15 @@ fn test_crash_tracking_bin_release() {
2325
}
2426

2527
fn test_crash_tracking_bin(crash_tracking_receiver_profile: BuildProfile) {
26-
let crashtracker_bin = ArtifactsBuild {
27-
name: "crashtracker_bin_test".to_owned(),
28-
build_profile: crash_tracking_receiver_profile,
29-
artifact_type: ArtifactType::Bin,
30-
triple_target: None,
31-
};
32-
let crashtracker_receiver = ArtifactsBuild {
33-
name: "crashtracker_receiver".to_owned(),
34-
build_profile: crash_tracking_receiver_profile,
35-
artifact_type: ArtifactType::Bin,
36-
triple_target: None,
37-
};
38-
let artifacts = build_artifacts(&[&crashtracker_receiver, &crashtracker_bin]).unwrap();
39-
40-
let tmpdir = tempfile::TempDir::new().unwrap();
41-
42-
let crash_profile_path = extend_path(tmpdir.path(), "crash");
43-
let crash_telemetry_path = extend_path(tmpdir.path(), "crash.telemetry");
44-
let stdout_path = extend_path(tmpdir.path(), "out.stdout");
45-
let stderr_path = extend_path(tmpdir.path(), "out.stderr");
28+
let (crashtracker_bin, crashtracker_receiver) =
29+
setup_crashtracking_crates(crash_tracking_receiver_profile);
30+
let fixtures = setup_test_fixtures(&[&crashtracker_receiver, &crashtracker_bin]);
4631

47-
let mut p = process::Command::new(&artifacts[&crashtracker_bin])
48-
.arg(&crash_profile_path)
49-
.arg(artifacts[&crashtracker_receiver].as_os_str())
50-
.arg(&stderr_path)
51-
.arg(&stdout_path)
32+
let mut p = process::Command::new(&fixtures.artifacts[&crashtracker_bin])
33+
.arg(format!("file://{}", fixtures.crash_profile_path.display()))
34+
.arg(fixtures.artifacts[&crashtracker_receiver].as_os_str())
35+
.arg(&fixtures.stderr_path)
36+
.arg(&fixtures.stdout_path)
5237
.spawn()
5338
.unwrap();
5439
let exit_status = bin_tests::timeit!("exit after signal", {
@@ -61,10 +46,10 @@ fn test_crash_tracking_bin(crash_tracking_receiver_profile: BuildProfile) {
6146
// running before the receiver has a chance to send the report.
6247
std::thread::sleep(std::time::Duration::from_millis(100));
6348

64-
let stderr = fs::read(stderr_path)
49+
let stderr = fs::read(fixtures.stderr_path)
6550
.context("reading crashtracker stderr")
6651
.unwrap();
67-
let stdout = fs::read(stdout_path)
52+
let stdout = fs::read(fixtures.stdout_path)
6853
.context("reading crashtracker stdout")
6954
.unwrap();
7055
assert!(matches!(
@@ -74,7 +59,7 @@ fn test_crash_tracking_bin(crash_tracking_receiver_profile: BuildProfile) {
7459
assert_eq!(Ok(""), String::from_utf8(stdout).as_deref());
7560

7661
// Check the crash data
77-
let crash_profile = fs::read(crash_profile_path)
62+
let crash_profile = fs::read(fixtures.crash_profile_path)
7863
.context("reading crashtracker profiling payload")
7964
.unwrap();
8065
let crash_payload = serde_json::from_slice::<serde_json::Value>(&crash_profile)
@@ -97,12 +82,17 @@ fn test_crash_tracking_bin(crash_tracking_receiver_profile: BuildProfile) {
9782
crash_payload["siginfo"]
9883
);
9984

100-
let crash_telemetry = fs::read(crash_telemetry_path)
85+
let crash_telemetry = fs::read(fixtures.crash_telemetry_path)
10186
.context("reading crashtracker telemetry payload")
10287
.unwrap();
103-
let telemetry_payload = serde_json::from_slice::<serde_json::Value>(&crash_telemetry)
104-
.context("deserializing crashtracker telemetry payload to json")
105-
.unwrap();
88+
assert_telemetry_message(&crash_telemetry);
89+
}
90+
91+
fn assert_telemetry_message(crash_telemetry: &[u8]) {
92+
let telemetry_payload: serde_json::Value =
93+
serde_json::from_slice::<serde_json::Value>(crash_telemetry)
94+
.context("deserializing crashtracker telemetry payload to json")
95+
.unwrap();
10696
assert_eq!(telemetry_payload["request_type"], "logs");
10797
assert_eq!(
10898
serde_json::json!({
@@ -136,6 +126,87 @@ fn test_crash_tracking_bin(crash_tracking_receiver_profile: BuildProfile) {
136126
assert_eq!(telemetry_payload["payload"][0]["is_sensitive"], true);
137127
}
138128

129+
#[test]
130+
#[cfg_attr(miri, ignore)]
131+
#[cfg(unix)]
132+
fn crash_tracking_empty_endpoint() {
133+
use std::os::unix::net::UnixListener;
134+
135+
let (crashtracker_bin, crashtracker_receiver) = setup_crashtracking_crates(BuildProfile::Debug);
136+
let fixtures = setup_test_fixtures(&[&crashtracker_receiver, &crashtracker_bin]);
137+
138+
let socket_path = extend_path(fixtures.tmpdir.path(), "trace_agent.socket");
139+
let listener = UnixListener::bind(&socket_path).unwrap();
140+
141+
process::Command::new(&fixtures.artifacts[&crashtracker_bin])
142+
// empty url, endpoint will be set to none
143+
.arg("")
144+
.arg(fixtures.artifacts[&crashtracker_receiver].as_os_str())
145+
.arg(&fixtures.stderr_path)
146+
.arg(&fixtures.stdout_path)
147+
.env(
148+
"DD_TRACE_AGENT_URL",
149+
format!("unix://{}", socket_path.display()),
150+
)
151+
.spawn()
152+
.unwrap();
153+
154+
let (mut stream, _) = listener.accept().unwrap();
155+
let mut out = vec![0; 65536];
156+
let read = stream.read(&mut out).unwrap();
157+
158+
stream
159+
.write_all(b"HTTP/1.1 404\r\nContent-Length: 0\r\n\r\n")
160+
.unwrap();
161+
let resp = String::from_utf8_lossy(&out[..read]);
162+
let pos = resp.find("\r\n\r\n").unwrap();
163+
let body = &resp[pos + 4..];
164+
assert_telemetry_message(body.as_bytes());
165+
}
166+
167+
struct TestFixtures<'a> {
168+
tmpdir: tempfile::TempDir,
169+
crash_profile_path: PathBuf,
170+
crash_telemetry_path: PathBuf,
171+
stdout_path: PathBuf,
172+
stderr_path: PathBuf,
173+
174+
artifacts: HashMap<&'a ArtifactsBuild, PathBuf>,
175+
}
176+
177+
fn setup_test_fixtures<'a>(crates: &[&'a ArtifactsBuild]) -> TestFixtures<'a> {
178+
let artifacts = build_artifacts(crates).unwrap();
179+
180+
let tmpdir = tempfile::TempDir::new().unwrap();
181+
TestFixtures {
182+
crash_profile_path: extend_path(tmpdir.path(), "crash"),
183+
crash_telemetry_path: extend_path(tmpdir.path(), "crash.telemetry"),
184+
stdout_path: extend_path(tmpdir.path(), "out.stdout"),
185+
stderr_path: extend_path(tmpdir.path(), "out.stderr"),
186+
187+
artifacts,
188+
tmpdir,
189+
}
190+
}
191+
192+
fn setup_crashtracking_crates(
193+
crash_tracking_receiver_profile: BuildProfile,
194+
) -> (ArtifactsBuild, ArtifactsBuild) {
195+
let crashtracker_bin = ArtifactsBuild {
196+
name: "crashtracker_bin_test".to_owned(),
197+
build_profile: crash_tracking_receiver_profile,
198+
artifact_type: ArtifactType::Bin,
199+
triple_target: None,
200+
};
201+
let crashtracker_receiver = ArtifactsBuild {
202+
name: "crashtracker_receiver".to_owned(),
203+
build_profile: crash_tracking_receiver_profile,
204+
artifact_type: ArtifactType::Bin,
205+
triple_target: None,
206+
};
207+
(crashtracker_bin, crashtracker_receiver)
208+
}
209+
139210
fn extend_path<T: AsRef<Path>>(parent: &Path, path: T) -> PathBuf {
140211
let mut parent = parent.to_path_buf();
141212
parent.push(path);

crashtracker/src/telemetry.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ impl TelemetryCrashUploader {
7070

7171
// ignore result because what are we going to do?
7272
let _ = if endpoint.url.scheme_str() == Some("file") {
73-
cfg.set_url(&format!("file://{}.telemetry", endpoint.url.path()))
73+
cfg.set_host_from_url(&format!("file://{}.telemetry", endpoint.url.path()))
7474
} else {
7575
cfg.set_endpoint(endpoint.clone())
7676
};
@@ -261,7 +261,7 @@ mod tests {
261261
let mut t = new_test_uploader();
262262

263263
t.cfg
264-
.set_url(&format!("file://{}", output_filename.to_str().unwrap()))
264+
.set_host_from_url(&format!("file://{}", output_filename.to_str().unwrap()))
265265
.unwrap();
266266

267267
let mut counters = HashMap::new();

ddcommon/src/connector/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use std::task::{Context, Poll};
1414
#[cfg(unix)]
1515
pub mod uds;
1616

17-
#[cfg(windows)]
1817
pub mod named_pipe;
1918

2019
pub mod errors;

ddcommon/src/lib.rs

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -79,48 +79,45 @@ where
7979
builder.build().map_err(Error::custom)
8080
}
8181

82-
// TODO: we should properly handle malformed urls
83-
// * For windows and unix schemes:
84-
// * For compatibility reasons with existing implementation this parser stores the encoded path
85-
// in authority section as there is no existing standard
86-
// [see](https://github.com/whatwg/url/issues/577) that covers this. We need to pick one hack
87-
// or another
88-
// * For file scheme implementation will simply backfill missing authority section
82+
/// TODO: we should properly handle malformed urls
83+
/// * For windows and unix schemes:
84+
/// * For compatibility reasons with existing implementation this parser stores the encoded path
85+
/// in authority section as there is no existing standard [see](https://github.com/whatwg/url/issues/577)
86+
/// that covers this. We need to pick one hack or another
87+
/// * For windows, interprets everything after windows: as path
88+
/// * For unix, interprets everything after unix:// as path
89+
/// * For file scheme implementation will simply backfill missing authority section
8990
pub fn parse_uri(uri: &str) -> anyhow::Result<hyper::Uri> {
90-
let scheme_pos = if let Some(scheme_pos) = uri.find("://") {
91-
scheme_pos
91+
if let Some(path) = uri.strip_prefix("unix://") {
92+
encode_uri_path_in_authority("unix", path)
93+
} else if let Some(path) = uri.strip_prefix("windows:") {
94+
encode_uri_path_in_authority("windows", path)
95+
} else if let Some(path) = uri.strip_prefix("file://") {
96+
let mut parts = uri::Parts::default();
97+
parts.scheme = uri::Scheme::from_str("file").ok();
98+
parts.authority = Some(uri::Authority::from_static("localhost"));
99+
100+
// TODO: handle edge cases like improperly escaped url strings
101+
//
102+
// this is eventually user configurable field
103+
// anything we can do to ensure invalid input becomes valid - will improve usability
104+
parts.path_and_query = uri::PathAndQuery::from_str(path).ok();
105+
106+
Ok(hyper::Uri::from_parts(parts)?)
92107
} else {
93-
return Ok(hyper::Uri::from_str(uri)?);
94-
};
108+
Ok(hyper::Uri::from_str(uri)?)
109+
}
110+
}
95111

96-
let scheme = &uri[0..scheme_pos];
97-
let rest = &uri[scheme_pos + 3..];
98-
match scheme {
99-
"windows" | "unix" => {
100-
let mut parts = uri::Parts::default();
101-
parts.scheme = uri::Scheme::from_str(scheme).ok();
112+
fn encode_uri_path_in_authority(scheme: &str, path: &str) -> anyhow::Result<hyper::Uri> {
113+
let mut parts = uri::Parts::default();
114+
parts.scheme = uri::Scheme::from_str(scheme).ok();
102115

103-
let path = hex::encode(rest);
116+
let path = hex::encode(path);
104117

105-
parts.authority = uri::Authority::from_str(path.as_str()).ok();
106-
parts.path_and_query = Some(uri::PathAndQuery::from_static(""));
107-
Ok(hyper::Uri::from_parts(parts)?)
108-
}
109-
"file" => {
110-
let mut parts = uri::Parts::default();
111-
parts.scheme = uri::Scheme::from_str(scheme).ok();
112-
parts.authority = Some(uri::Authority::from_static("localhost"));
113-
114-
// TODO: handle edge cases like improperly escaped url strings
115-
//
116-
// this is eventually user configurable field
117-
// anything we can do to ensure invalid input becomes valid - will improve usability
118-
parts.path_and_query = uri::PathAndQuery::from_str(rest).ok();
119-
120-
Ok(hyper::Uri::from_parts(parts)?)
121-
}
122-
_ => Ok(hyper::Uri::from_str(uri)?),
123-
}
118+
parts.authority = uri::Authority::from_str(path.as_str()).ok();
119+
parts.path_and_query = Some(uri::PathAndQuery::from_static(""));
120+
Ok(hyper::Uri::from_parts(parts)?)
124121
}
125122

126123
impl Endpoint {

0 commit comments

Comments
 (0)