1use perma_core::{Redirect, RedirectTo, RelativeRef};
2
3use worker::event;
4use worker::Method;
5use worker::{Context, Env, Request, Response, Result};
6
7#[event(fetch)]
8async fn fetch(mut req: Request, env: Env, _ctx: Context) -> Result<Response> {
9 let kv = env.kv("KV")?;
10 let psk = env.secret("PSK")?.to_string();
11 let url = req.url()?;
12
13 if url.path() == "/api/v1/_redirects" {
14 if req.headers().get("Authorization")? != Some(format!("Bearer sk-{}", psk)) {
15 return Response::error("unauthorized", 401);
16 }
17
18 if req.method() == Method::Get {
19 let accept = req.headers().get("Accept")?.unwrap_or_default();
20 match accept.as_str() {
21 "text/redirects" => {
22 if let Some(redirects) = kv.get("_redirects:raw").bytes().await? {
23 Response::builder()
24 .with_header("Content-Type", "application/octet-stream")?
25 .from_bytes(redirects)
26 } else {
27 Response::error("_redirects not found", 404)
28 }
29 }
30 "application/json" | _ => {
31 if let Some(redirects) = kv.get("_redirects").bytes().await? {
32 Response::builder()
33 .with_header("Content-Type", "application/application/json")?
34 .from_bytes(redirects)
35 } else {
36 Response::error("_redirects not found", 404)
37 }
38 }
39 }
40 } else if req.method() == Method::Put {
41 let ct = req.headers().get("Content-Type")?.unwrap_or_default();
42
43 match ct.as_str() {
44 "text/redirects" => {
45 let raw = req.text().await?;
46 match raw
47 .lines()
48 .map(|l| l.trim())
49 .filter(|l| !l.is_empty() && !l.starts_with('#'))
50 .map(|l| l.parse::<Redirect>().map_err(|e| (l, e)))
51 .collect::<Result<Vec<_>, _>>()
52 {
53 Ok(redirects) => {
54 kv.put("_redirects:raw", raw.as_str())?.execute().await?;
55 kv.put("_redirects", &redirects)?.execute().await?;
56 Response::empty()
57 }
58 Err((l, e)) => {
59 Response::error(format!("parsing \"{}\" failed: {}", l, e), 400)
60 }
61 }
62 }
63 "application/json" => {
64 let redirects = req.json::<Vec<Redirect>>().await?;
65 let raw = redirects
66 .iter()
67 .map(|r| format!("{} {} {}", r.from(), r.to(), r.code().as_u16()))
68 .collect::<Vec<_>>()
69 .join("\n");
70 kv.put("_redirects:raw", raw.as_str())?.execute().await?;
71 kv.put("_redirects", &redirects)?.execute().await?;
72 Response::empty()
73 }
74 _ => Response::error("bad request", 400),
75 }
76 } else {
77 Response::error("method not allowed", 405)
78 }
79 } else {
80 if let Some(redirects) = kv.get("_redirects").json::<Vec<Redirect>>().await? {
81 let lookup = url.path().trim_end_matches('/');
82 if let Some((redirect, rel)) = redirects.into_iter().find_map(|r| {
83 let path = r.from().path().trim_end_matches('/');
84 if path == lookup {
85 Some((r, None))
86 } else if let Some(path) = r.from().path().strip_suffix("*") {
87 if let Some(rel) = lookup.strip_prefix(path.trim_end_matches('/')) {
88 Some((r, Some(RelativeRef::parse(rel).unwrap())))
89 } else {
90 None
91 }
92 } else {
93 None
94 }
95 }) {
96 let mut to = match redirect.to() {
97 RedirectTo::Uri(uri) => uri.clone(),
98 RedirectTo::Path(path) => url.join(path.as_str())?,
99 };
100
101 if to.path_segments().and_then(|segs| segs.last()) == Some(":splat") {
102 if let Ok(mut segments) = to.path_segments_mut() {
103 segments.pop();
104
105 if let Some(rel) = rel {
106 for seg in rel.path_segments().into_iter().flatten() {
107 segments.push(seg);
108 }
109 }
110 }
111 }
112
113 Response::builder()
114 .with_status(redirect.code().as_u16())
115 .with_header("location", &to.to_string())?
116 .from_html(&format!(
117 "<a href=\"{}\">Redirecting...</a>",
118 to.to_string()
119 ))
120 } else {
121 Response::error("not found", 404)
122 }
123 } else {
124 Response::error("_redirects not found", 404)
125 }
126 }
127}