From 9648b06c631581b506fd4e47d7673f8b7682c308 Mon Sep 17 00:00:00 2001 From: Patrick Hener Date: Thu, 27 Jun 2024 14:42:02 +0200 Subject: [PATCH 1/3] implementing feature request #62 --- .gitignore | 1 + Makefile | 40 ++++++++- README.md | 26 +++++- embedded/example.txt | 1 + embedded/test/test | 1 + httpserver/embed_embedded.go | 8 ++ httpserver/{embed.go => embed_static.go} | 0 httpserver/handler.go | 103 +++++++++++++++++++---- httpserver/helper.go | 25 ++++++ httpserver/server.go | 3 + httpserver/static/templates/index.html | 29 +++++++ httpserver/structs.go | 11 ++- main.go | 5 ++ 13 files changed, 230 insertions(+), 23 deletions(-) create mode 100644 embedded/example.txt create mode 100644 embedded/test/test create mode 100644 httpserver/embed_embedded.go rename httpserver/{embed.go => embed_static.go} (100%) diff --git a/.gitignore b/.gitignore index 86d6bd8..a989c9f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ /goshs *.exe *resource.go +/httpserver/embedded diff --git a/Makefile b/Makefile index 5dcb793..1f6d99d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build +.PHONY: build-all # uglify-js and sass needed generate: @@ -7,6 +7,9 @@ generate: @uglifyjs -o httpserver/static/js/color-modes.min.js assets/js/color-modes.js @sass --no-source-map -s compressed assets/css/style.scss httpserver/static/css/style.css @echo "[OK] Done minifying and compiling things" + @echo "[*] Copying embedded files to target location" + @rm -rf httpserver/embedded + @cp -r embedded httpserver/ security: @echo "[*] Checking with gosec" @@ -14,7 +17,7 @@ security: @echo "[OK] No issues detected" -build: clean generate security +build-all: clean generate @echo "[*] go mod dowload" @go mod download @echo "[*] Building for linux" @@ -32,6 +35,39 @@ build: clean generate security @GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o dist/arm64_8/goshs @echo "[OK] App binary was created!" +build-linux: clean generate + @echo "[*] go mod dowload" + @go mod download + @echo "[*] Building for linux" + @GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o dist/linux_amd64/goshs + @GOOS=linux GOARCH=386 go build -ldflags="-s -w" -o dist/linux_386/goshs + @echo "[OK] App binary was created!" + +build-mac: clean generate + @echo "[*] go mod dowload" + @go mod download + @echo "[*] Building for mac" + @GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o dist/darwin_amd64/goshs + @echo "[OK] App binary was created!" + +build-windows: clean generate + @echo "[*] go mod dowload" + @go mod download + @echo "[*] Building for windows" + @GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o dist/windows_amd64/goshs.exe + @GOOS=windows GOARCH=386 go build -ldflags="-s -w" -o dist/windows_386/goshs.exe + @echo "[OK] App binary was created!" + +build-arm: clean generate + @echo "[*] go mod dowload" + @go mod download + @echo "[*] Building for arm" + @GOOS=linux GOARCH=arm GOARM=5 go build -ldflags="-s -w" -o dist/arm_5/goshs + @GOOS=linux GOARCH=arm GOARM=6 go build -ldflags="-s -w" -o dist/arm_6/goshs + @GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="-s -w" -o dist/arm_7/goshs + @GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o dist/arm64_8/goshs + @echo "[OK] App binary was created!" + run: @go run main.go diff --git a/README.md b/README.md index dd5bdef..2c8674a 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ goshs is a replacement for Python's `SimpleHTTPServer`. It allows uploading and * You can place a `.goshs` in any folder to apply custom ACLs * You can apply custom basic auth per folder * You can restrict access to specific files completely +* Embed files on compile time # Installation @@ -61,7 +62,7 @@ Building requirements are [ugilfy-js](https://www.npmjs.com/package/uglify-js) a ```bash git clone https://github.com/patrickhener/goshs.git cd goshs -make build +make build-all ``` # Usage @@ -82,6 +83,7 @@ Web server options: -uo, --upload-only Upload only mode, no download possible (default: false) -si, --silent Running without dir listing (default: false) -c, --cli Enable cli (only with auth and tls) (default: false) + -e, --embedded Show embedded files in UI (default: false) TLS options: -s, --ssl Use TLS @@ -278,6 +280,28 @@ Hash: $2a$14$hh50ncgjLAOQT3KI1RlVYus3gMecE4/Ul2HakUp6iiBCnl2c5M0da The `block` mode will **hide** the folders and files from the listing **and restrict access** to them regardless. Please be aware that a file inside a blocked folder will be accessible unless you define a new `.goshs` file within that blocked folder. +**Embed files on compile time** + +You can embed files at compile time and ship them with your version of `goshs`. Any file that is in the folder `embed` will be compiled into the binary and will be available while running. There is a file called `example.txt` in the folder by default to demonstrate the feature. + +To compile just use `make build-`, like for example `make build-linux` for a version running on linux. Be sure to checkout and understand the section [Build yourself](#build-yourself). + +You can then retrieve the file browsing to `/file/path?embedded` or use the flag `-e` / `--embedded` to show the embedded files in the frontend. + +``` +user@host:~$ ./goshs -e +INFO [2024-06-27 18:50:03] Download embedded file at: /example.txt?embedded +INFO [2024-06-27 18:50:03] Serving on interface eth0 bound to 10.137.0.27:8000 +INFO [2024-06-27 18:50:03] Serving on interface lo bound to 127.0.0.1:8000 +INFO [2024-06-27 18:50:03] Serving HTTP from / +INFO [2024-06-27 18:50:56] 127.0.0.1:48784 - [200] - "GET /example.txt?embedded HTTP/1.1" +``` + +``` +user@host:~$ curl http://127.0.0.1:8000/example.txt?embedded +This is an example for an embedded file on compilation time. If you place any other file here before compiling using the Makefile, then it will be added to the goshs binary and will be available when running it. +``` + # Credits A special thank you goes to *sc0tfree* for inspiring this project with his project [updog](https://github.com/sc0tfree/updog) written in Python. diff --git a/embedded/example.txt b/embedded/example.txt new file mode 100644 index 0000000..d622f23 --- /dev/null +++ b/embedded/example.txt @@ -0,0 +1 @@ +This is an example for an embedded file on compilation time. If you place any other file here before compiling using the Makefile, then it will be added to the goshs binary and will be available when running it. diff --git a/embedded/test/test b/embedded/test/test new file mode 100644 index 0000000..41b99de --- /dev/null +++ b/embedded/test/test @@ -0,0 +1 @@ +nested test \ No newline at end of file diff --git a/httpserver/embed_embedded.go b/httpserver/embed_embedded.go new file mode 100644 index 0000000..7a1ca7e --- /dev/null +++ b/httpserver/embed_embedded.go @@ -0,0 +1,8 @@ +package httpserver + +import "embed" + +// Embedded will provide additional embedded files as http.FS +// +//go:embed embedded +var embedded embed.FS diff --git a/httpserver/embed.go b/httpserver/embed_static.go similarity index 100% rename from httpserver/embed.go rename to httpserver/embed_static.go diff --git a/httpserver/handler.go b/httpserver/handler.go index 3738acc..a04b36b 100644 --- a/httpserver/handler.go +++ b/httpserver/handler.go @@ -5,6 +5,7 @@ import ( "fmt" "html/template" "io" + "io/fs" "net/http" "net/url" "os" @@ -19,11 +20,35 @@ import ( "github.com/patrickhener/goshs/ws" ) +// embedded will give additional embedded content shipped with the binary +func (fs *FileServer) embedded(w http.ResponseWriter, req *http.Request) error { + path := "embedded" + req.URL.Path + // Load file from embed package + + embeddedFile, err := embedded.ReadFile(path) + if err != nil { + logger.Errorf("embedded file: %+v cannot be loaded: %+v", path, err) + return err + } + + // Get mimetype from extension + contentType := utils.MimeByExtension(path) + + // Set mimetype and deliver to browser + w.Header().Add("Content-Type", contentType) + if _, err := w.Write(embeddedFile); err != nil { + logger.Errorf("Error writing response to browser: %+v", err) + return err + } + + return nil +} + // static will give static content for style and function func (fs *FileServer) static(w http.ResponseWriter, req *http.Request) { // Construct static path to file path := "static" + req.URL.Path - // Load file with parcello + // Load file from embed package staticFile, err := static.ReadFile(path) if err != nil { logger.Errorf("static file: %+v cannot be loaded: %+v", path, err) @@ -58,7 +83,14 @@ func (fs *FileServer) handler(w http.ResponseWriter, req *http.Request) { fs.static(w, req) return } - + if _, ok := req.URL.Query()["embedded"]; ok { + if err := fs.embedded(w, req); err != nil { + logger.LogRequest(req, http.StatusNotFound, fs.Verbose) + return + } + logger.LogRequest(req, http.StatusOK, fs.Verbose) + return + } if _, ok := req.URL.Query()["delete"]; ok { if !fs.ReadOnly && !fs.UploadOnly { fs.deleteFile(w, req) @@ -148,11 +180,11 @@ func (fs *FileServer) handler(w http.ResponseWriter, req *http.Request) { } } -func (fs *FileServer) processDir(w http.ResponseWriter, req *http.Request, file *os.File, relpath string, jsonOutput bool, acl configFile) { +func (fileS *FileServer) processDir(w http.ResponseWriter, req *http.Request, file *os.File, relpath string, jsonOutput bool, acl configFile) { // Read directory FileInfo fis, err := file.Readdir(-1) if err != nil { - fs.handleError(w, req, err, http.StatusNotFound) + fileS.handleError(w, req, err, http.StatusNotFound) return } @@ -165,7 +197,7 @@ func (fs *FileServer) processDir(w http.ResponseWriter, req *http.Request, file username, password, authOK := req.BasicAuth() if !authOK { - fs.handleError(w, req, fmt.Errorf("%s", "not authorized"), http.StatusUnauthorized) + fileS.handleError(w, req, fmt.Errorf("%s", "not authorized"), http.StatusUnauthorized) return } @@ -173,7 +205,7 @@ func (fs *FileServer) processDir(w http.ResponseWriter, req *http.Request, file passwordHash := strings.Split(acl.Auth, ":")[1] if username != user || !checkPasswordHash(password, passwordHash) { - fs.handleError(w, req, fmt.Errorf("%s", "not authorized"), http.StatusUnauthorized) + fileS.handleError(w, req, fmt.Errorf("%s", "not authorized"), http.StatusUnauthorized) return } } @@ -203,11 +235,11 @@ func (fs *FileServer) processDir(w http.ResponseWriter, req *http.Request, file item.SortSize = fi.Size() item.DisplayLastModified = fi.ModTime().Format("Mon Jan _2 15:04:05 2006") item.SortLastModified = fi.ModTime().UTC().UnixMilli() - item.ReadOnly = fs.ReadOnly + item.ReadOnly = fileS.ReadOnly // Check and resolve symlink if fi.Mode()&os.ModeSymlink != 0 { item.IsSymlink = true - item.SymlinkTarget, err = os.Readlink(path.Join(fs.Webroot, relpath, fi.Name())) + item.SymlinkTarget, err = os.Readlink(path.Join(fileS.Webroot, relpath, fi.Name())) if err != nil { logger.Errorf("resolving symlink: %+v", err) } @@ -228,6 +260,35 @@ func (fs *FileServer) processDir(w http.ResponseWriter, req *http.Request, file return strings.ToLower(items[i].Name) < strings.ToLower(items[j].Name) }) + // Construct Items for embedded files + embeddedItems := make([]item, 0) + // Iterate over FileInfo of embedded FS + err = fs.WalkDir(embedded, ".", + func(pathS string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + // Set item fields + item := item{} + if !d.IsDir() { + name_temp := url.PathEscape(pathS) + name_temp = strings.TrimPrefix(name_temp, "embedded") + item.Name = strings.ReplaceAll(name_temp, "%2F", "/") + item.Ext = strings.ToLower(utils.ReturnExt(d.Name())) + uri_temp := url.PathEscape(pathS) + uri_temp = strings.TrimPrefix(uri_temp, "embedded") + item.URI = fmt.Sprintf("%s?embedded", uri_temp) + + // Add to items slice + embeddedItems = append(embeddedItems, item) + } + + return nil + }) + if err != nil { + logger.Errorf("error compiling list for embedded files: %+v", err) + } + // if ?json output json listing if jsonOutput { w.Header().Add("Content-Type", "application/json") @@ -242,9 +303,9 @@ func (fs *FileServer) processDir(w http.ResponseWriter, req *http.Request, file return } - if fs.Silent { + if fileS.Silent { tem := &baseTemplate{ - GoshsVersion: fs.Version, + GoshsVersion: fileS.Version, Directory: &directory{AbsPath: "silent mode"}, } @@ -268,7 +329,7 @@ func (fs *FileServer) processDir(w http.ResponseWriter, req *http.Request, file // Construct directory for template d := &directory{ RelPath: relpath, - AbsPath: filepath.Join(fs.Webroot, relpath), + AbsPath: filepath.Join(fileS.Webroot, relpath), Content: items, } if relpath != "/" { @@ -289,17 +350,27 @@ func (fs *FileServer) processDir(w http.ResponseWriter, req *http.Request, file d.IsSubdirectory = false } + // Construct directory for embedded files + e := &directory{ + RelPath: fileS.Webroot, + AbsPath: fileS.Webroot, + Content: embeddedItems, + } + // upload only mode empty directory - if fs.UploadOnly { + if fileS.UploadOnly { d = &directory{} + e = &directory{} } // Construct template tem := &baseTemplate{ - Directory: d, - GoshsVersion: fs.Version, - Clipboard: fs.Clipboard, - CLI: fs.CLI, + Directory: d, + GoshsVersion: fileS.Version, + Clipboard: fileS.Clipboard, + CLI: fileS.CLI, + Embedded: fileS.Embedded, + EmbeddedContent: e, } files := []string{"static/templates/index.html", "static/templates/header.tmpl", "static/templates/footer.tmpl", "static/templates/scripts_index.tmpl"} diff --git a/httpserver/helper.go b/httpserver/helper.go index 6011127..6cfe921 100644 --- a/httpserver/helper.go +++ b/httpserver/helper.go @@ -1,5 +1,12 @@ package httpserver +import ( + "io/fs" + "strings" + + "github.com/patrickhener/goshs/logger" +) + func removeItem(sSlice []item, item string) []item { index := 0 @@ -11,3 +18,21 @@ func removeItem(sSlice []item, item string) []item { return append(sSlice[:index], sSlice[index+1:]...) } + +func (files *FileServer) PrintEmbeddedFiles() { + err := fs.WalkDir(embedded, ".", + func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if !d.IsDir() { + outPath := strings.TrimPrefix(path, "embedded") + logger.Infof("Download embedded file at: %+v?embedded", outPath) + } + return nil + }) + if err != nil { + logger.Errorf("error printing info about embedded files: %+v", err) + } + +} diff --git a/httpserver/server.go b/httpserver/server.go index 94e86ec..63781a3 100644 --- a/httpserver/server.go +++ b/httpserver/server.go @@ -85,6 +85,9 @@ func (fs *FileServer) Start(what string) { logger.Info("Serving in silent mode - no dir listing available at HTTP Listener") } + // Print all embedded files as info to the console + fs.PrintEmbeddedFiles() + // Check if ssl if fs.SSL { // Check if selfsigned diff --git a/httpserver/static/templates/index.html b/httpserver/static/templates/index.html index 5194bb3..5c25852 100644 --- a/httpserver/static/templates/index.html +++ b/httpserver/static/templates/index.html @@ -282,6 +282,35 @@
{{.ID}}
{{ end }} + {{ if .Embedded }} + +
+
+

