[go: up one dir, main page]

worker/
cf.rs

1#[cfg(feature = "timezone")]
2use crate::Result;
3
4use serde::de::DeserializeOwned;
5use wasm_bindgen::JsCast;
6
7/// In addition to the methods on the `Request` struct, the `Cf` struct on an inbound Request contains information about the request provided by Cloudflare’s edge.
8///
9/// [Details](https://developers.cloudflare.com/workers/runtime-apis/request#incomingrequestcfproperties)
10#[derive(Debug, Clone)]
11pub struct Cf {
12    inner: worker_sys::IncomingRequestCfProperties,
13}
14
15unsafe impl Send for Cf {}
16unsafe impl Sync for Cf {}
17
18impl Cf {
19    #[cfg(feature = "http")]
20    pub(crate) fn new(inner: worker_sys::IncomingRequestCfProperties) -> Self {
21        Self { inner }
22    }
23
24    #[cfg(feature = "http")]
25    pub(crate) fn inner(&self) -> &worker_sys::IncomingRequestCfProperties {
26        &self.inner
27    }
28
29    /// The three-letter airport code (e.g. `ATX`, `LUX`) representing
30    /// the colocation which processed the request
31    pub fn colo(&self) -> String {
32        self.inner.colo().unwrap()
33    }
34
35    /// The Autonomous System Number (ASN) of the request, e.g. `395747`
36    pub fn asn(&self) -> Option<u32> {
37        self.inner.asn().unwrap()
38    }
39
40    /// The Autonomous System organization name of the request, e.g. `Cloudflare, Inc.`
41    pub fn as_organization(&self) -> Option<String> {
42        self.inner.as_organization().unwrap()
43    }
44
45    /// The two-letter country code of origin for the request.
46    /// This is the same value as that provided in the CF-IPCountry header, e.g.  `"US"`
47    pub fn country(&self) -> Option<String> {
48        self.inner.country().unwrap()
49    }
50
51    /// The HTTP Protocol (e.g. "HTTP/2") used by the request
52    pub fn http_protocol(&self) -> String {
53        self.inner.http_protocol().unwrap()
54    }
55
56    /// The browser-requested prioritization information in the request object,
57    ///
58    /// See [this blog post](https://blog.cloudflare.com/better-http-2-prioritization-for-a-faster-web/#customizingprioritizationwithworkers) for details.
59    pub fn request_priority(&self) -> Option<RequestPriority> {
60        if let Some(priority) = self.inner.request_priority().unwrap() {
61            let mut weight = 1;
62            let mut exclusive = false;
63            let mut group = 0;
64            let mut group_weight = 0;
65
66            priority
67                .as_str()
68                .split(';')
69                .map(|key_value_pair| {
70                    let mut iter = key_value_pair.split('=');
71
72                    // this pair is guaranteed to have 2 elements
73                    let key = iter.next().unwrap(); // first element
74                    let value = iter.next().unwrap(); // second element
75
76                    (key, value)
77                })
78                .for_each(|(key, value)| match key {
79                    "weight" => weight = value.parse().unwrap(),
80                    "exclusive" => exclusive = value == "1",
81                    "group" => group = value.parse().unwrap(),
82                    "group-weight" => group_weight = value.parse().unwrap(),
83                    _ => unreachable!(),
84                });
85
86            Some(RequestPriority {
87                weight,
88                exclusive,
89                group,
90                group_weight,
91            })
92        } else {
93            None
94        }
95    }
96
97    /// The cipher for the connection to Cloudflare, e.g. "AEAD-AES128-GCM-SHA256".
98    pub fn tls_cipher(&self) -> String {
99        self.inner.tls_cipher().unwrap()
100    }
101
102    /// Information about the client's authorization.
103    /// Only set when using Cloudflare Access or API Shield.
104    pub fn tls_client_auth(&self) -> Option<TlsClientAuth> {
105        self.inner.tls_client_auth().unwrap().map(Into::into)
106    }
107
108    /// The TLS version of the connection to Cloudflare, e.g. TLSv1.3.
109    pub fn tls_version(&self) -> String {
110        // TODO: should this be strongly typed? with ordering, etc.?
111        self.inner.tls_version().unwrap()
112    }
113
114    /// City of the incoming request, e.g. "Austin".
115    pub fn city(&self) -> Option<String> {
116        self.inner.city().unwrap()
117    }
118
119    /// Continent of the incoming request, e.g. "NA"
120    pub fn continent(&self) -> Option<String> {
121        self.inner.continent().unwrap()
122    }
123
124    /// Latitude and longitude of the incoming request, e.g. (30.27130, -97.74260)
125    pub fn coordinates(&self) -> Option<(f32, f32)> {
126        let lat_opt = self.inner.latitude().unwrap();
127        let lon_opt = self.inner.longitude().unwrap();
128        match (lat_opt, lon_opt) {
129            (Some(lat_str), Some(lon_str)) => {
130                // SAFETY: i think this is fine..?
131                let lat = lat_str.parse().unwrap();
132                let lon = lon_str.parse().unwrap();
133                Some((lat, lon))
134            }
135            _ => None,
136        }
137    }
138
139    /// Postal code of the incoming request, e.g. "78701"
140    pub fn postal_code(&self) -> Option<String> {
141        self.inner.postal_code().unwrap()
142    }
143
144    /// Metro code (DMA) of the incoming request, e.g. "635"
145    pub fn metro_code(&self) -> Option<String> {
146        self.inner.metro_code().unwrap()
147    }
148
149    /// If known, the [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) name for the first level region associated with the IP address of the incoming request, e.g. "Texas".
150    pub fn region(&self) -> Option<String> {
151        self.inner.region().unwrap()
152    }
153
154    /// If known, the [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) code for the first level region associated with the IP address of the incoming request, e.g. "TX".
155    pub fn region_code(&self) -> Option<String> {
156        self.inner.region_code().unwrap()
157    }
158
159    /// **Requires** `timezone` feature. Timezone of the incoming request
160    #[cfg(feature = "timezone")]
161    pub fn timezone(&self) -> Result<impl chrono::TimeZone> {
162        let tz = self.inner.timezone()?;
163        Ok(tz.parse::<chrono_tz::Tz>()?)
164    }
165
166    /// Timezone name of the incoming request
167    pub fn timezone_name(&self) -> String {
168        self.inner.timezone().unwrap()
169    }
170
171    /// Whether the country of the incoming request is in the EU
172    pub fn is_eu_country(&self) -> bool {
173        self.inner.is_eu_country().unwrap() == Some("1".to_string())
174    }
175
176    pub fn host_metadata<T: serde::de::DeserializeOwned>(&self) -> crate::Result<Option<T>> {
177        let host_metadata = self.inner.host_metadata()?;
178        if host_metadata.is_undefined() {
179            Ok(None)
180        } else {
181            serde_wasm_bindgen::from_value(host_metadata)
182                .map(Some)
183                .map_err(|e| wasm_bindgen::JsValue::from(e.to_string()))
184        }
185        .map_err(crate::Error::from)
186    }
187}
188
189/// Browser-requested prioritization information.
190#[derive(Debug, Clone, Copy)]
191pub struct RequestPriority {
192    /// The browser-requested weight for the HTTP/2 prioritization
193    pub weight: usize,
194
195    /// The browser-requested HTTP/2 exclusive flag (true for Chromium-based browsers, false for others).
196    pub exclusive: bool,
197
198    /// HTTP/2 stream ID for the request group (only non-zero for Firefox)
199    pub group: usize,
200
201    /// HTTP/2 weight for the request group (only non-zero for Firefox)
202    pub group_weight: usize,
203}
204
205impl From<worker_sys::IncomingRequestCfProperties> for Cf {
206    fn from(inner: worker_sys::IncomingRequestCfProperties) -> Self {
207        Self { inner }
208    }
209}
210
211/// Only set when using Cloudflare Access or API Shield
212#[derive(Debug)]
213pub struct TlsClientAuth {
214    inner: worker_sys::TlsClientAuth,
215}
216
217impl TlsClientAuth {
218    pub fn cert_issuer_dn_legacy(&self) -> String {
219        self.inner.cert_issuer_dn_legacy().unwrap()
220    }
221
222    pub fn cert_issuer_dn(&self) -> String {
223        self.inner.cert_issuer_dn().unwrap()
224    }
225
226    pub fn cert_issuer_dn_rfc2253(&self) -> String {
227        self.inner.cert_issuer_dn_rfc2253().unwrap()
228    }
229
230    pub fn cert_subject_dn_legacy(&self) -> String {
231        self.inner.cert_subject_dn_legacy().unwrap()
232    }
233
234    pub fn cert_verified(&self) -> String {
235        self.inner.cert_verified().unwrap()
236    }
237
238    pub fn cert_not_after(&self) -> String {
239        self.inner.cert_not_after().unwrap()
240    }
241
242    pub fn cert_subject_dn(&self) -> String {
243        self.inner.cert_subject_dn().unwrap()
244    }
245
246    pub fn cert_fingerprint_sha1(&self) -> String {
247        self.inner.cert_fingerprint_sha1().unwrap()
248    }
249
250    pub fn cert_fingerprint_sha256(&self) -> String {
251        self.inner.cert_fingerprint_sha256().unwrap()
252    }
253
254    pub fn cert_not_before(&self) -> String {
255        self.inner.cert_not_before().unwrap()
256    }
257
258    pub fn cert_serial(&self) -> String {
259        self.inner.cert_serial().unwrap()
260    }
261
262    pub fn cert_presented(&self) -> String {
263        self.inner.cert_presented().unwrap()
264    }
265
266    pub fn cert_subject_dn_rfc2253(&self) -> String {
267        self.inner.cert_subject_dn_rfc2253().unwrap()
268    }
269}
270
271impl From<worker_sys::TlsClientAuth> for TlsClientAuth {
272    fn from(inner: worker_sys::TlsClientAuth) -> Self {
273        Self { inner }
274    }
275}
276
277#[derive(Clone, Debug)]
278pub struct CfResponseProperties(pub(crate) js_sys::Object);
279
280impl CfResponseProperties {
281    pub fn into_raw(self) -> js_sys::Object {
282        self.0
283    }
284
285    pub fn try_into<T: DeserializeOwned>(self) -> crate::Result<T> {
286        Ok(serde_wasm_bindgen::from_value(self.0.unchecked_into())?)
287    }
288}
289
290unsafe impl Send for CfResponseProperties {}
291unsafe impl Sync for CfResponseProperties {}