Skip to main content

rustack_cloudfront_http/
request.rs

1//! Parse request XML bodies into domain types.
2
3use rustack_cloudfront_model::{
4    CacheBehavior, CachePolicyConfig, CachePolicyCookiesConfig, CachePolicyHeadersConfig,
5    CachePolicyQueryStringsConfig, CloudFrontError, CloudFrontOriginAccessIdentityConfig,
6    CookiePreference, CustomErrorResponse, CustomHeader, CustomOriginConfig, DistributionConfig,
7    FieldLevelEncryptionConfig, FieldLevelEncryptionProfileConfig, ForwardedValues,
8    FunctionAssociation, FunctionConfig, GeoRestriction, InvalidationBatch, KeyGroupConfig,
9    LambdaFunctionAssociation, LoggingConfig, Origin, OriginAccessControlConfig, OriginGroup,
10    OriginRequestPolicyConfig, OriginRequestPolicyCookiesConfig, OriginRequestPolicyHeadersConfig,
11    OriginRequestPolicyQueryStringsConfig, OriginShield, ParamsInCacheKey, PublicKeyConfig,
12    RealtimeLogConfig, ResponseHeadersPolicyConfig, Restrictions, S3OriginConfig, Tag, TagSet,
13    ViewerCertificate,
14};
15
16use crate::xml::de::{Node, parse};
17
18/// Parse the incoming body into the root node.
19///
20/// # Errors
21/// Returns `MalformedInput` on XML errors.
22pub fn parse_root(body: &[u8]) -> Result<Node, CloudFrontError> {
23    parse(body).map_err(CloudFrontError::MalformedInput)
24}
25
26/// Parse `<DistributionConfig>`.
27pub fn parse_distribution_config(n: &Node) -> DistributionConfig {
28    DistributionConfig {
29        caller_reference: n.child_text("CallerReference").to_owned(),
30        aliases: n.string_items("Aliases", "CNAME"),
31        default_root_object: n.child_text("DefaultRootObject").to_owned(),
32        origins: n
33            .items_named("Origins", "Origin")
34            .into_iter()
35            .map(parse_origin)
36            .collect(),
37        origin_groups: n
38            .items_named("OriginGroups", "OriginGroup")
39            .into_iter()
40            .map(parse_origin_group)
41            .collect(),
42        default_cache_behavior: n
43            .child("DefaultCacheBehavior")
44            .map(parse_cache_behavior)
45            .unwrap_or_default(),
46        cache_behaviors: n
47            .items_named("CacheBehaviors", "CacheBehavior")
48            .into_iter()
49            .map(parse_cache_behavior)
50            .collect(),
51        custom_error_responses: n
52            .items_named("CustomErrorResponses", "CustomErrorResponse")
53            .into_iter()
54            .map(parse_custom_error_response)
55            .collect(),
56        comment: n.child_text("Comment").to_owned(),
57        logging: n.child("Logging").map(parse_logging).unwrap_or_default(),
58        price_class: n.child_text("PriceClass").to_owned(),
59        enabled: n.child_bool("Enabled"),
60        viewer_certificate: n
61            .child("ViewerCertificate")
62            .map(parse_viewer_certificate)
63            .unwrap_or_default(),
64        restrictions: n
65            .child("Restrictions")
66            .map(parse_restrictions)
67            .unwrap_or_default(),
68        web_acl_id: n.child_text("WebACLId").to_owned(),
69        http_version: n.child_text("HttpVersion").to_owned(),
70        is_ipv6_enabled: n.child_bool("IsIPV6Enabled"),
71        continuous_deployment_policy_id: n.child_text("ContinuousDeploymentPolicyId").to_owned(),
72        staging: n.child_bool("Staging"),
73        anycast_ip_list_id: n.child_text("AnycastIpListId").to_owned(),
74        connection_mode: n.child_text("ConnectionMode").to_owned(),
75        tenant_config_parameters: Vec::new(),
76    }
77}
78
79fn parse_origin(n: &Node) -> Origin {
80    Origin {
81        id: n.child_text("Id").to_owned(),
82        domain_name: n.child_text("DomainName").to_owned(),
83        origin_path: n.child_text("OriginPath").to_owned(),
84        custom_headers: n
85            .items_named("CustomHeaders", "OriginCustomHeader")
86            .into_iter()
87            .map(|c| CustomHeader {
88                header_name: c.child_text("HeaderName").to_owned(),
89                header_value: c.child_text("HeaderValue").to_owned(),
90            })
91            .collect(),
92        s3_origin_config: n.child("S3OriginConfig").map(|s| S3OriginConfig {
93            origin_access_identity: s.child_text("OriginAccessIdentity").to_owned(),
94        }),
95        custom_origin_config: n.child("CustomOriginConfig").map(|c| CustomOriginConfig {
96            http_port: c.child_i32("HTTPPort"),
97            https_port: c.child_i32("HTTPSPort"),
98            origin_protocol_policy: c.child_text("OriginProtocolPolicy").to_owned(),
99            origin_ssl_protocols: c.string_items("OriginSslProtocols", "SslProtocol"),
100            origin_read_timeout: c.child_i32("OriginReadTimeout"),
101            origin_keepalive_timeout: c.child_i32("OriginKeepaliveTimeout"),
102        }),
103        connection_attempts: n.child_i32("ConnectionAttempts"),
104        connection_timeout: n.child_i32("ConnectionTimeout"),
105        origin_shield: n.child("OriginShield").map(|os| OriginShield {
106            enabled: os.child_bool("Enabled"),
107            origin_shield_region: os.child_text("OriginShieldRegion").to_owned(),
108        }),
109        origin_access_control_id: n.child_text("OriginAccessControlId").to_owned(),
110        vpc_origin_config: None,
111    }
112}
113
114fn parse_origin_group(n: &Node) -> OriginGroup {
115    OriginGroup {
116        id: n.child_text("Id").to_owned(),
117        failover_status_codes: n
118            .child("FailoverCriteria")
119            .map(|f| {
120                f.items_named("StatusCodes", "StatusCode")
121                    .into_iter()
122                    .map(|i| i.text.parse().unwrap_or(0))
123                    .collect()
124            })
125            .unwrap_or_default(),
126        member_origins: n
127            .items_named("Members", "OriginGroupMember")
128            .into_iter()
129            .map(|x| x.child_text("OriginId").to_owned())
130            .collect(),
131        selection_criteria: n.child_text("SelectionCriteria").to_owned(),
132    }
133}
134
135fn parse_cache_behavior(n: &Node) -> CacheBehavior {
136    CacheBehavior {
137        path_pattern: n.child_text("PathPattern").to_owned(),
138        target_origin_id: n.child_text("TargetOriginId").to_owned(),
139        viewer_protocol_policy: n.child_text("ViewerProtocolPolicy").to_owned(),
140        allowed_methods: n
141            .child("AllowedMethods")
142            .map(|a| a.direct_string_items("Method"))
143            .unwrap_or_default(),
144        cached_methods: n
145            .child("AllowedMethods")
146            .and_then(|a| a.child("CachedMethods"))
147            .map(|c| c.direct_string_items("Method"))
148            .unwrap_or_default(),
149        smooth_streaming: n.child_bool("SmoothStreaming"),
150        compress: n.child_bool("Compress"),
151        field_level_encryption_id: n.child_text("FieldLevelEncryptionId").to_owned(),
152        realtime_log_config_arn: n.child_text("RealtimeLogConfigArn").to_owned(),
153        cache_policy_id: n.child_text("CachePolicyId").to_owned(),
154        origin_request_policy_id: n.child_text("OriginRequestPolicyId").to_owned(),
155        response_headers_policy_id: n.child_text("ResponseHeadersPolicyId").to_owned(),
156        grpc_enabled: n
157            .child("GrpcConfig")
158            .map(|g| g.child_bool("Enabled"))
159            .unwrap_or(false),
160        trusted_signers: n
161            .child("TrustedSigners")
162            .map(|t| t.direct_string_items("AwsAccountNumber"))
163            .unwrap_or_default(),
164        trusted_signers_enabled: n
165            .child("TrustedSigners")
166            .map(|t| t.child_bool("Enabled"))
167            .unwrap_or(false),
168        trusted_key_groups: n
169            .child("TrustedKeyGroups")
170            .map(|t| t.direct_string_items("KeyGroup"))
171            .unwrap_or_default(),
172        trusted_key_groups_enabled: n
173            .child("TrustedKeyGroups")
174            .map(|t| t.child_bool("Enabled"))
175            .unwrap_or(false),
176        lambda_function_associations: n
177            .items_named("LambdaFunctionAssociations", "LambdaFunctionAssociation")
178            .into_iter()
179            .map(|la| LambdaFunctionAssociation {
180                lambda_function_arn: la.child_text("LambdaFunctionARN").to_owned(),
181                event_type: la.child_text("EventType").to_owned(),
182                include_body: la.child_bool("IncludeBody"),
183            })
184            .collect(),
185        function_associations: n
186            .items_named("FunctionAssociations", "FunctionAssociation")
187            .into_iter()
188            .map(|fa| FunctionAssociation {
189                function_arn: fa.child_text("FunctionARN").to_owned(),
190                event_type: fa.child_text("EventType").to_owned(),
191            })
192            .collect(),
193        forwarded_values: n.child("ForwardedValues").map(|fv| ForwardedValues {
194            query_string: fv.child_bool("QueryString"),
195            cookies: fv
196                .child("Cookies")
197                .map(parse_cookie_preference)
198                .unwrap_or_default(),
199            headers: fv
200                .child("Headers")
201                .map(|h| h.direct_string_items("Name"))
202                .unwrap_or_default(),
203            query_string_cache_keys: fv
204                .child("QueryStringCacheKeys")
205                .map(|q| q.direct_string_items("Name"))
206                .unwrap_or_default(),
207        }),
208        min_ttl: n.child_i64("MinTTL"),
209        default_ttl: n.child_i64("DefaultTTL"),
210        max_ttl: n.child_i64("MaxTTL"),
211    }
212}
213
214fn parse_cookie_preference(n: &Node) -> CookiePreference {
215    CookiePreference {
216        forward: n.child_text("Forward").to_owned(),
217        whitelisted_names: n
218            .child("WhitelistedNames")
219            .map(|w| w.direct_string_items("Name"))
220            .unwrap_or_default(),
221    }
222}
223
224fn parse_custom_error_response(n: &Node) -> CustomErrorResponse {
225    CustomErrorResponse {
226        error_code: n.child_i32("ErrorCode"),
227        response_page_path: n.child_text("ResponsePagePath").to_owned(),
228        response_code: n.child_text("ResponseCode").to_owned(),
229        error_caching_min_ttl: n.child_i64("ErrorCachingMinTTL"),
230    }
231}
232
233fn parse_logging(n: &Node) -> LoggingConfig {
234    LoggingConfig {
235        enabled: n.child_bool("Enabled"),
236        include_cookies: n.child_bool("IncludeCookies"),
237        bucket: n.child_text("Bucket").to_owned(),
238        prefix: n.child_text("Prefix").to_owned(),
239    }
240}
241
242fn parse_viewer_certificate(n: &Node) -> ViewerCertificate {
243    ViewerCertificate {
244        cloud_front_default_certificate: n.child_bool("CloudFrontDefaultCertificate"),
245        acm_certificate_arn: n.child_text("ACMCertificateArn").to_owned(),
246        iam_certificate_id: n.child_text("IAMCertificateId").to_owned(),
247        minimum_protocol_version: n.child_text("MinimumProtocolVersion").to_owned(),
248        ssl_support_method: n.child_text("SSLSupportMethod").to_owned(),
249        certificate: n.child_text("Certificate").to_owned(),
250        certificate_source: n.child_text("CertificateSource").to_owned(),
251    }
252}
253
254fn parse_restrictions(n: &Node) -> Restrictions {
255    Restrictions {
256        geo_restriction: n
257            .child("GeoRestriction")
258            .map(|g| GeoRestriction {
259                restriction_type: g.child_text("RestrictionType").to_owned(),
260                locations: g.direct_string_items("Location"),
261            })
262            .unwrap_or_default(),
263    }
264}
265
266/// Parse `<InvalidationBatch>`.
267pub fn parse_invalidation_batch(n: &Node) -> InvalidationBatch {
268    InvalidationBatch {
269        paths: n.string_items("Paths", "Path"),
270        caller_reference: n.child_text("CallerReference").to_owned(),
271    }
272}
273
274/// Parse `<OriginAccessControlConfig>`.
275pub fn parse_oac_config(n: &Node) -> OriginAccessControlConfig {
276    OriginAccessControlConfig {
277        name: n.child_text("Name").to_owned(),
278        description: n.child_text("Description").to_owned(),
279        signing_protocol: {
280            let s = n.child_text("SigningProtocol");
281            if s.is_empty() {
282                "sigv4".to_owned()
283            } else {
284                s.to_owned()
285            }
286        },
287        signing_behavior: {
288            let s = n.child_text("SigningBehavior");
289            if s.is_empty() {
290                "always".to_owned()
291            } else {
292                s.to_owned()
293            }
294        },
295        origin_access_control_origin_type: {
296            let s = n.child_text("OriginAccessControlOriginType");
297            if s.is_empty() {
298                "s3".to_owned()
299            } else {
300                s.to_owned()
301            }
302        },
303    }
304}
305
306/// Parse `<CloudFrontOriginAccessIdentityConfig>`.
307pub fn parse_oai_config(n: &Node) -> CloudFrontOriginAccessIdentityConfig {
308    CloudFrontOriginAccessIdentityConfig {
309        caller_reference: n.child_text("CallerReference").to_owned(),
310        comment: n.child_text("Comment").to_owned(),
311    }
312}
313
314/// Parse `<CachePolicyConfig>`.
315pub fn parse_cache_policy_config(n: &Node) -> CachePolicyConfig {
316    let params = n.child("ParametersInCacheKeyAndForwardedToOrigin");
317    CachePolicyConfig {
318        comment: n.child_text("Comment").to_owned(),
319        name: n.child_text("Name").to_owned(),
320        default_ttl: n.child_i64("DefaultTTL"),
321        max_ttl: n.child_i64("MaxTTL"),
322        min_ttl: n.child_i64("MinTTL"),
323        parameters_in_cache_key_and_forwarded_to_origin: ParamsInCacheKey {
324            enable_accept_encoding_gzip: params
325                .map(|p| p.child_bool("EnableAcceptEncodingGzip"))
326                .unwrap_or(false),
327            enable_accept_encoding_brotli: params
328                .map(|p| p.child_bool("EnableAcceptEncodingBrotli"))
329                .unwrap_or(false),
330            headers_config: params
331                .and_then(|p| p.child("HeadersConfig"))
332                .map(|h| CachePolicyHeadersConfig {
333                    header_behavior: h.child_text("HeaderBehavior").to_owned(),
334                    headers: h.string_items("Headers", "Name"),
335                })
336                .unwrap_or_default(),
337            cookies_config: params
338                .and_then(|p| p.child("CookiesConfig"))
339                .map(|c| CachePolicyCookiesConfig {
340                    cookie_behavior: c.child_text("CookieBehavior").to_owned(),
341                    cookies: c.string_items("Cookies", "Name"),
342                })
343                .unwrap_or_default(),
344            query_strings_config: params
345                .and_then(|p| p.child("QueryStringsConfig"))
346                .map(|q| CachePolicyQueryStringsConfig {
347                    query_string_behavior: q.child_text("QueryStringBehavior").to_owned(),
348                    query_strings: q.string_items("QueryStrings", "Name"),
349                })
350                .unwrap_or_default(),
351        },
352    }
353}
354
355/// Parse `<OriginRequestPolicyConfig>`.
356pub fn parse_origin_request_policy_config(n: &Node) -> OriginRequestPolicyConfig {
357    OriginRequestPolicyConfig {
358        comment: n.child_text("Comment").to_owned(),
359        name: n.child_text("Name").to_owned(),
360        headers_config: n
361            .child("HeadersConfig")
362            .map(|h| OriginRequestPolicyHeadersConfig {
363                header_behavior: h.child_text("HeaderBehavior").to_owned(),
364                headers: h.string_items("Headers", "Name"),
365            })
366            .unwrap_or_default(),
367        cookies_config: n
368            .child("CookiesConfig")
369            .map(|c| OriginRequestPolicyCookiesConfig {
370                cookie_behavior: c.child_text("CookieBehavior").to_owned(),
371                cookies: c.string_items("Cookies", "Name"),
372            })
373            .unwrap_or_default(),
374        query_strings_config: n
375            .child("QueryStringsConfig")
376            .map(|q| OriginRequestPolicyQueryStringsConfig {
377                query_string_behavior: q.child_text("QueryStringBehavior").to_owned(),
378                query_strings: q.string_items("QueryStrings", "Name"),
379            })
380            .unwrap_or_default(),
381    }
382}
383
384/// Parse `<ResponseHeadersPolicyConfig>` (minimal fields only).
385pub fn parse_response_headers_policy_config(n: &Node) -> ResponseHeadersPolicyConfig {
386    ResponseHeadersPolicyConfig {
387        comment: n.child_text("Comment").to_owned(),
388        name: n.child_text("Name").to_owned(),
389        ..Default::default()
390    }
391}
392
393/// Parse `<KeyGroupConfig>`.
394pub fn parse_key_group_config(n: &Node) -> KeyGroupConfig {
395    KeyGroupConfig {
396        name: n.child_text("Name").to_owned(),
397        items: n.direct_string_items("PublicKey"),
398        comment: n.child_text("Comment").to_owned(),
399    }
400}
401
402/// Parse `<PublicKeyConfig>`.
403pub fn parse_public_key_config(n: &Node) -> PublicKeyConfig {
404    PublicKeyConfig {
405        caller_reference: n.child_text("CallerReference").to_owned(),
406        name: n.child_text("Name").to_owned(),
407        encoded_key: n.child_text("EncodedKey").to_owned(),
408        comment: n.child_text("Comment").to_owned(),
409    }
410}
411
412/// Parse `<FunctionConfig>`.
413pub fn parse_function_config(n: &Node) -> FunctionConfig {
414    FunctionConfig {
415        comment: n.child_text("Comment").to_owned(),
416        runtime: n.child_text("Runtime").to_owned(),
417        key_value_store_associations: n
418            .child("KeyValueStoreAssociations")
419            .map(|k| k.direct_string_items("KeyValueStoreAssociation"))
420            .unwrap_or_default(),
421    }
422}
423
424/// Parse `<FieldLevelEncryptionConfig>`.
425pub fn parse_fle_config(n: &Node) -> FieldLevelEncryptionConfig {
426    FieldLevelEncryptionConfig {
427        caller_reference: n.child_text("CallerReference").to_owned(),
428        comment: n.child_text("Comment").to_owned(),
429        query_arg_profile_config_enabled: n
430            .child("QueryArgProfileConfig")
431            .map(|_| true)
432            .unwrap_or(false),
433        content_type_profile_config_enabled: n
434            .child("ContentTypeProfileConfig")
435            .map(|_| true)
436            .unwrap_or(false),
437    }
438}
439
440/// Parse `<FieldLevelEncryptionProfileConfig>`.
441pub fn parse_fle_profile_config(n: &Node) -> FieldLevelEncryptionProfileConfig {
442    FieldLevelEncryptionProfileConfig {
443        name: n.child_text("Name").to_owned(),
444        caller_reference: n.child_text("CallerReference").to_owned(),
445        comment: n.child_text("Comment").to_owned(),
446    }
447}
448
449/// Parse `<RealtimeLogConfig>` at creation.
450pub fn parse_realtime_log_config(n: &Node) -> RealtimeLogConfig {
451    RealtimeLogConfig {
452        arn: n.child_text("ARN").to_owned(),
453        name: n.child_text("Name").to_owned(),
454        sampling_rate: n.child_i64("SamplingRate"),
455        end_points: Vec::new(),
456        fields: n.string_items("Fields", "Field"),
457    }
458}
459
460/// Parse a tagging payload: `<Tagging><TagSet><Tag>...</Tag></TagSet></Tagging>`.
461pub fn parse_tag_set(n: &Node) -> TagSet {
462    n.child("TagSet")
463        .or_else(|| n.child("Tags"))
464        .map(|ts| {
465            ts.children_named("Tag")
466                .map(|t| Tag {
467                    key: t.child_text("Key").to_owned(),
468                    value: t.child_text("Value").to_owned(),
469                })
470                .collect()
471        })
472        .unwrap_or_default()
473}
474
475/// Parse a list of tag keys: `<TagKeys><Key>foo</Key></TagKeys>`.
476pub fn parse_tag_keys(n: &Node) -> Vec<String> {
477    n.children_named("Key").map(|k| k.text.clone()).collect()
478}