Skip to content

Commit aaa4264

Browse files
authored
feat(ic-asset-certification): allow adding additional headers to redi… (#400)
#399 must be merged first.
1 parent b8aa830 commit aaa4264

File tree

7 files changed

+141
-18
lines changed

7 files changed

+141
-18
lines changed

examples/http-certification/assets/README.md

+7
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,13 @@ fn certify_all_assets() {
229229
from: "/old-url".to_string(),
230230
to: "/".to_string(),
231231
kind: AssetRedirectKind::Permanent,
232+
headers: get_asset_headers(vec![
233+
("content-type".to_string(), "text/plain".to_string()),
234+
(
235+
"cache-control".to_string(),
236+
NO_CACHE_ASSET_CACHE_CONTROL.to_string(),
237+
),
238+
]),
232239
},
233240
];
234241

examples/http-certification/assets/src/backend/src/lib.rs

+7
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,13 @@ fn certify_all_assets() {
132132
from: "/old-url".to_string(),
133133
to: "/".to_string(),
134134
kind: AssetRedirectKind::Permanent,
135+
headers: get_asset_headers(vec![
136+
("content-type".to_string(), "text/plain".to_string()),
137+
(
138+
"cache-control".to_string(),
139+
NO_CACHE_ASSET_CACHE_CONTROL.to_string(),
140+
),
141+
]),
135142
},
136143
];
137144

examples/http-certification/assets/src/tests/src/http.spec.ts

+14-8
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,12 @@ describe('Assets', () => {
103103

104104
expect(response.status_code).toBe(301);
105105
expectHeader(response.headers, ['location', '/']);
106-
// enable when additional headers can be added to redirect responses
107-
// expectSecurityHeaders(response.headers);
106+
expectHeader(response.headers, ['content-type', 'text/plain']);
107+
expectHeader(response.headers, [
108+
'cache-control',
109+
'public, no-cache, no-store',
110+
]);
111+
expectSecurityHeaders(response.headers);
108112

109113
let verificationResult = verifyRequestResponsePair(
110114
request,
@@ -116,14 +120,16 @@ describe('Assets', () => {
116120
CERTIFICATE_VERSION,
117121
);
118122

123+
const verifiedResponse = verificationResult.response;
119124
expect(verificationResult.verificationVersion).toEqual(CERTIFICATE_VERSION);
120-
expect(verificationResult.response?.statusCode).toBe(301);
121-
expect(verificationResult.response?.headers).toContainEqual([
122-
'location',
123-
'/',
125+
expect(verifiedResponse?.statusCode).toBe(301);
126+
expectHeader(verifiedResponse?.headers, ['location', '/']);
127+
expectHeader(verifiedResponse?.headers, ['content-type', 'text/plain']);
128+
expectHeader(verifiedResponse?.headers, [
129+
'cache-control',
130+
'public, no-cache, no-store',
124131
]);
125-
// enable when additional headers can be added to redirect responses
126-
// expectSecurityHeaders(verificationResult.response?.headers);
132+
expectSecurityHeaders(verifiedResponse?.headers);
127133
});
128134

129135
// paths must be updated if the asset content changes

packages/ic-asset-certification/README.md

+17
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,10 @@ let config = AssetConfig::Redirect {
328328
from: "/old".to_string(),
329329
to: "/new".to_string(),
330330
kind: AssetRedirectKind::Permanent,
331+
headers: vec![(
332+
"content-type".to_string(),
333+
"text/plain; charset=utf-8".to_string(),
334+
)],
331335
};
332336
```
333337

@@ -429,6 +433,10 @@ let asset_configs = vec![
429433
from: "/old".to_string(),
430434
to: "/new".to_string(),
431435
kind: AssetRedirectKind::Permanent,
436+
headers: vec![(
437+
"content-type".to_string(),
438+
"text/plain; charset=utf-8".to_string(),
439+
)],
432440
},
433441
];
434442

@@ -631,6 +639,10 @@ let asset_configs = vec![
631639
from: "/old".to_string(),
632640
to: "/new".to_string(),
633641
kind: AssetRedirectKind::Permanent,
642+
headers: vec![(
643+
"content-type".to_string(),
644+
"text/plain; charset=utf-8".to_string(),
645+
)],
634646
},
635647
];
636648

@@ -742,6 +754,10 @@ asset_router
742754
from: "/old".to_string(),
743755
to: "/new".to_string(),
744756
kind: AssetRedirectKind::Permanent,
757+
headers: vec![(
758+
"content-type".to_string(),
759+
"text/plain; charset=utf-8".to_string(),
760+
)],
745761
}],
746762
)
747763
.unwrap();
@@ -864,6 +880,7 @@ let asset_configs = vec![
864880
from: "/old".to_string(),
865881
to: "/new".to_string(),
866882
kind: AssetRedirectKind::Permanent,
883+
headers: vec![("content-type".to_string(), "text/plain".to_string())],
867884
},
868885
];
869886

packages/ic-asset-certification/src/asset_config.rs

+32-3
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ use std::fmt::{Display, Formatter};
161161
/// from: "/old".to_string(),
162162
/// to: "/new".to_string(),
163163
/// kind: AssetRedirectKind::Temporary,
164+
/// headers: vec![(
165+
/// "content-type".to_string(),
166+
/// "text/plain; charset=utf-8".to_string(),
167+
/// )],
164168
/// };
165169
/// ```
166170
///
@@ -176,6 +180,10 @@ use std::fmt::{Display, Formatter};
176180
/// from: "/old".to_string(),
177181
/// to: "/new".to_string(),
178182
/// kind: AssetRedirectKind::Permanent,
183+
/// headers: vec![(
184+
/// "content-type".to_string(),
185+
/// "text/plain; charset=utf-8".to_string(),
186+
/// )],
179187
/// };
180188
/// ```
181189
#[derive(Debug, Clone)]
@@ -365,6 +373,14 @@ pub enum AssetConfig {
365373

366374
/// The kind redirect to configure.
367375
kind: AssetRedirectKind,
376+
377+
/// Additional headers to be inserted into the response. Each additional
378+
/// header added will be included in certification and served by the
379+
/// [AssetRouter](crate::AssetRouter) for matching [Assets](Asset).
380+
///
381+
/// Note that the `Location` header will be automatically added to the
382+
/// response with the value of the `to` field.
383+
headers: Vec<(String, String)>,
368384
},
369385
}
370386

@@ -535,6 +551,7 @@ pub(crate) enum NormalizedAssetConfig {
535551
from: String,
536552
to: String,
537553
kind: AssetRedirectKind,
554+
headers: Vec<(String, String)>,
538555
},
539556
}
540557

@@ -569,9 +586,17 @@ impl TryFrom<AssetConfig> for NormalizedAssetConfig {
569586
headers,
570587
encodings,
571588
}),
572-
AssetConfig::Redirect { from, to, kind } => {
573-
Ok(NormalizedAssetConfig::Redirect { from, to, kind })
574-
}
589+
AssetConfig::Redirect {
590+
from,
591+
to,
592+
kind,
593+
headers,
594+
} => Ok(NormalizedAssetConfig::Redirect {
595+
from,
596+
to,
597+
kind,
598+
headers,
599+
}),
575600
}
576601
}
577602
}
@@ -726,6 +751,10 @@ mod tests {
726751
from: asset_path.to_string(),
727752
to: asset_path.to_string(),
728753
kind: AssetRedirectKind::Permanent,
754+
headers: vec![(
755+
"content-type".to_string(),
756+
"text/plain; charset=utf-8".to_string(),
757+
)],
729758
}
730759
.try_into()
731760
.unwrap();

