summaryrefslogtreecommitdiff
path: root/src/glaze.go
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2024-08-10 21:02:25 -0300
committerEuAndreh <eu@euandre.org>2024-08-10 21:02:25 -0300
commitb03960fd93e5a1039754321a3562d48483801191 (patch)
tree65a8a41ebd07f3a46d8939863bfd4850ae4ae403 /src/glaze.go
parentInitial implementation (diff)
downloadglaze-b03960fd93e5a1039754321a3562d48483801191.tar.gz
glaze-b03960fd93e5a1039754321a3562d48483801191.tar.xz
Build with "go tool" and remove uneeded dependencies
Diffstat (limited to 'src/glaze.go')
-rw-r--r--src/glaze.go277
1 files changed, 277 insertions, 0 deletions
diff --git a/src/glaze.go b/src/glaze.go
new file mode 100644
index 0000000..d0bc3e0
--- /dev/null
+++ b/src/glaze.go
@@ -0,0 +1,277 @@
+package glaze
+
+import (
+ "context"
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "io/fs"
+ "net"
+ "net/http"
+ "net/url"
+ "os"
+ "strings"
+ "syscall"
+
+
+ g "gobang"
+)
+
+
+
+type patternPath struct {}
+
+type pathInfo struct {
+ pattern string
+ path string
+ label string
+ fileHandle *os.File
+ fileInfo fs.FileInfo
+}
+
+
+
+const (
+ xattrTryIndexHtml = "user.glaze.try-index-html"
+ xattrDirectoryListing = "user.glaze.directory-listing"
+ xattrExpectedValue = "true"
+)
+
+
+
+func httpError(w http.ResponseWriter, code int, err error) {
+ t := http.StatusText(code)
+ g.Error(
+ err.Error(), "http-server-error",
+ "code", code,
+ "error", err,
+ "status-text", t,
+ )
+ http.Error(w, t, code)
+}
+
+func withTrailingSlash(path string) string {
+ if strings.HasSuffix(path, "/") {
+ return path
+ } else {
+ return path + "/"
+ }
+}
+
+func adjustPattern(pattern string) string {
+ if pattern == "*" {
+ return "/"
+ } else if strings.HasSuffix(pattern, "*") {
+ return pattern[0:len(pattern) - 1]
+ } else {
+ return pattern + "{$}"
+ }
+}
+
+func handleSymlink(info pathInfo, w http.ResponseWriter, r *http.Request) error {
+ linked, err := os.Readlink(info.path)
+ if err != nil {
+ return err
+ }
+
+ // From http.Redirect():
+ //
+ // > If the Content-Type header has not been set,
+ // > Redirect sets it to "text/html; charset=utf-8" and
+ // > writes a small HTML body.
+ //
+ // 🙄
+ w.Header().Set("Content-Type", "text/plain; charset=utf-8")
+
+ http.Redirect(w, r, linked, http.StatusMovedPermanently)
+ return nil
+}
+
+func handleProxy(info pathInfo, w http.ResponseWriter, r *http.Request) error {
+ target, err := url.Parse(info.path)
+ if err != nil {
+ return err
+ }
+
+ target.Scheme = "http"
+ target.Host = "localhost"
+ httpClient := http.Client {
+ Transport: &http.Transport {
+ DialContext: func(_ context.Context, _ string, _ string) (net.Conn, error) {
+ return net.Dial("unix", info.path)
+ },
+ },
+ }
+
+ r.URL.Scheme = target.Scheme
+ r.URL.Host = target.Host
+ r.RequestURI = ""
+ response, err := httpClient.Do(r)
+ if err != nil {
+ return err
+ }
+ for k, vArr := range response.Header {
+ for _, v := range vArr {
+ w.Header().Add(k, v)
+ }
+ }
+ w.WriteHeader(response.StatusCode)
+ io.Copy(w, response.Body)
+ return nil
+}
+
+func handleFile(info pathInfo, w http.ResponseWriter, r *http.Request) error {
+ http.ServeContent(w, r, info.fileInfo.Name(), info.fileInfo.ModTime(), info.fileHandle)
+ return nil
+}
+
+func getXattr(path string, name string) (string, error) {
+ data := make([]byte, 256)
+ i, err := syscall.Getxattr(path, name, data)
+ fmt.Println("i", i)
+ if err != nil {
+ return "", err
+ }
+ return string(data[0:i]), nil
+}
+
+func handleDirectoryTarget(info pathInfo, w http.ResponseWriter, r *http.Request) error {
+ targetPath := withTrailingSlash(info.path) + r.URL.Path
+ newInfo, fn, err := handlerForDynPath(targetPath)
+ if err != nil {
+ return err
+ }
+
+ if (newInfo.label != "directory") {
+ return fn(newInfo, w, r)
+ }
+
+ indexHtmlXattr, err := getXattr(targetPath, xattrTryIndexHtml)
+ if err == nil && string(indexHtmlXattr) == xattrExpectedValue {
+ newPath := withTrailingSlash(targetPath) + "index.html"
+ _, err := os.Open(newPath)
+ if err == nil {
+ return fn(newInfo, w, r)
+ }
+ }
+
+ dirListXattr, err := getXattr(targetPath, xattrDirectoryListing)
+ if err == nil && string(dirListXattr) == xattrExpectedValue {
+ http.FileServer(http.Dir(newInfo.path)).ServeHTTP(w, r)
+ return nil
+ }
+
+ http.Error(w, "Forbidden", http.StatusForbidden)
+ return nil
+}
+
+func handlerFuncFor(mode fs.FileMode) (string, func(pathInfo, http.ResponseWriter, *http.Request) error) {
+ if mode.Type() == fs.ModeSymlink {
+ return "symlink", handleSymlink
+ } else if mode.Type() == fs.ModeSocket {
+ return "socket", handleProxy
+ } else if !mode.IsDir() {
+ return "file", handleFile
+ } else {
+ return "directory", handleDirectoryTarget
+ }
+}
+
+func handlerForDynPath(path string) (pathInfo, func(pathInfo, http.ResponseWriter, *http.Request) error, error) {
+ var info pathInfo
+ fileHandle, err := os.Open(path)
+ if err != nil {
+ return info, nil, err
+ }
+
+ fileInfo, err := os.Lstat(path)
+ if err != nil {
+ return info, nil, err
+ }
+
+ label, fn := handlerFuncFor(fileInfo.Mode())
+ g.Info("Handler picked", "handler-picked", "label", label)
+ info = pathInfo {
+ path: path,
+ label: label,
+ fileHandle: fileHandle,
+ fileInfo: fileInfo,
+ }
+ return info, fn, nil
+}
+
+func handlerFor(path string) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ id := g.NewUUID()
+ g.Info("in-request", "in-request", "path", path, "id", id)
+ defer g.Info("in-response", "in-response", "path", path, "id", id)
+
+ info, fn, err := handlerForDynPath(path)
+ if err != nil {
+ httpError(w, http.StatusInternalServerError, err)
+ return
+ }
+
+ err = fn(info, w, r)
+ if err != nil {
+ httpError(w, http.StatusInternalServerError, err)
+ return
+ }
+ })
+}
+
+func (i *patternPath) String() string {
+ return ""
+}
+
+func (_ *patternPath) Set(value string) error {
+ arr := strings.Split(value, ":")
+ if len(arr) != 2 {
+ return errors.New("Bad value for path pattern: " + value)
+ }
+
+ pattern := adjustPattern(arr[0])
+ path := arr[1]
+
+ http.Handle(pattern, http.StripPrefix(pattern, handlerFor(path)))
+ return nil
+}
+
+func parseArgs(args []string) string {
+ var pat patternPath
+ fs := flag.NewFlagSet(args[0], flag.ExitOnError)
+ fs.Var(&pat, "P", "")
+ fs.Parse(args[1:])
+ if fs.NArg() != 1 {
+ fmt.Fprintf(
+ os.Stderr,
+ "Usage: %s [ -P PATTERN:PATH ]... LISTEN.socket\n",
+ args[0],
+ )
+ os.Exit(2)
+ }
+ return fs.Arg(0)
+}
+
+func listen(fromAddr string) net.Listener {
+ listener, err := net.Listen("unix", fromAddr)
+ g.FatalIf(err)
+ g.Info("Started listening", "listen-start", "from-address", fromAddr)
+ return listener
+}
+
+func start(listener net.Listener) {
+ server := http.Server {}
+ err := server.Serve(listener)
+ g.FatalIf(err)
+}
+
+
+
+func Main() {
+ g.Init()
+ addr := parseArgs(os.Args)
+ listener := listen(addr)
+ start(listener)
+}