@@ -5,6 +5,7 @@ use google_cloud_storage::http::objects::list::ListObjectsRequest;
55use log:: { LevelFilter , error, info} ;
66use std:: convert:: Infallible ;
77use std:: env;
8+ use std:: net:: SocketAddr ;
89use time:: format_description;
910use warp:: http:: StatusCode ;
1011use warp:: { Filter , http:: Response } ;
@@ -27,17 +28,33 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
2728
2829 let client_filter = warp:: any ( ) . map ( move || client. clone ( ) ) ;
2930 let bucket_filter = warp:: any ( ) . map ( move || bucket_name. clone ( ) ) ;
31+ // Filter to get the client's IP address (SocketAddr)
32+ let remote_addr_filter = warp:: addr:: remote ( ) ;
33+
34+ // Filter to get the User-Agent header
35+ let user_agent_filter = warp:: header:: optional ( "user-agent" ) ;
3036
3137 let routes = warp:: path:: full ( )
38+ . and ( remote_addr_filter)
39+ . and ( user_agent_filter)
3240 . and ( client_filter)
3341 . and ( bucket_filter)
3442 . and_then ( serve_gcs_content) ;
3543
36- warp:: serve ( routes) . run ( ( [ 0 , 0 , 0 , 0 ] , 8585 ) ) . await ;
44+ warp:: serve ( routes) . run ( ( [ 0 , 0 , 0 , 0 ] , 8080 ) ) . await ;
3745
3846 Ok ( ( ) )
3947}
4048
49+ fn is_bot ( user_agent : & str ) -> bool {
50+ let ua = user_agent. to_lowercase ( ) ;
51+ ua. contains ( "bot" )
52+ || ua. contains ( "spider" )
53+ || ua. contains ( "scrap" )
54+ || ua. contains ( "googlebot" )
55+ || ua. contains ( "bingbot" )
56+ || ua. contains ( "http client" )
57+ }
4158async fn check_for_dirs (
4259 client : & Client ,
4360 bucket : String ,
@@ -74,18 +91,21 @@ async fn check_for_dirs(
7491 }
7592 }
7693 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())
8194}
8295
8396async fn serve_gcs_content (
8497 path : warp:: path:: FullPath ,
98+ remote_addr : Option < SocketAddr > ,
99+ user_agent : Option < String > ,
85100 client : Client ,
86101 bucket_name : String ,
87102) -> Result < impl warp:: Reply , Infallible > {
88103 let path_str = path. as_str ( ) . trim_start_matches ( '/' ) . to_string ( ) ;
104+ let client_ip = remote_addr
105+ . map ( |addr| addr. ip ( ) . to_string ( ) )
106+ . unwrap_or_else ( || "unknown" . to_string ( ) ) ;
107+ let user_agent = user_agent. unwrap_or_else ( || "none" . to_string ( ) ) ;
108+ let is_scrapper = is_bot ( user_agent. as_str ( ) ) ;
89109
90110 // The GCS API is a flat hierarchy. A "folder" is just an object prefix.
91111 let is_dir = path_str. ends_with ( '/' ) ;
@@ -110,7 +130,10 @@ async fn serve_gcs_content(
110130 object : path_str. clone ( ) ,
111131 ..Default :: default ( )
112132 } ;
113- info ! ( "DOWNLOAD_REQUEST: path='{}'" , path_str) ;
133+ info ! (
134+ "DOWNLOAD_REQUEST: path='{}', user-agent='{}', ip='{}', scrapper={}" ,
135+ path_str, user_agent, client_ip, is_scrapper
136+ ) ;
114137
115138 let response = match client. download_object ( & request, & Range :: default ( ) ) . await {
116139 Ok ( data) => {
@@ -130,10 +153,13 @@ async fn serve_gcs_content(
130153 "attachment"
131154 } ;
132155 info ! (
133- "DOWNLOAD_SUCCESS: file='{}', size={} bytes, disposition='{}'" ,
156+ "DOWNLOAD_SUCCESS: file='{}', size={} bytes, disposition='{}', user-agent='{}', ip='{}', scrapper={} " ,
134157 path_str,
135158 data. len( ) ,
136- disposition
159+ disposition,
160+ user_agent,
161+ client_ip,
162+ is_scrapper
137163 ) ;
138164 Response :: builder ( )
139165 . header (
@@ -144,7 +170,10 @@ async fn serve_gcs_content(
144170 . unwrap ( )
145171 }
146172 Err ( e) => {
147- error ! ( "DOWNLOAD_ERROR: file='{}', error='{:?}'" , path_str, e) ;
173+ error ! (
174+ "DOWNLOAD_ERROR: file='{}', user-agent='{}', ip='{}', scrapper={}, error='{:?}'" ,
175+ path_str, user_agent, client_ip, is_scrapper, e
176+ ) ;
148177 Response :: builder ( )
149178 . status ( 404 )
150179 . body ( "File not found" . as_bytes ( ) . to_vec ( ) )
@@ -162,7 +191,10 @@ async fn serve_gcs_content(
162191 Some ( format ! ( "{}/" , path_str. trim_end_matches( '/' ) ) )
163192 } ;
164193
165- info ! ( "LISTING_REQUEST: path='{}'" , path_str) ;
194+ info ! (
195+ "LISTING_REQUEST: path='{}', user-agent='{}', ip='{}', scrapper={}" ,
196+ path_str, user_agent, client_ip, is_scrapper
197+ ) ;
166198 let mut folders = Vec :: new ( ) ;
167199 let mut files = Vec :: new ( ) ;
168200 let mut next_page_token: Option < String > = None ;
@@ -223,11 +255,14 @@ async fn serve_gcs_content(
223255 next_page_token = objects. next_page_token ;
224256 }
225257 info ! (
226- "LISTING_SUCCESS: path='{}', folders={}, files={}, total={}" ,
258+ "LISTING_SUCCESS: path='{}', folders={}, files={}, total={}, user-agent='{}', ip='{}', scrapper={} " ,
227259 path_str,
228260 folders. len( ) ,
229261 files. len( ) ,
230- folders. len( ) + files. len( )
262+ folders. len( ) + files. len( ) ,
263+ user_agent,
264+ client_ip,
265+ is_scrapper
231266 ) ;
232267
233268 let html = build_html ( path_str, folders, files) ;
@@ -244,6 +279,7 @@ async fn serve_gcs_content(
244279 . body ( "Not Found" . as_bytes ( ) . to_vec ( ) )
245280 . unwrap ( ) )
246281}
282+
247283fn build_html (
248284 path_str : String ,
249285 folders : Vec < String > ,
@@ -317,8 +353,10 @@ fn build_html(
317353 files
318354 . iter( )
319355 . map( |( name, size, updated) | format!(
320- 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>"# ,
321- name,
356+ 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>"# ,
357+ // **CHANGE: Use only the file name for the relative HREF attribute**
358+ name. split( '/' ) . next_back( ) . unwrap_or( "" ) ,
359+ // Use only the file name for the display text
322360 name. split( '/' ) . next_back( ) . unwrap_or( "" ) ,
323361 size,
324362 updated
0 commit comments