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