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        };
91        let rdap = domain.to_response();
92
93        // and GIVEN httpdata with content type that is not RDAP media type
94        let http_data = HttpData::example().content_type(JSON_MEDIA_TYPE).build();
95
96        // WHEN checks are run
97        let checks = http_data.get_checks(None, CheckParams::for_rdap(&rdap));
98
99        // THEN incorrect media type check is found
100        assert!(checks
101            .items
102            .iter()
103            .any(|c| c.check == Check::ContentTypeIsNotRdap));
104    }
105
106    #[test]
107    fn check_exactly_rdap_media() {
108        // GIVEN an rdap response
109        let domain = Domain {
110            common: Common::level0()
111                .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension())
112                .build(),
113            object_common: ObjectCommon::domain().build(),
114            ldh_name: Some("foo.example".to_string()),
115            unicode_name: None,
116            variants: None,
117            secure_dns: None,
118            nameservers: None,
119            public_ids: None,
120            network: None,
121        };
122        let rdap = domain.to_response();
123
124        // and GIVEN httpdata with content type that is not RDAP media type
125        let http_data = HttpData::example().content_type(RDAP_MEDIA_TYPE).build();
126
127        // WHEN checks are run
128        let checks = http_data.get_checks(None, CheckParams::for_rdap(&rdap));
129
130        // THEN incorrect media type check is not found
131        assert!(!checks
132            .items
133            .iter()
134            .any(|c| c.check == Check::ContentTypeIsNotRdap));
135    }
136
137    #[test]
138    fn check_rdap_media_with_charset_parameter() {
139        // GIVEN an rdap response
140        let domain = Domain {
141            common: Common::level0()
142                .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension())
143                .build(),
144            object_common: ObjectCommon::domain().build(),
145            ldh_name: Some("foo.example".to_string()),
146            unicode_name: None,
147            variants: None,
148            secure_dns: None,
149            nameservers: None,
150            public_ids: None,
151            network: None,
152        };
153        let rdap = domain.to_response();
154
155        // and GIVEN httpdata with content type that is not RDAP media type with charset parameter
156        let mt = format!("{RDAP_MEDIA_TYPE};charset=UTF-8");
157        let http_data = HttpData::example().content_type(mt).build();
158
159        // WHEN checks are run
160        let checks = http_data.get_checks(None, CheckParams::for_rdap(&rdap));
161
162        // THEN incorrect media type check is not found
163        assert!(!checks
164            .items
165            .iter()
166            .any(|c| c.check == Check::ContentTypeIsNotRdap));
167    }
168
169    #[test]
170    fn check_media_type_absent() {
171        // GIVEN an rdap response
172        let domain = Domain {
173            common: Common::level0()
174                .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension())
175                .build(),
176            object_common: ObjectCommon::domain().build(),
177            ldh_name: Some("foo.example".to_string()),
178            unicode_name: None,
179            variants: None,
180            secure_dns: None,
181            nameservers: None,
182            public_ids: None,
183            network: None,
184        };
185        let rdap = domain.to_response();
186
187        // and GIVEN httpdata no content type
188        let http_data = HttpData::example().build();
189
190        // WHEN checks are run
191        let checks = http_data.get_checks(None, CheckParams::for_rdap(&rdap));
192
193        // THEN no media type check is found
194        assert!(checks
195            .items
196            .iter()
197            .any(|c| c.check == Check::ContentTypeIsAbsent));
198    }
199
200    #[test]
201    fn check_cors_header_with_tig() {
202        // GIVEN a response with gtld tig
203        let domain = Domain {
204            common: Common::level0()
205                .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension())
206                .build(),
207            object_common: ObjectCommon::domain().build(),
208            ldh_name: Some("foo.example".to_string()),
209            unicode_name: None,
210            variants: None,
211            secure_dns: None,
212            nameservers: None,
213            public_ids: None,
214            network: None,
215        };
216        let rdap = domain.to_response();
217
218        // and GIVEN a cors header with *
219        let http_data = HttpData::example().access_control_allow_origin("*").build();
220
221        // WHEN running checks
222        let checks = http_data.get_checks(None, CheckParams::for_rdap(&rdap));
223
224        // THEN no check is given
225        assert!(!checks
226            .items
227            .iter()
228            .any(|c| c.check == Check::AllowOriginNotStar));
229    }
230
231    #[test]
232    fn check_cors_header_with_foo_and_tig() {
233        // GIVEN a response with gtld tig extension
234        let domain = Domain {
235            common: Common::level0()
236                .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension())
237                .build(),
238            object_common: ObjectCommon::domain().build(),
239            ldh_name: Some("foo.example".to_string()),
240            unicode_name: None,
241            variants: None,
242            secure_dns: None,
243            nameservers: None,
244            public_ids: None,
245            network: None,
246        };
247        let rdap = domain.to_response();
248
249        // and GIVEN response with cors header of "foo" (not "*")
250        let http_data = HttpData::example()
251            .access_control_allow_origin("foo")
252            .build();
253
254        // WHEN running checks
255        let checks = http_data.get_checks(None, CheckParams::for_rdap(&rdap));
256
257        // THEN the check is found
258        assert!(checks
259            .items
260            .iter()
261            .any(|c| c.check == Check::AllowOriginNotStar));
262    }
263
264    #[test]
265    fn check_no_cors_header_and_tig() {
266        // GIVEN domain response with gtld tig extension id
267        let domain = Domain {
268            common: Common::level0()
269                .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension())
270                .build(),
271            object_common: ObjectCommon::domain().build(),
272            ldh_name: Some("foo.example".to_string()),
273            unicode_name: None,
274            variants: None,
275            secure_dns: None,
276            nameservers: None,
277            public_ids: None,
278            network: None,
279        };
280        let rdap = domain.to_response();
281
282        // and GIVEN a response with no cors header
283        let http_data = HttpData::example().build();
284
285        // WHEN running checks
286        let checks = http_data.get_checks(None, CheckParams::for_rdap(&rdap));
287
288        // THEN check for missing cors is found
289        dbg!(&checks);
290        assert!(checks
291            .items
292            .iter()
293            .any(|c| c.check == Check::AllowOriginNotStar));
294    }
295
296    #[test]
297    fn given_response_is_over_https_and_tig() {
298        // GIVEN response with gtld tig extension
299        let domain = Domain {
300            common: Common::level0()
301                .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension())
302                .build(),
303            object_common: ObjectCommon::domain().build(),
304            ldh_name: Some("foo.example".to_string()),
305            unicode_name: None,
306            variants: None,
307            secure_dns: None,
308            nameservers: None,
309            public_ids: None,
310            network: None,
311        };
312        let rdap = domain.to_response();
313
314        // and GIVEN response is over https
315        let http_data = HttpData::now().scheme("https").host("example.com").build();
316
317        // WHEN running checks
318        let checks = http_data.get_checks(None, CheckParams::for_rdap(&rdap));
319
320        // THEN then check for must use https is not present
321        assert!(!checks.items.iter().any(|c| c.check == Check::MustUseHttps));
322    }
323
324    #[test]
325    fn response_over_htttp_and_tig() {
326        // GIVEN domain response with gtld tig extension
327        let domain = Domain {
328            common: Common::level0()
329                .extension(ExtensionId::IcannRdapTechnicalImplementationGuide0.to_extension())
330                .build(),
331            object_common: ObjectCommon::domain().build(),
332            ldh_name: Some("foo.example".to_string()),
333            unicode_name: None,
334            variants: None,
335            secure_dns: None,
336            nameservers: None,
337            public_ids: None,
338            network: None,
339        };
340        let rdap = domain.to_response();
341
342        // and GIVEN response is with http (not https)
343        let http_data = HttpData::now().scheme("http").host("example.com").build();
344
345        // WHEN running checks
346        let checks = http_data.get_checks(None, CheckParams::for_rdap(&rdap));
347
348        // THEN check for must use https is found
349        assert!(checks.items.iter().any(|c| c.check == Check::MustUseHttps));
350    }
351}