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