Skip to main content

icann_rdap_common/check/
httpdata.rs

1use crate::{httpdata::HttpData, media_types::RDAP_MEDIA_TYPE, prelude::ExtensionId};
2
3use super::{Check, Checks, GetChecks};
4
5impl GetChecks for HttpData {
6    fn get_checks(
7        &self,
8        index: Option<usize>,
9        params: crate::check::CheckParams,
10    ) -> crate::check::Checks {
11        let mut items = vec![];
12
13        // RFC checks
14        if let Some(allow_origin) = &self.access_control_allow_origin {
15            if !allow_origin.eq("*") {
16                items.push(Check::CorsAllowOriginStarRecommended.check_item())
17            }
18        } else {
19            items.push(Check::CorsAllowOriginRecommended.check_item())
20        }
21        if self.access_control_allow_credentials.is_some() {
22            items.push(Check::CorsAllowCredentialsNotRecommended.check_item())
23        }
24        if let Some(content_type) = &self.content_type {
25            if !content_type.starts_with(RDAP_MEDIA_TYPE) {
26                items.push(Check::ContentTypeIsNotRdap.check_item());
27            }
28        } else {
29            items.push(Check::ContentTypeIsAbsent.check_item());
30        }
31
32        // checks for Gtld profile
33        if params
34            .root
35            .has_extension_id(ExtensionId::IcannRdapTechnicalImplementationGuide0)
36            || params
37                .root
38                .has_extension_id(ExtensionId::IcannRdapTechnicalImplementationGuide1)
39        {
40            if let Some(scheme) = &self.scheme {
41                if !scheme.eq_ignore_ascii_case("HTTPS") {
42                    items.push(Check::MustUseHttps.check_item());
43                }
44            } else {
45                items.push(Check::MustUseHttps.check_item());
46            }
47            if let Some(allow_origin) = &self.access_control_allow_origin {
48                if !allow_origin.eq("*") {
49                    items.push(Check::AllowOriginNotStar.check_item())
50                }
51            } else {
52                items.push(Check::AllowOriginNotStar.check_item())
53            }
54        }
55
56        Checks {
57            rdap_struct: super::RdapStructure::HttpData,
58            index,
59            items,
60            sub_checks: vec![],
61        }
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use crate::{
68        check::{Check, CheckParams, GetChecks},
69        httpdata::HttpData,
70        media_types::{JSON_MEDIA_TYPE, RDAP_MEDIA_TYPE},
71        prelude::{Common, ExtensionId, ObjectCommon, ToResponse},
72        response::domain::Domain,
73    };
74
75    #[test]
76    fn check_not_rdap_media() {
77        // GIVEN an rdap response
78        let domain = Domain {
79            common: Common::level0()
80                .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension())
81                .build(),
82            object_common: ObjectCommon::domain().build(),
83            ldh_name: Some("foo.example".to_string()),
84            unicode_name: None,
85            variants: None,
86            secure_dns: None,
87            nameservers: None,
88            public_ids: None,
89            network: None,
90            ttl0_data: None,
91        };
92        let rdap = domain.to_response();
93
94        // and GIVEN httpdata with content type that is not RDAP media type
95        let http_data = HttpData::example().content_type(JSON_MEDIA_TYPE).build();
96
97        // WHEN checks are run
98        let checks = http_data.get_checks(None, CheckParams::for_rdap(&rdap));
99
100        // THEN incorrect media type check is found
101        assert!(checks
102            .items
103            .iter()
104            .any(|c| c.check == Check::ContentTypeIsNotRdap));
105    }
106
107    #[test]
108    fn check_exactly_rdap_media() {
109        // GIVEN an rdap response
110        let domain = Domain {
111            common: Common::level0()
112                .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension())
113                .build(),
114            object_common: ObjectCommon::domain().build(),
115            ldh_name: Some("foo.example".to_string()),
116            unicode_name: None,
117            variants: None,
118            secure_dns: None,
119            nameservers: None,
120            public_ids: None,
121            network: None,
122            ttl0_data: None,
123        };
124        let rdap = domain.to_response();
125
126        // and GIVEN httpdata with content type that is not RDAP media type
127        let http_data = HttpData::example().content_type(RDAP_MEDIA_TYPE).build();
128
129        // WHEN checks are run
130        let checks = http_data.get_checks(None, CheckParams::for_rdap(&rdap));
131
132        // THEN incorrect media type check is not found
133        assert!(!checks
134            .items
135            .iter()
136            .any(|c| c.check == Check::ContentTypeIsNotRdap));
137    }
138
139    #[test]
140    fn check_rdap_media_with_charset_parameter() {
141        // GIVEN an rdap response
142        let domain = Domain {
143            common: Common::level0()
144                .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension())
145                .build(),
146            object_common: ObjectCommon::domain().build(),
147            ldh_name: Some("foo.example".to_string()),
148            unicode_name: None,
149            variants: None,
150            secure_dns: None,
151            nameservers: None,
152            public_ids: None,
153            network: None,
154            ttl0_data: None,
155        };
156        let rdap = domain.to_response();
157
158        // and GIVEN httpdata with content type that is not RDAP media type with charset parameter
159        let mt = format!("{RDAP_MEDIA_TYPE};charset=UTF-8");
160        let http_data = HttpData::example().content_type(mt).build();
161
162        // WHEN checks are run
163        let checks = http_data.get_checks(None, CheckParams::for_rdap(&rdap));
164
165        // THEN incorrect media type check is not found
166        assert!(!checks
167            .items
168            .iter()
169            .any(|c| c.check == Check::ContentTypeIsNotRdap));
170    }
171
172    #[test]
173    fn check_media_type_absent() {
174        // GIVEN an rdap response
175        let domain = Domain {
176            common: Common::level0()
177                .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension())
178                .build(),
179            object_common: ObjectCommon::domain().build(),
180            ldh_name: Some("foo.example".to_string()),
181            unicode_name: None,
182            variants: None,
183            secure_dns: None,
184            nameservers: None,
185            public_ids: None,
186            network: None,
187            ttl0_data: None,
188        };
189        let rdap = domain.to_response();
190
191        // and GIVEN httpdata no content type
192        let http_data = HttpData::example().build();
193
194        // WHEN checks are run
195        let checks = http_data.get_checks(None, CheckParams::for_rdap(&rdap));
196
197        // THEN no media type check is found
198        assert!(checks
199            .items
200            .iter()
201            .any(|c| c.check == Check::ContentTypeIsAbsent));
202    }
203
204    #[test]
205    fn check_cors_header_with_tig() {
206        // GIVEN a response with gtld tig
207        let domain = Domain {
208            common: Common::level0()
209                .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension())
210                .build(),
211            object_common: ObjectCommon::domain().build(),
212            ldh_name: Some("foo.example".to_string()),
213            unicode_name: None,
214            variants: None,
215            secure_dns: None,
216            nameservers: None,
217            public_ids: None,
218            network: None,
219            ttl0_data: None,
220        };
221        let rdap = domain.to_response();
222
223        // and GIVEN a cors header with *
224        let http_data = HttpData::example().access_control_allow_origin("*").build();
225
226        // WHEN running checks
227        let checks = http_data.get_checks(None, CheckParams::for_rdap(&rdap));
228
229        // THEN no check is given
230        assert!(!checks
231            .items
232            .iter()
233            .any(|c| c.check == Check::AllowOriginNotStar));
234    }
235
236    #[test]
237    fn check_cors_header_with_foo_and_tig() {
238        // GIVEN a response with gtld tig extension
239        let domain = Domain {
240            common: Common::level0()
241                .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension())
242                .build(),
243            object_common: ObjectCommon::domain().build(),
244            ldh_name: Some("foo.example".to_string()),
245            unicode_name: None,
246            variants: None,
247            secure_dns: None,
248            nameservers: None,
249            public_ids: None,
250            network: None,
251            ttl0_data: None,
252        };
253        let rdap = domain.to_response();
254
255        // and GIVEN response with cors header of "foo" (not "*")
256        let http_data = HttpData::example()
257            .access_control_allow_origin("foo")
258            .build();
259
260        // WHEN running checks
261        let checks = http_data.get_checks(None, CheckParams::for_rdap(&rdap));
262
263        // THEN the check is found
264        assert!(checks
265            .items
266            .iter()
267            .any(|c| c.check == Check::AllowOriginNotStar));
268    }
269
270    #[test]
271    fn check_no_cors_header_and_tig() {
272        // GIVEN domain response with gtld tig extension id
273        let domain = Domain {
274            common: Common::level0()
275                .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension())
276                .build(),
277            object_common: ObjectCommon::domain().build(),
278            ldh_name: Some("foo.example".to_string()),
279            unicode_name: None,
280            variants: None,
281            secure_dns: None,
282            nameservers: None,
283            public_ids: None,
284            network: None,
285            ttl0_data: None,
286        };
287        let rdap = domain.to_response();
288
289        // and GIVEN a response with no cors header
290        let http_data = HttpData::example().build();
291
292        // WHEN running checks
293        let checks = http_data.get_checks(None, CheckParams::for_rdap(&rdap));
294
295        // THEN check for missing cors is found
296        dbg!(&checks);
297        assert!(checks
298            .items
299            .iter()
300            .any(|c| c.check == Check::AllowOriginNotStar));
301    }
302
303    #[test]
304    fn given_response_is_over_https_and_tig() {
305        // GIVEN response with gtld tig extension
306        let domain = Domain {
307            common: Common::level0()
308                .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension())
309                .build(),
310            object_common: ObjectCommon::domain().build(),
311            ldh_name: Some("foo.example".to_string()),
312            unicode_name: None,
313            variants: None,
314            secure_dns: None,
315            nameservers: None,
316            public_ids: None,
317            network: None,
318            ttl0_data: None,
319        };
320        let rdap = domain.to_response();
321
322        // and GIVEN response is over https
323        let http_data = HttpData::now().scheme("https").host("example.com").build();
324
325        // WHEN running checks
326        let checks = http_data.get_checks(None, CheckParams::for_rdap(&rdap));
327
328        // THEN then check for must use https is not present
329        assert!(!checks.items.iter().any(|c| c.check == Check::MustUseHttps));
330    }
331
332    #[test]
333    fn response_over_http_and_tig() {
334        // GIVEN domain response with gtld tig extension
335        let domain = Domain {
336            common: Common::level0()
337                .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension())
338                .build(),
339            object_common: ObjectCommon::domain().build(),
340            ldh_name: Some("foo.example".to_string()),
341            unicode_name: None,
342            variants: None,
343            secure_dns: None,
344            nameservers: None,
345            public_ids: None,
346            network: None,
347            ttl0_data: None,
348        };
349        let rdap = domain.to_response();
350
351        // and GIVEN response is with http (not https)
352        let http_data = HttpData::now().scheme("http").host("example.com").build();
353
354        // WHEN running checks
355        let checks = http_data.get_checks(None, CheckParams::for_rdap(&rdap));
356
357        // THEN check for must use https is found
358        assert!(checks.items.iter().any(|c| c.check == Check::MustUseHttps));
359    }
360}