Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature embed #63

Merged
merged 3 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
/goshs
*.exe
*resource.go
/httpserver/embedded
40 changes: 38 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: build
.PHONY: build-all

# uglify-js and sass needed
generate:
Expand All @@ -7,14 +7,17 @@ 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"
@gosec ./...
@echo "[OK] No issues detected"


build: clean generate security
build-all: clean generate
@echo "[*] go mod dowload"
@go mod download
@echo "[*] Building for linux"
Expand All @@ -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

Expand Down
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 `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-<os>`, 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.
1 change: 1 addition & 0 deletions embedded/example.txt
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 8 additions & 0 deletions httpserver/embed_embedded.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package httpserver

import "embed"

// Embedded will provide additional embedded files as http.FS
//
//go:embed embedded
var embedded embed.FS
File renamed without changes.
103 changes: 87 additions & 16 deletions httpserver/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"html/template"
"io"
"io/fs"
"net/http"
"net/url"
"os"
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}

Expand All @@ -165,15 +197,15 @@ 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
}

user := strings.Split(acl.Auth, ":")[0]
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
}
}
Expand Down Expand Up @@ -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()))
Dismissed Show dismissed Hide dismissed
if err != nil {
logger.Errorf("resolving symlink: %+v", err)
}
Expand All @@ -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")
Expand All @@ -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"},
}

Expand All @@ -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 != "/" {
Expand All @@ -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"}
Expand Down
25 changes: 25 additions & 0 deletions httpserver/helper.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
package httpserver

import (
"io/fs"
"strings"

"github.com/patrickhener/goshs/logger"
)

func removeItem(sSlice []item, item string) []item {
index := 0

Expand All @@ -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)
}

}
3 changes: 3 additions & 0 deletions httpserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading