Files
guara/crates/guara-core/src/http.rs
T
Lucas RayanandClaude Sonnet 5.5 97f940f261
Release / Cria a release (push) Canceled after 0s
Release / CLI (linux-aarch64) (push) Canceled after 0s
Release / CLI (linux-x86_64) (push) Canceled after 0s
Release / CLI (macos-aarch64) (push) Canceled after 0s
Release / CLI (macos-x86_64) (push) Canceled after 0s
Release / CLI (windows-x86_64) (push) Canceled after 0s
Release / App desktop (linux-x86_64) (push) Canceled after 0s
Release / App desktop (macos-aarch64) (push) Canceled after 0s
Release / App desktop (macos-x86_64) (push) Canceled after 0s
Release / App desktop (windows-x86_64) (push) Canceled after 0s
Release / Publica a release (push) Canceled after 0s
fix: vídeo no app Linux travava a janela; abrir no player do sistema (0.3.2)
O AppImage traz o WebKitGTK sem os plugins de mídia do GStreamer; criar um <video>
derrubava o processo da página. O app Linux não cria mais <video>/<audio>: oferece
"Abrir no player do sistema". Clicar no preview abre a visualização grande.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 18:09:28 -03:00

104 lines
3.9 KiB
Rust

//! Rotas HTTP de mídia (`/file` com Range e `/thumb`) compartilhadas pelo modo `guara ui`.
use crate::service::Engine;
use tiny_http::{Header, Method, Request, Response, StatusCode};
pub fn header(name: &str, value: &str) -> Header {
Header::from_bytes(name.as_bytes(), value.as_bytes()).unwrap()
}
/// O cabeçalho Host bate com um dos endereços esperados (defesa contra DNS rebinding)?
pub fn host_ok(req: &Request, hosts: &[String]) -> bool {
req.headers()
.iter()
.any(|h| h.field.equiv("Host") && hosts.iter().any(|x| x == h.value.as_str()))
}
/// Responde `/file/<job>/<id>` (com Range) e `/thumb/<job>/<id>?s=NNN`.
/// Devolve a requisição quando ela não é uma rota de mídia.
pub fn handle_media(eng: &Engine, token: &str, req: Request) -> Option<Request> {
let full = req.url().to_string();
let (path, query) = full.split_once('?').unwrap_or((full.as_str(), ""));
let is_file = path.starts_with("/file/");
let is_thumb = path.starts_with("/thumb/");
if !(is_file || is_thumb) {
return Some(req);
}
if req.method() == &Method::Options {
let r = Response::empty(204)
.with_header(header("Access-Control-Allow-Origin", "*"))
.with_header(header("Access-Control-Allow-Headers", "Range"))
.with_header(header("Access-Control-Allow-Methods", "GET, OPTIONS"));
let _ = req.respond(r);
return None;
}
if req.method() != &Method::Get {
let _ = req.respond(Response::from_string("método não permitido").with_status_code(405));
return None;
}
if !query
.split('&')
.any(|kv| kv.strip_prefix("t=") == Some(token))
{
let _ = req.respond(Response::from_string("token inválido").with_status_code(401));
return None;
}
let mut it = path
.trim_start_matches("/file/")
.trim_start_matches("/thumb/")
.split('/');
let (job, id) = (
it.next().unwrap_or(""),
it.next().and_then(|s| s.parse::<u64>().ok()),
);
let Some(id) = id else {
let _ = req.respond(Response::from_string("id inválido").with_status_code(400));
return None;
};
if is_thumb {
let size = query
.split('&')
.find_map(|kv| kv.strip_prefix("s="))
.and_then(|v| v.parse::<u32>().ok())
.unwrap_or(256);
match eng.thumb(job, id, size) {
Ok(bytes) => {
let r = Response::from_data(bytes.as_ref().clone())
.with_header(header("Content-Type", "image/jpeg"))
.with_header(header("Cache-Control", "private, max-age=3600"))
.with_header(header("Access-Control-Allow-Origin", "*"));
let _ = req.respond(r);
}
Err(code) => {
let _ = req.respond(
Response::from_string("sem miniatura")
.with_status_code(code)
.with_header(header("Access-Control-Allow-Origin", "*")),
);
}
}
return None;
}
let range = req
.headers()
.iter()
.find(|h| h.field.equiv("Range"))
.map(|h| h.value.as_str().to_string());
match eng.file_slice(job, id, range.as_deref()) {
Ok(slice) => {
let (status, len) = (slice.status(), slice.len() as usize);
let headers: Vec<Header> = slice.headers().iter().map(|(k, v)| header(k, v)).collect();
let resp = Response::new(StatusCode(status), headers, slice.reader(), Some(len), None);
let _ = req.respond(resp);
}
Err(code) => {
let _ = req.respond(
Response::from_string("indisponível")
.with_status_code(code)
.with_header(header("Access-Control-Allow-Origin", "*")),
);
}
}
None
}