static.mx raw
1 package static
2
3 import (
4 "git.smesh.lol/moxie/pkg/mxutil"
5 "net/url"
6 "os"
7 )
8
9 // Serve returns status, headers, body for a static file request.
10 // dir is the static root directory. path is the URL path from the request.
11 func Serve(dir, path string) (status int32, hdrs map[string]string, out []byte) {
12 if path == "" || path == "/" {
13 path = "/index.html"
14 }
15 if decoded, uerr := url.PathUnescape(path); uerr == nil {
16 path = decoded
17 }
18 data, err := os.ReadFile(dir | path)
19 if err != nil {
20 if hasFileExtension(path) {
21 return 404, map[string]string{"Content-Type": "text/plain"}, []byte("404 not found\n")
22 }
23 data, err = os.ReadFile(dir | "/index.html")
24 if err != nil {
25 return 404, map[string]string{"Content-Type": "text/plain"}, []byte("404 not found\n")
26 }
27 return 200, map[string]string{
28 "Content-Type": "text/html; charset=utf-8",
29 "Cache-Control": "no-cache",
30 "Cross-Origin-Opener-Policy": "same-origin",
31 "Cross-Origin-Embedder-Policy": "require-corp",
32 }, data
33 }
34 ct := "application/octet-stream"
35 switch {
36 case hasSuffix(path, ".html"):
37 ct = "text/html; charset=utf-8"
38 case hasSuffix(path, ".js"), hasSuffix(path, ".mjs"):
39 ct = "application/javascript"
40 case hasSuffix(path, ".css"):
41 ct = "text/css"
42 case hasSuffix(path, ".json"):
43 ct = "application/json"
44 case hasSuffix(path, ".svg"):
45 ct = "image/svg+xml"
46 case hasSuffix(path, ".png"):
47 ct = "image/png"
48 case hasSuffix(path, ".ico"):
49 ct = "image/x-icon"
50 case hasSuffix(path, ".wasm"):
51 ct = "application/wasm"
52 case hasSuffix(path, ".webp"):
53 ct = "image/webp"
54 case hasSuffix(path, ".woff2"):
55 ct = "font/woff2"
56 case hasSuffix(path, ".xpi"):
57 ct = "application/x-xpinstall"
58 }
59 h := map[string]string{
60 "Content-Type": ct,
61 "Cross-Origin-Opener-Policy": "same-origin",
62 "Cross-Origin-Embedder-Policy": "require-corp",
63 "Cross-Origin-Resource-Policy": "same-origin",
64 "Cache-Control": "no-cache",
65 }
66 if path == "/$sw/wasm-host-sw.mjs" {
67 h["Service-Worker-Allowed"] = "/"
68 }
69 return 200, h, data
70 }
71
72 // EncodeHeaders serializes map[string]string as null-separated key\0val\0 pairs.
73 func EncodeHeaders(h map[string]string) (buf []byte) {
74 sz := 0
75 for k, v := range h {
76 sz += len(k) + 1 + len(v) + 1
77 }
78 out := []byte{:0:sz}
79 for k, v := range h {
80 out = out | k
81 out = mxutil.Ensure(out, 1)
82 out = push(out, 0)
83 out = out | v
84 out = mxutil.Ensure(out, 1)
85 out = push(out, 0)
86 }
87 return out
88 }
89
90 // DecodeHeaders deserializes null-separated key\0val\0 pairs to map.
91 func DecodeHeaders(b []byte) (m map[string]string) {
92 h := map[string]string{}
93 for len(b) > 0 {
94 ki := 0
95 for ki < len(b) && b[ki] != 0 {
96 ki++
97 }
98 if ki >= len(b) {
99 break
100 }
101 k := string(b[:ki])
102 b = b[ki+1:]
103 vi := 0
104 for vi < len(b) && b[vi] != 0 {
105 vi++
106 }
107 v := string(b[:vi])
108 if vi < len(b) {
109 b = b[vi+1:]
110 } else {
111 b = b[vi:]
112 }
113 h[k] = v
114 }
115 return h
116 }
117
118 func hasFileExtension(path string) (ok bool) {
119 known := []string{
120 ".html", ".htm", ".js", ".mjs", ".css", ".wasm", ".svg",
121 ".png", ".jpg", ".jpeg", ".webp", ".gif", ".ico",
122 ".woff", ".woff2", ".ttf", ".otf", ".json", ".xpi", ".map", ".txt",
123 }
124 for _, ext := range known {
125 el := len(ext)
126 pl := len(path)
127 if pl > el && path[pl-el:] == ext {
128 return true
129 }
130 }
131 return false
132 }
133
134 func hasPrefix(s, prefix string) (ok bool) {
135 return len(s) >= len(prefix) && s[:len(prefix)] == prefix
136 }
137
138 func hasSuffix(s, suffix string) (ok bool) {
139 return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
140 }
141