Skip to content

Commit 44bbff1

Browse files
committed
verify query and fragment works as expected for assets and redirects
1 parent 25c36df commit 44bbff1

File tree

1 file changed

+105
-44
lines changed

1 file changed

+105
-44
lines changed

src/webserver/routing.rs

Lines changed: 105 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use awc::http::Uri;
2+
use std::ffi::OsStr;
23
use std::path::PathBuf;
34
use RoutingAction::{Error, Execute, NotFound, Redirect, Serve};
45

@@ -21,68 +22,96 @@ pub trait ExecutionStore {
2122
}
2223

2324
pub trait RoutingConfig {
24-
fn prefix(&self) -> &str;
25+
fn prefix(&self) -> &Uri;
2526
}
2627

2728
pub async fn calculate_route<T, C>(uri: Uri, store: &T, config: &C) -> RoutingAction
2829
where
2930
T: ExecutionStore,
3031
C: RoutingConfig,
3132
{
32-
if !uri.path().starts_with(config.prefix()) {
33-
return Redirect(config.prefix().parse().unwrap());
34-
};
35-
36-
let mut path = PathBuf::from(format!(
37-
"/{}",
38-
uri.path().strip_prefix(config.prefix()).unwrap()
39-
));
40-
41-
match path.extension() {
42-
None => {
43-
if uri.path().ends_with(FORWARD_SLASH) {
44-
path.push(INDEX);
45-
find_execution_or_not_found(path, store).await
46-
} else {
47-
let path_with_ext = path.with_extension(EXECUTION_EXTENSION);
48-
match find_execution(path_with_ext, store).await {
49-
Some(action) => action,
50-
None => Redirect(append_to_uri_path(&uri, FORWARD_SLASH)),
51-
}
52-
}
53-
}
54-
Some(extension) => {
55-
if extension == EXECUTION_EXTENSION {
56-
find_execution_or_not_found(path, store).await
57-
} else {
58-
Serve(path)
59-
}
33+
match check_uri(&uri, config) {
34+
Ok(path) => match path.clone().extension() {
35+
None => calculate_route_without_extension(&uri, path, store).await,
36+
Some(extension) => calculate_route_with_extension(path, extension, store).await,
37+
},
38+
Err(action) => action,
39+
}
40+
}
41+
42+
fn check_uri<C>(uri: &Uri, config: &C) -> Result<PathBuf, RoutingAction>
43+
where
44+
C: RoutingConfig,
45+
{
46+
if uri.path().starts_with(config.prefix().path()) {
47+
Ok(PathBuf::from(format!(
48+
"/{}",
49+
uri.path()
50+
.strip_prefix(config.prefix().path())
51+
.expect("Unable to remove expected prefix from path")
52+
)))
53+
} else {
54+
Err(Redirect(config.prefix().clone()))
55+
}
56+
}
57+
58+
async fn calculate_route_without_extension<T>(
59+
uri: &Uri,
60+
mut path: PathBuf,
61+
store: &T,
62+
) -> RoutingAction
63+
where
64+
T: ExecutionStore,
65+
{
66+
if uri.path().ends_with(FORWARD_SLASH) {
67+
path.push(INDEX);
68+
find_execution_or_not_found(&path, store).await
69+
} else {
70+
let path_with_ext = path.with_extension(EXECUTION_EXTENSION);
71+
match find_execution(&path_with_ext, store).await {
72+
Some(action) => action,
73+
None => Redirect(append_to_uri_path(&uri, FORWARD_SLASH)),
6074
}
6175
}
6276
}
6377

64-
async fn find_execution_or_not_found<T>(path: PathBuf, store: &T) -> RoutingAction
78+
async fn calculate_route_with_extension<T>(
79+
path: PathBuf,
80+
extension: &OsStr,
81+
store: &T,
82+
) -> RoutingAction
83+
where
84+
T: ExecutionStore,
85+
{
86+
if extension == EXECUTION_EXTENSION {
87+
find_execution_or_not_found(&path, store).await
88+
} else {
89+
Serve(path)
90+
}
91+
}
92+
93+
async fn find_execution_or_not_found<T>(path: &PathBuf, store: &T) -> RoutingAction
6594
where
6695
T: ExecutionStore,
6796
{
68-
match find_execution(path.clone(), store).await {
97+
match find_execution(path, store).await {
6998
None => find_not_found(path, store).await,
7099
Some(execute) => execute,
71100
}
72101
}
73102

74-
async fn find_execution<T>(path: PathBuf, store: &T) -> Option<RoutingAction>
103+
async fn find_execution<T>(path: &PathBuf, store: &T) -> Option<RoutingAction>
75104
where
76105
T: ExecutionStore,
77106
{
78-
if store.contains(&path).await {
79-
Some(Execute(path))
107+
if store.contains(path).await {
108+
Some(Execute(path.clone()))
80109
} else {
81110
None
82111
}
83112
}
84113

85-
async fn find_not_found<T>(path: PathBuf, store: &T) -> RoutingAction
114+
async fn find_not_found<T>(path: &PathBuf, store: &T) -> RoutingAction
86115
where
87116
T: ExecutionStore,
88117
{
@@ -96,13 +125,13 @@ where
96125
}
97126
}
98127

99-
Error(path_to_string(&path))
128+
Error(path_to_string(path))
100129
}
101130

102131
fn append_to_uri_path(uri: &Uri, append: &str) -> Uri {
103132
let mut full_uri = uri.to_string();
104133
full_uri.insert_str(uri.path().len(), append);
105-
full_uri.parse().unwrap()
134+
full_uri.parse().expect("Could not append uri path")
106135
}
107136

108137
fn path_to_string(path: &PathBuf) -> String {
@@ -132,23 +161,23 @@ mod tests {
132161
}
133162

134163
#[tokio::test]
135-
async fn root_path_with_site_prefix_executes_index() {
164+
async fn root_path_and_site_prefix_executes_index() {
136165
let actual = do_route("/prefix/", Default, Some("/prefix/")).await;
137166
let expected = execute("/index.sql");
138167

139168
assert_eq!(expected, actual);
140169
}
141170

142171
#[tokio::test]
143-
async fn sql_extension() {
172+
async fn extension() {
144173
let actual = do_route("/index.sql", Default, None).await;
145174
let expected = execute("/index.sql");
146175

147176
assert_eq!(expected, actual);
148177
}
149178

150179
#[tokio::test]
151-
async fn sql_extension_and_site_prefix() {
180+
async fn extension_and_site_prefix() {
152181
let actual = do_route("/prefix/index.sql", Default, Some("/prefix/")).await;
153182
let expected = execute("/index.sql");
154183

@@ -294,6 +323,22 @@ mod tests {
294323
assert_eq!(expected, actual);
295324
}
296325

326+
#[tokio::test]
327+
async fn asset_trims_query() {
328+
let actual = do_route("/favicon.ico?version=10", Default, None).await;
329+
let expected = serve("/favicon.ico");
330+
331+
assert_eq!(expected, actual);
332+
}
333+
334+
#[tokio::test]
335+
async fn asset_trims_fragment() {
336+
let actual = do_route("/favicon.ico#asset1", Default, None).await;
337+
let expected = serve("/favicon.ico");
338+
339+
assert_eq!(expected, actual);
340+
}
341+
297342
#[tokio::test]
298343
async fn serves_corresponding_asset_given_site_prefix() {
299344
let actual = do_route("/prefix/favicon.ico", Default, Some("/prefix/")).await;
@@ -323,6 +368,22 @@ mod tests {
323368
assert_eq!(expected, actual);
324369
}
325370

371+
#[tokio::test]
372+
async fn no_extension_no_corresponding_file_redirects_with_trailing_slash_and_query() {
373+
let actual = do_route("/folder?misc=1&foo=bar", Default, None).await;
374+
let expected = redirect("/folder/?misc=1&foo=bar");
375+
376+
assert_eq!(expected, actual);
377+
}
378+
379+
#[tokio::test]
380+
async fn no_extension_no_corresponding_file_redirects_with_trailing_slash_and_fragment() {
381+
let actual = do_route("/folder#anchor1", Default, None).await;
382+
let expected = redirect("/folder/#anchor1");
383+
384+
assert_eq!(expected, actual);
385+
}
386+
326387
#[tokio::test]
327388
async fn no_extension_site_prefix_and_no_corresponding_file_redirects_with_trailing_slash()
328389
{
@@ -409,19 +470,19 @@ mod tests {
409470
}
410471

411472
struct Config {
412-
prefix: String,
473+
prefix: Uri,
413474
}
414475

415476
impl Config {
416477
fn new(prefix: &str) -> Self {
417478
Self {
418-
prefix: prefix.to_string(),
479+
prefix: prefix.parse().unwrap(),
419480
}
420481
}
421482
}
422483
impl RoutingConfig for Config {
423-
fn prefix(&self) -> &str {
424-
self.prefix.as_str()
484+
fn prefix(&self) -> &Uri {
485+
&self.prefix
425486
}
426487
}
427488

0 commit comments

Comments
 (0)