diff options
author | EuAndreh <eu@euandre.org> | 2024-05-14 10:21:04 -0300 |
---|---|---|
committer | EuAndreh <eu@euandre.org> | 2024-05-14 10:21:04 -0300 |
commit | e10252708668b1b82ee4b8bca02c43fda3115c62 (patch) | |
tree | ba450ba549dbd836b263c7a302188460efc6603b | |
parent | Initial empty commit (diff) | |
download | glaze-e10252708668b1b82ee4b8bca02c43fda3115c62.tar.gz glaze-e10252708668b1b82ee4b8bca02c43fda3115c62.tar.xz |
Initial implementation
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile | 113 | ||||
-rw-r--r-- | deps.mk | 0 | ||||
-rw-r--r-- | go.mod | 8 | ||||
-rw-r--r-- | go.sum | 4 | ||||
-rwxr-xr-x | mkdeps.sh | 10 | ||||
-rw-r--r-- | src/cmd/main.go | 7 | ||||
-rw-r--r-- | src/lib.go | 187 | ||||
-rwxr-xr-x | tests/cli-opts.sh | 4 | ||||
-rw-r--r-- | tests/lib_test.go | 13 |
10 files changed, 348 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..85a298a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/*.bin +/tests/*.bin diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0893458 --- /dev/null +++ b/Makefile @@ -0,0 +1,113 @@ +.POSIX: +DATE = 1970-01-01 +VERSION = 0.1.0 +NAME = glaze +NAME_UC = $(NAME) +LANGUAGES = en +## Installation prefix. Defaults to "/usr". +PREFIX = /usr +BINDIR = $(PREFIX)/bin +LIBDIR = $(PREFIX)/lib +INCLUDEDIR = $(PREFIX)/include +SRCDIR = $(PREFIX)/src/$(NAME) +SHAREDIR = $(PREFIX)/share +LOCALEDIR = $(SHAREDIR)/locale +MANDIR = $(SHAREDIR)/man +EXEC = ./ +## Where to store the installation. Empty by default. +DESTDIR = +LDLIBS = + + + +.SUFFIXES: +.SUFFIXES: .go .bin + + + +all: +include deps.mk + + +sources = \ + src/lib.go \ + src/cmd/main.go \ + + +derived-assets = \ + $(NAME).bin \ + tests/lib_test.bin \ + +side-assets = \ + glaze.socket \ + + + +## Default target. Builds all artifacts required for testing +## and installation. +all: $(derived-assets) + + +$(NAME).bin: src/lib.go src/cmd/main.go Makefile + go build -o $@ -v src/cmd/main.go + +tests/lib_test.bin: src/lib.go tests/lib_test.go Makefile + go test -c -o $@ -v $*.go + + + +check-unit: tests/lib_test.bin + ./tests/lib_test.bin + + +integration-tests = \ + tests/cli-opts.sh \ + +$(integration-tests): $(NAME).bin ALWAYS + sh $@ + +check-integration: $(integration-tests) + + +## Run all tests. Each test suite is isolated, so that a parallel +## build can run tests at the same time. The required artifacts +## are created if missing. +check: check-unit check-integration + + + +## Remove *all* derived artifacts produced during the build. +## A dedicated test asserts that this is always true. +clean: + rm -rf $(derived-assets) $(side-assets) + + +## Installs into $(DESTDIR)$(PREFIX). Its dependency target +## ensures that all installable artifacts are crafted beforehand. +install: all + mkdir -p \ + '$(DESTDIR)$(BINDIR)' \ + + cp $(NAME).bin '$(DESTDIR)$(BINDIR)'/$(NAME) + for f in $(sources); do \ + dir='$(DESTDIR)$(SRCDIR)'/"`dirname "$${f#src/}"`"; \ + mkdir -p "$$dir"; \ + cp -P "$$f" "$$dir"; \ + done + +## Uninstalls from $(DESTDIR)$(PREFIX). This is a perfect mirror +## of the "install" target, and removes *all* that was installed. +## A dedicated test asserts that this is always true. +uninstall: + rm -rf \ + '$(DESTDIR)$(BINDIR)'/$(NAME) \ + '$(DESTDIR)$(SRCDIR)' \ + + +## Run it locally. +run: all + rm -f glaze.socket + ./$(NAME).bin -P '/api/socket*:retcp.socket' -P '*:../papo/src/static/' glaze.socket + + +ALWAYS: @@ -0,0 +1,8 @@ +module euandre.org/glaze + +go 1.21.5 + +require ( + github.com/pkg/xattr v0.4.9 // indirect + golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f // indirect +) @@ -0,0 +1,4 @@ +github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE= +github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= +golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f h1:8w7RhxzTVgUzw/AH/9mUV5q0vMgy40SQRursCcfmkCw= +golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/mkdeps.sh b/mkdeps.sh new file mode 100755 index 0000000..a6b23d5 --- /dev/null +++ b/mkdeps.sh @@ -0,0 +1,10 @@ +#!/bin/sh +set -eu + +export LANG=POSIX.UTF-8 + +varlist() { + printf '%s = \\\n' "$1" + sed 's|^\(.*\)$|\t\1 \\|' + printf '\n' +} diff --git a/src/cmd/main.go b/src/cmd/main.go new file mode 100644 index 0000000..8c2fbe3 --- /dev/null +++ b/src/cmd/main.go @@ -0,0 +1,7 @@ +package main + +import "euandre.org/glaze/src" + +func main() { + glaze.Main() +} diff --git a/src/lib.go b/src/lib.go new file mode 100644 index 0000000..6b486f5 --- /dev/null +++ b/src/lib.go @@ -0,0 +1,187 @@ +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/tests/cli-opts.sh b/tests/cli-opts.sh new file mode 100755 index 0000000..fcb62ca --- /dev/null +++ b/tests/cli-opts.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -eu + +exit diff --git a/tests/lib_test.go b/tests/lib_test.go new file mode 100644 index 0000000..31259c6 --- /dev/null +++ b/tests/lib_test.go @@ -0,0 +1,13 @@ +package lib_test + +import ( + "testing" + + "euandre.org/glaze/src" +) + +func TestPlaceholder(t *testing.T) { + if (&glaze.PatternPath{}).String() != "FIXME" { + t.Fail() + } +} |