diff options
author | EuAndreh <eu@euandre.org> | 2024-08-10 21:02:25 -0300 |
---|---|---|
committer | EuAndreh <eu@euandre.org> | 2024-08-10 21:02:25 -0300 |
commit | b03960fd93e5a1039754321a3562d48483801191 (patch) | |
tree | 65a8a41ebd07f3a46d8939863bfd4850ae4ae403 /src | |
parent | Initial implementation (diff) | |
download | glaze-b03960fd93e5a1039754321a3562d48483801191.tar.gz glaze-b03960fd93e5a1039754321a3562d48483801191.tar.xz |
Build with "go tool" and remove uneeded dependencies
Diffstat (limited to 'src')
-rw-r--r-- | src/glaze.go | 277 | ||||
-rw-r--r-- | src/lib.go | 187 | ||||
-rw-r--r-- | src/main.go (renamed from src/cmd/main.go) | 2 |
3 files changed, 278 insertions, 188 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) +} diff --git a/src/lib.go b/src/lib.go deleted file mode 100644 index 6b486f5..0000000 --- a/src/lib.go +++ /dev/null @@ -1,187 +0,0 @@ -package glaze - -import ( - "context" - "errors" - "flag" - "io" - "io/fs" - "log" - "net" - "net/http" - "net/url" - "os" - "strings" - - "github.com/pkg/xattr" -) - - - -const TRY_INDEX_HTML_XATTR = "user.glaze.directory-listing" - - -type PatternPath struct {} - -func (i *PatternPath) String() string { - return "FIXME" -} - -func proxyHandler(path string) http.Handler { - target, err := url.Parse(path) - if err != nil { - log.Fatal(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", path) - }, - }, - } - - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - r.URL.Scheme = target.Scheme - r.URL.Host = target.Host - r.RequestURI = "" - - response, err := httpClient.Do(r) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - log.Println(err) - return - } - - for k, vArr := range response.Header { - for _, v := range vArr { - w.Header().Add(k, v) - } - } - w.WriteHeader(response.StatusCode) - io.Copy(w, response.Body) - }) -} - -func fileHandler(path string) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fileHandle, err := os.Open(path) - if err != nil { - http.Error(w, "Not Founde", http.StatusNotFound) - return - } - - fileInfo, err := fileHandle.Stat() - if err != nil { - http.Error(w, "Server Errore", http.StatusInternalServerError) - return - } - - http.ServeContent(w, r, fileInfo.Name(), fileInfo.ModTime(), fileHandle) - }) -} - -func directoryHandler(path string) http.Handler { - // FIXME: return 301/302 on symlink - return http.FileServer(http.Dir(path)) -} - -func adjustPattern(pattern string) string { - if pattern == "*" { - return "/" - } else if strings.HasSuffix(pattern, "*") { - return pattern[0:len(pattern) - 1] - } else { - return pattern - // FIXME: when on Go 1.22, use: - // return pattern + "{$}" - } -} - -func registerHandler(tag string, pattern string, handler http.Handler) { - http.Handle( - pattern, - logged( - tag, - http.StripPrefix( - pattern, - handler, - ), - ), - ) -} - -func registerPattern(fileInfo fs.FileInfo, pattern string, path string) { - if fileInfo.Mode().Type() == fs.ModeSocket { - registerHandler("proxy", pattern, proxyHandler(path)) - } else if !fileInfo.Mode().IsDir() { - registerHandler("file", pattern, fileHandler(path)) - } else { - registerHandler("directory", pattern, directoryHandler(path)) - - data, err := xattr.Get(path, TRY_INDEX_HTML_XATTR) - if err != nil { - log.Fatal(err) - } - if string(data) == "true" { - indexPattern := pattern - if !strings.HasSuffix(indexPattern, "/") { - indexPattern += "/" - } - indexPattern += "index.html" - registerHandler("index-html-file", indexPattern, fileHandler(path + "/index.html")) - } - } -} - -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] - - fileInfo, err := os.Stat(path) - if err != nil { - return err - } - - registerPattern(fileInfo, pattern, path) - return nil -} - -func logged(tag string, next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Printf("%s: %s %s\n", tag, r.Method, r.URL.Path) - next.ServeHTTP(w, r) - }) -} - -// FIXME: log to json -func Main() { - var myFlags PatternPath - flag.Var(&myFlags, "P", "URL:PATH pattern") - flag.Parse() - - if flag.NArg() != 1 { - flag.Usage() - os.Exit(2) - } - listenPath := flag.Arg(0) - - listener, err := net.Listen("unix", listenPath) - if err != nil { - log.Fatal("Failed to net.listen(): " + err.Error()) - } - - server := http.Server {} - err = server.Serve(listener) - if err != nil { - log.Fatal(err) - } -} diff --git a/src/cmd/main.go b/src/main.go index 8c2fbe3..15f5d79 100644 --- a/src/cmd/main.go +++ b/src/main.go @@ -1,6 +1,6 @@ package main -import "euandre.org/glaze/src" +import "glaze" func main() { glaze.Main() |