summaryrefslogtreecommitdiff
path: root/src
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
parentInitial implementation (diff)
downloadglaze-b03960fd93e5a1039754321a3562d48483801191.tar.gz
glaze-b03960fd93e5a1039754321a3562d48483801191.tar.xz
Build with "go tool" and remove uneeded dependencies
Diffstat (limited to 'src')
-rw-r--r--src/glaze.go277
-rw-r--r--src/lib.go187
-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()