sentry/transports/
curl.rs1use std::io::{Cursor, Read};
2use std::time::Duration;
3
4use curl::easy::Easy as CurlClient;
5
6use super::thread::TransportThread;
7
8use crate::{sentry_debug, types::Scheme, ClientOptions, Envelope, Transport};
9
10#[cfg_attr(doc_cfg, doc(cfg(feature = "curl")))]
14pub struct CurlHttpTransport {
15 thread: TransportThread,
16}
17
18impl CurlHttpTransport {
19 pub fn new(options: &ClientOptions) -> Self {
21 Self::new_internal(options, None)
22 }
23
24 pub fn with_client(options: &ClientOptions, client: CurlClient) -> Self {
26 Self::new_internal(options, Some(client))
27 }
28
29 fn new_internal(options: &ClientOptions, client: Option<CurlClient>) -> Self {
30 let client = client.unwrap_or_else(CurlClient::new);
31 let http_proxy = options.http_proxy.as_ref().map(ToString::to_string);
32 let https_proxy = options.https_proxy.as_ref().map(ToString::to_string);
33 let dsn = options.dsn.as_ref().unwrap();
34 let user_agent = options.user_agent.clone();
35 let auth = dsn.to_auth(Some(&user_agent)).to_string();
36 let url = dsn.envelope_api_url().to_string();
37 let scheme = dsn.scheme();
38 let accept_invalid_certs = options.accept_invalid_certs;
39
40 let mut handle = client;
41 let thread = TransportThread::new(move |envelope, rl| {
42 handle.reset();
43 handle.url(&url).unwrap();
44 handle.custom_request("POST").unwrap();
45
46 if accept_invalid_certs {
47 handle.ssl_verify_host(false).unwrap();
48 handle.ssl_verify_peer(false).unwrap();
49 }
50
51 match (scheme, &http_proxy, &https_proxy) {
52 (Scheme::Https, _, Some(proxy)) => {
53 if let Err(err) = handle.proxy(proxy) {
54 sentry_debug!("invalid proxy: {:?}", err);
55 }
56 }
57 (_, Some(proxy), _) => {
58 if let Err(err) = handle.proxy(proxy) {
59 sentry_debug!("invalid proxy: {:?}", err);
60 }
61 }
62 _ => {}
63 }
64
65 let mut body = Vec::new();
66 envelope.to_writer(&mut body).unwrap();
67 let mut body = Cursor::new(body);
68
69 let mut retry_after = None;
70 let mut sentry_header = None;
71 let mut headers = curl::easy::List::new();
72 headers.append(&format!("X-Sentry-Auth: {auth}")).unwrap();
73 headers.append("Expect:").unwrap();
74 handle.http_headers(headers).unwrap();
75 handle.upload(true).unwrap();
76 handle.in_filesize(body.get_ref().len() as u64).unwrap();
77 handle
78 .read_function(move |buf| Ok(body.read(buf).unwrap_or(0)))
79 .unwrap();
80 handle.verbose(true).unwrap();
81 handle
82 .debug_function(move |info, data| {
83 let prefix = match info {
84 curl::easy::InfoType::HeaderIn => "< ",
85 curl::easy::InfoType::HeaderOut => "> ",
86 curl::easy::InfoType::DataOut => "",
87 _ => return,
88 };
89 sentry_debug!("curl: {}{}", prefix, String::from_utf8_lossy(data).trim());
90 })
91 .unwrap();
92
93 {
94 let mut handle = handle.transfer();
95 let retry_after_setter = &mut retry_after;
96 let sentry_header_setter = &mut sentry_header;
97 handle
98 .header_function(move |data| {
99 if let Ok(data) = std::str::from_utf8(data) {
100 let mut iter = data.split(':');
101 if let Some(key) = iter.next().map(str::to_lowercase) {
102 if key == "retry-after" {
103 *retry_after_setter = iter.next().map(|x| x.trim().to_string());
104 } else if key == "x-sentry-rate-limits" {
105 *sentry_header_setter =
106 iter.next().map(|x| x.trim().to_string());
107 }
108 }
109 }
110 true
111 })
112 .unwrap();
113 handle.perform().ok();
114 }
115
116 match handle.response_code() {
117 Ok(response_code) => {
118 if let Some(sentry_header) = sentry_header {
119 rl.update_from_sentry_header(&sentry_header);
120 } else if let Some(retry_after) = retry_after {
121 rl.update_from_retry_after(&retry_after);
122 } else if response_code == 429 {
123 rl.update_from_429();
124 }
125 }
126 Err(err) => {
127 sentry_debug!("Failed to send envelope: {}", err);
128 }
129 }
130 });
131 Self { thread }
132 }
133}
134
135impl Transport for CurlHttpTransport {
136 fn send_envelope(&self, envelope: Envelope) {
137 self.thread.send(envelope)
138 }
139 fn flush(&self, timeout: Duration) -> bool {
140 self.thread.flush(timeout)
141 }
142
143 fn shutdown(&self, timeout: Duration) -> bool {
144 self.flush(timeout)
145 }
146}