Like in GitLab Pages, can bypass html extensions
This commit is contained in:
		
							
								
								
									
										99
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										99
									
								
								src/main.rs
									
									
									
									
									
								
							| @@ -42,6 +42,10 @@ struct Args { | ||||
|     /// Optional proxy IP | ||||
|     #[clap(short, long, env)] | ||||
|     proxy_ip: Option<String>, | ||||
|  | ||||
|     /// Specify whether HTML extensions can be bypassed | ||||
|     #[clap(short, long, env)] | ||||
|     can_bypass_html_ext: bool, | ||||
| } | ||||
|  | ||||
| impl Args { | ||||
| @@ -52,7 +56,7 @@ impl Args { | ||||
|     pub fn default_handler_path(&self) -> Option<PathBuf> { | ||||
|         match self.not_found_file.is_empty() { | ||||
|             true => None, | ||||
|             false => Some(self.storage_path().join(&self.not_found_file)) | ||||
|             false => Some(self.storage_path().join(&self.not_found_file)), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -75,7 +79,6 @@ pub fn match_ip(pattern: &str, ip: &str) -> bool { | ||||
|     false | ||||
| } | ||||
|  | ||||
|  | ||||
| /// Get the remote IP address | ||||
| fn get_remote_ip(req: &HttpRequest, args: &Args) -> String { | ||||
|     let mut ip = req.peer_addr().unwrap().ip().to_string(); | ||||
| @@ -102,14 +105,19 @@ fn get_remote_ip(req: &HttpRequest, args: &Args) -> String { | ||||
|     ip | ||||
| } | ||||
|  | ||||
|  | ||||
| /// Replace all the files of the website | ||||
| async fn replace_files(args: web::Data<Args>, req: HttpRequest, mut payload: Multipart) -> Result<HttpResponse, Error> { | ||||
|  | ||||
| async fn replace_files( | ||||
|     args: web::Data<Args>, | ||||
|     req: HttpRequest, | ||||
|     mut payload: Multipart, | ||||
| ) -> Result<HttpResponse, Error> { | ||||
|     // Validate remote IP | ||||
|     let remote_ip = get_remote_ip(&req, &args); | ||||
|     if !match_ip(&args.allowed_ips_for_update, &remote_ip) { | ||||
|         log::warn!("Block unauthorized attempt to perform site update from {}", remote_ip); | ||||
|         log::warn!( | ||||
|             "Block unauthorized attempt to perform site update from {}", | ||||
|             remote_ip | ||||
|         ); | ||||
|         return Err(ErrorUnauthorized("You are not allowed to perform updates!")); | ||||
|     } | ||||
|  | ||||
| @@ -118,8 +126,9 @@ async fn replace_files(args: web::Data<Args>, req: HttpRequest, mut payload: Mul | ||||
|         None => { | ||||
|             return Err(ErrorUnauthorized("Token required!")); | ||||
|         } | ||||
|         Some(t) => t.to_str() | ||||
|             .map_err(|_| ErrorInternalServerError("Failed to parse token!"))? | ||||
|         Some(t) => t | ||||
|             .to_str() | ||||
|             .map_err(|_| ErrorInternalServerError("Failed to parse token!"))?, | ||||
|     }; | ||||
|  | ||||
|     if !token.eq(&args.update_token) || args.update_token.is_empty() { | ||||
| @@ -129,8 +138,9 @@ async fn replace_files(args: web::Data<Args>, req: HttpRequest, mut payload: Mul | ||||
|     // Get base folder to keep from tar-file | ||||
|     let base_uri = match req.headers().get("BaseURI") { | ||||
|         None => "/", | ||||
|         Some(t) => t.to_str() | ||||
|             .map_err(|_| ErrorInternalServerError("Failed to parse base URI to keep!"))? | ||||
|         Some(t) => t | ||||
|             .to_str() | ||||
|             .map_err(|_| ErrorInternalServerError("Failed to parse base URI to keep!"))?, | ||||
|     }; | ||||
|  | ||||
|     let mut new_files = Vec::new(); | ||||
| @@ -145,8 +155,10 @@ async fn replace_files(args: web::Data<Args>, req: HttpRequest, mut payload: Mul | ||||
|         } | ||||
|  | ||||
|         let mut archive = tar::Archive::new(b.as_ref()); | ||||
|         for entry in archive.entries() | ||||
|             .map_err(|_| ErrorInternalServerError("Failed to parse TAR archive!"))? { | ||||
|         for entry in archive | ||||
|             .entries() | ||||
|             .map_err(|_| ErrorInternalServerError("Failed to parse TAR archive!"))? | ||||
|         { | ||||
|             let mut file = entry?; | ||||
|             let inner_path = file.header().path()?; | ||||
|             let inner_path_str = inner_path.to_string_lossy(); | ||||
| @@ -205,6 +217,41 @@ async fn replace_files(args: web::Data<Args>, req: HttpRequest, mut payload: Mul | ||||
|     Ok(HttpResponse::Ok().into()) | ||||
| } | ||||
|  | ||||
| async fn default_files_handler(req: ServiceRequest) -> Result<ServiceResponse, Error> { | ||||
|     let (req, _) = req.into_parts(); | ||||
|     let args: &web::Data<Args> = req.app_data().unwrap(); | ||||
|  | ||||
|     // Search for alternate paths | ||||
|     if args.can_bypass_html_ext | ||||
|         && !req.path().ends_with(".html") | ||||
|         && !req.path().ends_with("/") | ||||
|         && !req.path().is_empty() { | ||||
|         let alt_file = args.storage_path() | ||||
|             .join(format!("{}.html", &req.path()[1..])); | ||||
|  | ||||
|         if alt_file.exists() { | ||||
|             let file = NamedFile::open_async(alt_file).await?; | ||||
|             let res = file.into_response(&req); | ||||
|             return Ok(ServiceResponse::new(req, res)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Default handler | ||||
|     if let Some(h) = args.default_handler_path() { | ||||
|         let file = NamedFile::open_async(h).await?; | ||||
|         let res = file.into_response(&req); | ||||
|         Ok(ServiceResponse::new(req, res)) | ||||
|     } | ||||
|  | ||||
|     // Dummy response | ||||
|     else { | ||||
|         Ok(ServiceResponse::new( | ||||
|             req, | ||||
|             HttpResponse::NotFound().body("404 Not found"), | ||||
|         )) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[actix_web::main] | ||||
| async fn main() -> std::io::Result<()> { | ||||
|     let args: Args = Args::parse(); | ||||
| @@ -219,33 +266,17 @@ async fn main() -> std::io::Result<()> { | ||||
|  | ||||
|     HttpServer::new(move || { | ||||
|         App::new() | ||||
|  | ||||
|             // Update service | ||||
|             .service(web::resource("/_mgmt/replace_files") | ||||
|                 .route(web::post().to(replace_files))) | ||||
|  | ||||
|             .service(web::resource("/_mgmt/replace_files").route(web::post().to(replace_files))) | ||||
|             // Serve a tree of static files at the web root and specify the index file. | ||||
|             // Note that the root path should always be defined as the last item. The paths are | ||||
|             // resolved in the order they are defined. If this would be placed before the `/images` | ||||
|             // path then the service for the static images would never be reached. | ||||
|             .service(Files::new("/", &args.files_path) | ||||
|  | ||||
|                 .index_file(&args.index_file) | ||||
|  | ||||
|                 .default_handler(fn_service(|req: ServiceRequest| async { | ||||
|                     let (req, _) = req.into_parts(); | ||||
|                     let args: &web::Data<Args> = req.app_data().unwrap(); | ||||
|                     if let Some(h) = args.default_handler_path() { | ||||
|                         let file = NamedFile::open_async(h).await?; | ||||
|                         let res = file.into_response(&req); | ||||
|                         Ok(ServiceResponse::new(req, res)) | ||||
|                     } else { | ||||
|                         Ok(ServiceResponse::new(req, HttpResponse::NotFound() | ||||
|                             .body("404 Not found"))) | ||||
|                     } | ||||
|                 })) | ||||
|             .service( | ||||
|                 Files::new("/", &args.files_path) | ||||
|                     .index_file(&args.index_file) | ||||
|                     .default_handler(fn_service(default_files_handler)), | ||||
|             ) | ||||
|  | ||||
|             // Enable the logger. | ||||
|             .wrap(Logger::default()) | ||||
|             .app_data(web::Data::new(args.clone())) | ||||
| @@ -253,4 +284,4 @@ async fn main() -> std::io::Result<()> { | ||||
|         .bind(listen_address)? | ||||
|         .run() | ||||
|         .await | ||||
| } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user