Skip to main content

perma_worker/
lib.rs

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}