Skip to main content

worker/
cf.rs

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