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) } }