packages/ic-asset-certification/src/asset_router.rs

+47-7
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,19 @@ use std::{borrow::Cow, cell::RefCell, cmp, collections::HashMap, rc::Rc};
7878
/// from: "/old-url".to_string(),
7979
/// to: "/".to_string(),
8080
/// kind: AssetRedirectKind::Permanent,
81+
/// headers: vec![(
82+
/// "content-type".to_string(),
83+
/// "text/plain; charset=utf-8".to_string(),
84+
/// )],
8185
/// },
8286
/// AssetConfig::Redirect {
8387
/// from: "/css/app.css".to_string(),
8488
/// to: "/css/app-ba74b708.css".to_string(),
8589
/// kind: AssetRedirectKind::Temporary,
90+
/// headers: vec![(
91+
/// "content-type".to_string(),
92+
/// "text/plain; charset=utf-8".to_string(),
93+
/// )],
8694
/// },
8795
/// ];
8896
///
@@ -311,8 +319,14 @@ impl<'content> AssetRouter<'content> {
311319
}
312320

313321
for asset_config in asset_configs {
314-
if let NormalizedAssetConfig::Redirect { from, to, kind } = asset_config {
315-
self.insert_redirect(from, to, kind)?;
322+
if let NormalizedAssetConfig::Redirect {
323+
from,
324+
to,
325+
kind,
326+
headers,
327+
} = asset_config
328+
{
329+
self.insert_redirect(from, to, kind, headers)?;
316330
}
317331
}
318332

@@ -367,8 +381,14 @@ impl<'content> AssetRouter<'content> {
367381
}
368382

369383
for asset_config in asset_configs {
370-
if let NormalizedAssetConfig::Redirect { from, to, kind } = asset_config {
371-
self.delete_redirect(from, to, kind)?;
384+
if let NormalizedAssetConfig::Redirect {
385+
from,
386+
to,
387+
kind,
388+
headers,
389+
} = asset_config
390+
{
391+
self.delete_redirect(from, to, kind, headers)?;
372392
}
373393
}
374394

@@ -799,8 +819,9 @@ impl<'content> AssetRouter<'content> {
799819
from: String,
800820
to: String,
801821
kind: AssetRedirectKind,
822+
additional_headers: Vec<(String, String)>,
802823
) -> AssetCertificationResult<()> {
803-
let response = Self::prepare_redirect(from.clone(), to, kind)?;
824+
let response = Self::prepare_redirect(from.clone(), to, kind, additional_headers)?;
804825

805826
self.tree.borrow_mut().insert(&response.tree_entry);
806827

@@ -815,8 +836,9 @@ impl<'content> AssetRouter<'content> {
815836
from: String,
816837
to: String,
817838
kind: AssetRedirectKind,
839+
addtional_headers: Vec<(String, String)>,
818840
) -> AssetCertificationResult<()> {
819-
let response = Self::prepare_redirect(from.clone(), to, kind)?;
841+
let response = Self::prepare_redirect(from.clone(), to, kind, addtional_headers)?;
820842

821843
self.tree.borrow_mut().delete(&response.tree_entry);
822844
self.responses.remove(&RequestKey::new(&from, None, None));
@@ -828,13 +850,15 @@ impl<'content> AssetRouter<'content> {
828850
from: String,
829851
to: String,
830852
kind: AssetRedirectKind,
853+
addtional_headers: Vec<(String, String)>,
831854
) -> AssetCertificationResult<CertifiedAssetResponse<'content>> {
832855
let status_code = match kind {
833856
AssetRedirectKind::Permanent => StatusCode::MOVED_PERMANENTLY,
834857
AssetRedirectKind::Temporary => StatusCode::TEMPORARY_REDIRECT,
835858
};
836859

837-
let headers = vec![("location".to_string(), to)];
860+
let mut headers = vec![("location".to_string(), to)];
861+
headers.extend(addtional_headers);
838862

839863
let (response, certification) = Self::prepare_response_and_certification(
840864
from.clone(),
@@ -3309,6 +3333,10 @@ mod tests {
33093333
CERTIFICATE_EXPRESSION_HEADER_NAME.to_string(),
33103334
cel_expr.clone(),
33113335
),
3336+
(
3337+
"content-type".to_string(),
3338+
"text/plain; charset=utf-8".to_string(),
3339+
),
33123340
])
33133341
.build();
33143342
let mut expected_old_url_response = HttpResponse::builder()
@@ -3317,6 +3345,10 @@ mod tests {
33173345
("content-length".to_string(), "0".to_string()),
33183346
("location".to_string(), "/".to_string()),
33193347
(CERTIFICATE_EXPRESSION_HEADER_NAME.to_string(), cel_expr),
3348+
(
3349+
"content-type".to_string(),
3350+
"text/plain; charset=utf-8".to_string(),
3351+
),
33203352
])
33213353
.build();
33223354

@@ -3891,6 +3923,10 @@ mod tests {
38913923
from: "/old-url".to_string(),
38923924
to: "/".to_string(),
38933925
kind: AssetRedirectKind::Permanent,
3926+
headers: vec![(
3927+
"content-type".to_string(),
3928+
"text/plain; charset=utf-8".to_string(),
3929+
)],
38943930
}
38953931
}
38963932

@@ -3900,6 +3936,10 @@ mod tests {
39003936
from: "/css/app.css".to_string(),
39013937
to: "/css/app-ba74b708.css".to_string(),
39023938
kind: AssetRedirectKind::Temporary,
3939+
headers: vec![(
3940+
"content-type".to_string(),
3941+
"text/plain; charset=utf-8".to_string(),
3942+
)],
39033943
}
39043944
}
39053945

