1use 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
18pub fn parse_root(body: &[u8]) -> Result<Node, CloudFrontError> {
23 parse(body).map_err(CloudFrontError::MalformedInput)
24}
25
26pub 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
266pub 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
274pub 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
306pub 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
314pub 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
355pub 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
384pub 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
393pub 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
402pub 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
412pub 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
424pub 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
440pub 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
449pub 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
460pub 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
475pub fn parse_tag_keys(n: &Node) -> Vec<String> {
477 n.children_named("Key").map(|k| k.text.clone()).collect()
478}