static.mx raw

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