Files
xevion.dev/src/utils.rs
Xevion dd1ce186d2 refactor: consolidate HTTP client for TCP/Unix socket handling
Extract reqwest client creation into dedicated HttpClient abstraction that handles both TCP and Unix socket connections transparently. Simplifies proxy logic by removing duplicate URL construction and client selection throughout the codebase.
2026-01-07 14:34:32 -06:00

130 lines
4.5 KiB
Rust

use axum::{
http::{HeaderMap, HeaderValue, StatusCode, header},
response::{IntoResponse, Response},
};
use crate::assets;
/// Check if a path represents a static asset
pub fn is_static_asset(path: &str) -> bool {
path.starts_with("/node_modules/")
|| path.starts_with("/@") // Vite internals like /@vite/client, /@fs/, /@id/
|| path.starts_with("/.svelte-kit/")
|| path.starts_with("/.well-known/")
|| path.ends_with(".woff2")
|| path.ends_with(".woff")
|| path.ends_with(".ttf")
|| path.ends_with(".ico")
|| path.ends_with(".png")
|| path.ends_with(".jpg")
|| path.ends_with(".svg")
|| path.ends_with(".webp")
|| path.ends_with(".css")
|| path.ends_with(".js")
|| path.ends_with(".map")
}
/// Check if a path represents a page route (not an asset)
pub fn is_page_route(path: &str) -> bool {
!path.starts_with("/node_modules/")
&& !path.starts_with("/@")
&& !path.starts_with("/.svelte-kit/")
&& !path.contains('.')
}
/// Check if the request accepts HTML responses
pub fn accepts_html(headers: &HeaderMap) -> bool {
if let Some(accept) = headers.get(header::ACCEPT) {
if let Ok(accept_str) = accept.to_str() {
return accept_str.contains("text/html") || accept_str.contains("*/*");
}
}
// Default to true for requests without Accept header (browsers typically send it)
true
}
/// Determines if request prefers raw content (CLI tools) over HTML
pub fn prefers_raw_content(headers: &HeaderMap) -> bool {
// Check User-Agent for known CLI tools first (most reliable)
if let Some(ua) = headers.get(header::USER_AGENT) {
if let Ok(ua_str) = ua.to_str() {
let ua_lower = ua_str.to_lowercase();
if ua_lower.starts_with("curl/")
|| ua_lower.starts_with("wget/")
|| ua_lower.starts_with("httpie/")
|| ua_lower.contains("curlie")
{
return true;
}
}
}
// Check Accept header - if it explicitly prefers text/html, serve HTML
if let Some(accept) = headers.get(header::ACCEPT) {
if let Ok(accept_str) = accept.to_str() {
// If text/html appears before */* in the list, they prefer HTML
if let Some(html_pos) = accept_str.find("text/html") {
if let Some(wildcard_pos) = accept_str.find("*/*") {
return html_pos > wildcard_pos;
}
// Has text/html but no */* → prefers HTML
return false;
}
// Has */* but no text/html → probably a CLI tool
if accept_str.contains("*/*") && !accept_str.contains("text/html") {
return true;
}
}
}
// No Accept header → assume browser (safer default)
false
}
/// Serve a prerendered error page for the given status code
pub fn serve_error_page(status: StatusCode) -> Response {
let status_code = status.as_u16();
if let Some(html) = assets::get_error_page(status_code) {
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
HeaderValue::from_static("text/html; charset=utf-8"),
);
headers.insert(
header::CACHE_CONTROL,
HeaderValue::from_static("no-cache, no-store, must-revalidate"),
);
(status, headers, html).into_response()
} else {
// Fallback for undefined error codes (500 generic page)
tracing::warn!(
status_code,
"No prerendered error page found for status code - using fallback"
);
if let Some(fallback_html) = assets::get_error_page(500) {
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
HeaderValue::from_static("text/html; charset=utf-8"),
);
headers.insert(
header::CACHE_CONTROL,
HeaderValue::from_static("no-cache, no-store, must-revalidate"),
);
(status, headers, fallback_html).into_response()
} else {
// Last resort: plaintext (should never happen if 500.html exists)
(status, format!("Error {}", status_code)).into_response()
}
}
}
/// Validate hex color format (6 characters, no hash, no alpha)
pub fn validate_hex_color(color: &str) -> bool {
color.len() == 6 && color.chars().all(|c| c.is_ascii_hexdigit())
}