Embedded files

+
+
+
+
+ + + + + + + + {{ range .EmbeddedContent.Content }} + + + + {{ end }} + +
Name
+ + {{.Name}} +
+
+
+ {{ end }} {{ if .CLI }}
diff --git a/httpserver/structs.go b/httpserver/structs.go index ebd0d0e..a53401a 100644 --- a/httpserver/structs.go +++ b/httpserver/structs.go @@ -8,10 +8,12 @@ import ( ) type baseTemplate struct { - Clipboard *clipboard.Clipboard - GoshsVersion string - Directory *directory - CLI bool + Clipboard *clipboard.Clipboard + GoshsVersion string + Directory *directory + EmbeddedContent *directory + CLI bool + Embedded bool } type directory struct { @@ -57,6 +59,7 @@ type FileServer struct { UploadOnly bool ReadOnly bool Silent bool + Embedded bool Verbose bool Hub *ws.Hub Clipboard *clipboard.Clipboard diff --git a/main.go b/main.go index d08e01b..1098e99 100644 --- a/main.go +++ b/main.go @@ -39,6 +39,7 @@ var ( leDomains = "" leHTTPPort = "80" leTLSPort = "443" + embedded = false ) // Man page @@ -58,6 +59,7 @@ Web server options: -uo, --upload-only Upload only mode, no download possible (default: false) -si, --silent Running without dir listing (default: false) -c, --cli Enable cli (only with auth and tls) (default: false) + -e, --embedded Show embedded files in UI (default: false) TLS options: -s, --ssl Use TLS @@ -141,6 +143,8 @@ func init() { flag.StringVar(&leHTTPPort, "le-http", leHTTPPort, "") flag.StringVar(&leTLSPort, "slt", leTLSPort, "") flag.StringVar(&leTLSPort, "le-tls", leTLSPort, "") + flag.BoolVar(&embedded, "e", embedded, "") + flag.BoolVar(&embedded, "embedded", embedded, "") hash := flag.Bool("H", false, "hash") hashLong := flag.Bool("hash", false, "hash") version := flag.Bool("v", false, "goshs version") @@ -253,6 +257,7 @@ func main() { UploadOnly: uploadOnly, ReadOnly: readOnly, Silent: silent, + Embedded: embedded, Verbose: verbose, Version: goshsVersion, } From c1bd4ef01e9d888886ba2ef37407bf1a5e23793e Mon Sep 17 00:00:00 2001 From: Patrick Hener Date: Thu, 27 Jun 2024 14:42:35 +0200 Subject: [PATCH 2/3] remove temp files --- embedded/test/test | 1 - 1 file changed, 1 deletion(-) delete mode 100644 embedded/test/test diff --git a/embedded/test/test b/embedded/test/test deleted file mode 100644 index 41b99de..0000000 --- a/embedded/test/test +++ /dev/null @@ -1 +0,0 @@ -nested test \ No newline at end of file From 9fcc7ed29baf6bf0ed90a0bb427ca508f8bb1201 Mon Sep 17 00:00:00 2001 From: Patrick Hener Date: Thu, 27 Jun 2024 14:46:33 +0200 Subject: [PATCH 3/3] README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2c8674a..7b2beff 100644 --- a/README.md +++ b/README.md @@ -282,7 +282,7 @@ The `block` mode will **hide** the folders and files from the listing **and rest **Embed files on compile time** -You can embed files at compile time and ship them with your version of `goshs`. Any file that is in the folder `embed` will be compiled into the binary and will be available while running. There is a file called `example.txt` in the folder by default to demonstrate the feature. +You can embed files at compile time and ship them with your version of `goshs`. Any file that is in the folder `embedded` will be compiled into the binary and will be available while running. There is a file called `example.txt` in the folder by default to demonstrate the feature. To compile just use `make build-`, like for example `make build-linux` for a version running on linux. Be sure to checkout and understand the section [Build yourself](#build-yourself).