icann_rdap_common/check/
httpdata.rs

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