sentry/transports/
curl.rs

1use 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/// A [`Transport`] that sends events via the [`curl`] library.
11///
12/// This is enabled by the `curl` feature flag.
13#[cfg_attr(doc_cfg, doc(cfg(feature = "curl")))]
14pub struct CurlHttpTransport {
15    thread: TransportThread,
16}
17
18impl CurlHttpTransport {
19    /// Creates a new Transport.
20    pub fn new(options: &ClientOptions) -> Self {
21        Self::new_internal(options, None)
22    }
23
24    /// Creates a new Transport that uses the specified [`CurlClient`].
25    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}