Skip to content

Commit 7b68ce9

Browse files
committed
Added redirect to missing /
1 parent 604d682 commit 7b68ce9

1 file changed

Lines changed: 55 additions & 31 deletions

File tree

src/main.rs

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use log::{LevelFilter, error, info};
66
use std::convert::Infallible;
77
use std::env;
88
use time::format_description;
9+
use warp::http::StatusCode;
910
use warp::{Filter, http::Response};
1011

1112
#[tokio::main]
@@ -14,12 +15,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
1415
.filter_level(LevelFilter::Info)
1516
.init();
1617
info!("GCS Web Server starting...");
17-
let bucket_name;
1818
let args: Vec<String> = env::args().collect();
19-
match args.get(1) {
20-
Some(bucket) => bucket_name = bucket.to_string(),
19+
let bucket_name = match args.get(1) {
20+
Some(bucket) => bucket.to_string(),
2121
None => panic!("No bucket was passed"),
22-
}
22+
};
2323
info!("Connecting to bucket gs://{bucket_name}");
2424

2525
let config = ClientConfig::default().with_auth().await?;
@@ -33,22 +33,51 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
3333
.and(bucket_filter)
3434
.and_then(serve_gcs_content);
3535

36-
warp::serve(routes).run(([0, 0, 0, 0], 8080)).await;
36+
warp::serve(routes).run(([0, 0, 0, 0], 8585)).await;
3737

3838
Ok(())
3939
}
4040

41-
async fn check_for_dirs(client: &Client, bucket: String, path: String) -> bool {
41+
async fn check_for_dirs(
42+
client: &Client,
43+
bucket: String,
44+
path: String,
45+
) -> Option<Response<Vec<u8>>> {
46+
if path.ends_with('/') || path.is_empty() {
47+
return None;
48+
}
4249
let list_req_test_prefix = ListObjectsRequest {
4350
bucket,
4451
prefix: Some(format!("{}/", path)),
4552
delimiter: Some("/".to_string()),
53+
max_results: Some(1), // Fast check: only need 1 result to confirm existence
4654
..Default::default()
4755
};
48-
client
49-
.list_objects(&list_req_test_prefix)
50-
.await
51-
.is_ok_and(|res| res.prefixes.is_some() || res.items.is_some())
56+
match client.list_objects(&list_req_test_prefix).await {
57+
Ok(res) => {
58+
// If any prefixes (sub-folders) or items (files) exist, it's a folder.
59+
if res.prefixes.is_some() || res.items.is_some() {
60+
let new_path = format!("/{}/", path);
61+
info!("REDIRECT: path='{}' -> '{}'", path, new_path);
62+
63+
// Return a 302 Found response to redirect the browser
64+
let res = Response::builder()
65+
.status(StatusCode::FOUND)
66+
.header("Location", new_path)
67+
.body(Vec::new())
68+
.unwrap();
69+
return Some(res);
70+
}
71+
}
72+
Err(e) => {
73+
error!("REDIRECT_CHECK_ERROR: path='{}', error='{:?}'", path, e);
74+
}
75+
}
76+
None
77+
// client
78+
// .list_objects(&list_req_test_prefix)
79+
// .await
80+
// .is_ok_and(|res| res.prefixes.is_some() || res.items.is_some())
5281
}
5382

5483
async fn serve_gcs_content(
@@ -70,10 +99,8 @@ async fn serve_gcs_content(
7099
let file_metadata = client.get_object(&file_req).await;
71100

72101
// Check if a path without a trailing slash is a directory
73-
let is_dir_no_slash = if !is_dir && !path_str.is_empty() {
74-
check_for_dirs(&client, bucket_name.clone(), path_str.clone()).await
75-
} else {
76-
false
102+
if let Some(res) = check_for_dirs(&client, bucket_name.clone(), path_str.clone()).await {
103+
return Ok(res);
77104
};
78105

79106
// If metadata is found, it's a file. Serve the download.
@@ -128,7 +155,7 @@ async fn serve_gcs_content(
128155
}
129156

130157
// If it's a directory (either with or without a trailing slash), list its contents
131-
if is_dir || path_str.is_empty() || is_dir_no_slash {
158+
if is_dir || path_str.is_empty() {
132159
let prefix = if path_str.is_empty() {
133160
None
134161
} else {
@@ -224,20 +251,18 @@ fn build_html(
224251
) -> String {
225252
let parent_path = if path_str.is_empty() {
226253
"".to_string()
254+
} else if path_str.ends_with("/") {
255+
str::trim_end_matches(&path_str, '/')
256+
.rsplit_once('/')
257+
.map_or("", |(parent, _)| parent)
258+
.to_string()
227259
} else {
228-
if path_str.ends_with("/") {
229-
str::trim_end_matches(&path_str, '/')
230-
.rsplit_once('/')
231-
.map_or("", |(parent, _)| parent)
232-
.to_string()
233-
} else {
234-
path_str
235-
.rsplit_once('/')
236-
.map_or("", |(parent, _)| parent)
237-
.to_string()
238-
}
260+
path_str
261+
.rsplit_once('/')
262+
.map_or("", |(parent, _)| parent)
263+
.to_string()
239264
};
240-
let html = format!(
265+
format!(
241266
r#"
242267
<!DOCTYPE html>
243268
<html>
@@ -286,19 +311,18 @@ fn build_html(
286311
.map(|f| format!(
287312
"<tr><td><i class=\"fa-solid fa-folder\"></i></td> <td><a href=\"/{}\">{}</a></td><td align=\"right\">-</td><td align=\"right\">-</td></tr>",
288313
f,
289-
f.trim_end_matches('/').split('/').last().unwrap_or("")
314+
f.trim_end_matches('/').split('/').next_back().unwrap_or("")
290315
))
291316
.collect::<String>(),
292317
files
293318
.iter()
294319
.map(|(name, size, updated)| format!(
295320
r#"<tr><td><i class="fa-solid fa-file"></i></td> <td><a href="/{}">{}</a></td><td align="right">{}</td><td align="right">{}</td></tr>"#,
296321
name,
297-
name.split('/').last().unwrap_or(""),
322+
name.split('/').next_back().unwrap_or(""),
298323
size,
299324
updated
300325
))
301326
.collect::<String>()
302-
);
303-
return html;
327+
)
304328
}

0 commit comments

Comments
 (0)