packages/ic-asset-certification/src/lib.rs

+17
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,10 @@
328328
//! from: "/old".to_string(),
329329
//! to: "/new".to_string(),
330330
//! kind: AssetRedirectKind::Permanent,
331+
//! headers: vec![(
332+
//! "content-type".to_string(),
333+
//! "text/plain; charset=utf-8".to_string(),
334+
//! )],
331335
//! };
332336
//! ```
333337
//!
@@ -430,6 +434,10 @@
430434
//! from: "/old".to_string(),
431435
//! to: "/new".to_string(),
432436
//! kind: AssetRedirectKind::Permanent,
437+
//! headers: vec![(
438+
//! "content-type".to_string(),
439+
//! "text/plain; charset=utf-8".to_string(),
440+
//! )],
433441
//! },
434442
//! ];
435443
//!
@@ -620,6 +628,10 @@
620628
//! from: "/old".to_string(),
621629
//! to: "/new".to_string(),
622630
//! kind: AssetRedirectKind::Permanent,
631+
//! headers: vec![(
632+
//! "content-type".to_string(),
633+
//! "text/plain; charset=utf-8".to_string(),
634+
//! )],
623635
//! },
624636
//! ];
625637
//!
@@ -750,6 +762,10 @@
750762
//! from: "/old".to_string(),
751763
//! to: "/new".to_string(),
752764
//! kind: AssetRedirectKind::Permanent,
765+
//! headers: vec![(
766+
//! "content-type".to_string(),
767+
//! "text/plain; charset=utf-8".to_string(),
768+
//! )],
753769
//! }],
754770
//! )
755771
//! .unwrap();
@@ -872,6 +888,7 @@
872888
//! from: "/old".to_string(),
873889
//! to: "/new".to_string(),
874890
//! kind: AssetRedirectKind::Permanent,
891+
//! headers: vec![("content-type".to_string(), "text/plain".to_string())],
875892
//! },
876893
//! ];
877894
//!

0 commit comments

Comments
 (0)