use perma_core::{Redirect, RedirectTo, RelativeRef};
use worker::event;
use worker::Method;
use worker::{Context, Env, Request, Response, Result};
#[event(fetch)]
async fn fetch(mut req: Request, env: Env, _ctx: Context) -> Result<Response> {
let kv = env.kv("KV")?;
let psk = env.secret("PSK")?.to_string();
let url = req.url()?;
if url.path() == "/api/v1/_redirects" {
if req.headers().get("Authorization")? != Some(format!("Bearer sk-{}", psk)) {
return Response::error("unauthorized", 401);
}
if req.method() == Method::Get {
let accept = req.headers().get("Accept")?.unwrap_or_default();
match accept.as_str() {
"text/redirects" => {
if let Some(redirects) = kv.get("_redirects:raw").bytes().await? {
Response::builder()
.with_header("Content-Type", "application/octet-stream")?
.from_bytes(redirects)
} else {
Response::error("_redirects not found", 404)
}
}
"application/json" | _ => {
if let Some(redirects) = kv.get("_redirects").bytes().await? {
Response::builder()
.with_header("Content-Type", "application/application/json")?
.from_bytes(redirects)
} else {
Response::error("_redirects not found", 404)
}
}
}
} else if req.method() == Method::Put {
let ct = req.headers().get("Content-Type")?.unwrap_or_default();
match ct.as_str() {
"text/redirects" => {
let raw = req.text().await?;
match raw
.lines()
.map(|l| l.trim())
.filter(|l| !l.is_empty() && !l.starts_with('#'))
.map(|l| l.parse::<Redirect>().map_err(|e| (l, e)))
.collect::<Result<Vec<_>, _>>()
{
Ok(redirects) => {
kv.put("_redirects:raw", raw.as_str())?.execute().await?;
kv.put("_redirects", &redirects)?.execute().await?;
Response::empty()
}
Err((l, e)) => {
Response::error(format!("parsing \"{}\" failed: {}", l, e), 400)
}
}
}
"application/json" => {
let redirects = req.json::<Vec<Redirect>>().await?;
let raw = redirects
.iter()
.map(|r| format!("{} {} {}", r.from(), r.to(), r.code().as_u16()))
.collect::<Vec<_>>()
.join("\n");
kv.put("_redirects:raw", raw.as_str())?.execute().await?;
kv.put("_redirects", &redirects)?.execute().await?;
Response::empty()
}
_ => Response::error("bad request", 400),
}
} else {
Response::error("method not allowed", 405)
}
} else {
if let Some(redirects) = kv.get("_redirects").json::<Vec<Redirect>>().await? {
let lookup = url.path().trim_end_matches('/');
if let Some((redirect, rel)) = redirects.into_iter().find_map(|r| {
let path = r.from().path().trim_end_matches('/');
if path == lookup {
Some((r, None))
} else if let Some(path) = r.from().path().strip_suffix("*") {
if let Some(rel) = lookup.strip_prefix(path.trim_end_matches('/')) {
Some((r, Some(RelativeRef::parse(rel).unwrap())))
} else {
None
}
} else {
None
}
}) {
let mut to = match redirect.to() {
RedirectTo::Uri(uri) => uri.clone(),
RedirectTo::Path(path) => url.join(path.as_str())?,
};
if to.path_segments().and_then(|segs| segs.last()) == Some(":splat") {
if let Ok(mut segments) = to.path_segments_mut() {
segments.pop();
if let Some(rel) = rel {
for seg in rel.path_segments().into_iter().flatten() {
segments.push(seg);
}
}
}
}
Response::builder()
.with_status(redirect.code().as_u16())
.with_header("location", &to.to_string())?
.from_html(&format!(
"<a href=\"{}\">Redirecting...</a>",
to.to_string()
))
} else {
Response::error("not found", 404)
}
} else {
Response::error("_redirects not found", 404)
}
}
}