prefixed ui + refactor

This commit is contained in:
Pierre Dubouilh
2019-07-20 13:45:52 +02:00
parent 881d5836bb
commit 44a04b1534
7 changed files with 94 additions and 85 deletions
+2
View File
@@ -34,3 +34,5 @@ test-fixture/*/*
!test-fixture/hols/glasgow.jpg
!test-fixture/hols/landscape-540116_1920.jpg
!test-fixture/hols/scotland-1761292_1920.jpg
!test-fixture/fancy-path
!test-fixture/fancy-path/*
+6 -1
View File
@@ -9,9 +9,14 @@ run:
make build
./gossa test-fixture
run-prefix:
make build
./gossa -prefix="/fancy-path/" test-fixture
ci:
timeout 10 make run &
cp src/gossa_test.go . && sleep 5 && go test
sleep 11 && timeout 10 make run-prefix &
cp src/gossa_test.go . && go test
rm gossa_test.go
watch:
+3 -1
View File
@@ -8,7 +8,7 @@ gossa
[![docker pulls](https://img.shields.io/docker/pulls/pldubouilh/gossa.svg?logo=docker)](https://hub.docker.com/r/pldubouilh/gossa)
[![github downloads](https://img.shields.io/github/downloads/pldubouilh/gossa/total.svg?logo=github)](https://github.com/pldubouilh/gossa/releases)
a fast and simple webserver for your files, that's dependency-free and with only 210 lines of code, easy to review.
a fast and simple webserver for your files, that's dependency-free and with under 200 lines of code, easy to review.
a [simple UI](https://github.com/pldubouilh/gossa-ui) comes as default, featuring :
@@ -26,6 +26,8 @@ built blobs are available on the [release page](https://github.com/pldubouilh/go
### usage
```sh
% ./gossa --help
% ./gossa -h 192.168.100.33 ~/storage
```
+45 -59
View File
@@ -20,6 +20,7 @@ import (
var host = flag.String("h", "127.0.0.1", "host to listen to")
var port = flag.String("p", "8001", "port to listen to")
var extraPath = flag.String("prefix", "/", "url prefix at which gossa can be reached, e.g. /gossa/ (slashes of importance)")
var verb = flag.Bool("verb", true, "verbosity")
var skipHidden = flag.Bool("k", true, "skip hidden files")
var initPath = "."
@@ -36,6 +37,7 @@ type rowTemplate struct {
type pageTemplate struct {
Title template.HTML
ExtraPath template.HTML
RowsFiles []rowTemplate
RowsFolders []rowTemplate
}
@@ -51,8 +53,11 @@ func check(e error) {
}
}
func logVerb(s ...interface{}) {
if *verb {
func exitPath(w http.ResponseWriter, s ...interface{}) {
if recover() != nil {
log.Println("error", s)
w.Write([]byte("error"))
} else if *verb {
log.Println(s...)
}
}
@@ -70,18 +75,21 @@ func sizeToString(bytes int64) string {
}
}
func replyList(w http.ResponseWriter, path string) {
func replyList(w http.ResponseWriter, fullPath string, path string) {
_files, err := ioutil.ReadDir(fullPath)
check(err)
if !strings.HasSuffix(path, "/") {
path += "/"
}
_files, err := ioutil.ReadDir(initPath + path)
check(err)
title := "/" + strings.TrimPrefix(path, *extraPath)
p := pageTemplate{}
if path != "/" {
if !(path == "/" || path == *extraPath) {
p.RowsFolders = append(p.RowsFolders, rowTemplate{"../", "../", "", "folder"})
}
p.ExtraPath = template.HTML(html.EscapeString(*extraPath))
p.Title = template.HTML(html.EscapeString(title))
for _, el := range _files {
name := el.Name()
@@ -101,85 +109,65 @@ func replyList(w http.ResponseWriter, path string) {
}
}
p.Title = template.HTML(html.EscapeString(path))
page.Execute(w, p)
}
func doContent(w http.ResponseWriter, r *http.Request) {
path := html.UnescapeString(r.URL.Path)
fullPath, errPath := checkPath(path)
stat, errStat := os.Stat(fullPath)
if errStat != nil || errPath != nil {
logVerb("Error", errStat, errPath)
w.Write([]byte("error"))
return
if !strings.HasPrefix(r.URL.Path, *extraPath) {
http.Redirect(w, r, *extraPath, 302)
}
path := html.UnescapeString(r.URL.Path)
defer exitPath(w, "get content", path)
fullPath := checkPath(path)
stat, errStat := os.Stat(fullPath)
check(errStat)
if stat.IsDir() {
logVerb("Get list", fullPath)
replyList(w, path)
replyList(w, fullPath, path)
} else {
logVerb("Get file", fullPath)
fs.ServeHTTP(w, r)
}
}
func upload(w http.ResponseWriter, r *http.Request) {
unescaped, _ := url.PathUnescape(r.Header.Get("gossa-path"))
fullPath, err := checkPath(unescaped)
logVerb("Up", err, fullPath)
if err != nil {
w.Write([]byte("error"))
return
}
path, _ := url.PathUnescape(r.Header.Get("gossa-path"))
defer exitPath(w, "upload", path)
reader, _ := r.MultipartReader()
part, _ := reader.NextPart()
dst, _ := os.Create(fullPath)
dst, _ := os.Create(checkPath(path))
io.Copy(dst, part)
logVerb("Done upping", fullPath)
w.Write([]byte("ok"))
}
func rpc(w http.ResponseWriter, r *http.Request) {
var err error
var rpc rpcCall
bodyBytes, _ := ioutil.ReadAll(r.Body)
bodyString := string(bodyBytes)
var payload rpcCall
json.Unmarshal([]byte(bodyString), &payload)
json.Unmarshal(bodyBytes, &rpc)
defer exitPath(w, "rpc", rpc)
for i := range payload.Args {
payload.Args[i], err = checkPath(payload.Args[i])
if err != nil {
logVerb("Cant read path", err, payload)
w.Write([]byte("error"))
return
}
if rpc.Call == "mkdirp" {
err = os.MkdirAll(checkPath(rpc.Args[0]), os.ModePerm)
} else if rpc.Call == "mv" {
err = os.Rename(checkPath(rpc.Args[0]), checkPath(rpc.Args[1]))
} else if rpc.Call == "rm" {
err = os.RemoveAll(checkPath(rpc.Args[0]))
}
if payload.Call == "mkdirp" {
err = os.MkdirAll(payload.Args[0], os.ModePerm)
} else if payload.Call == "mv" {
err = os.Rename(payload.Args[0], payload.Args[1])
} else if payload.Call == "rm" {
err = os.RemoveAll(payload.Args[0])
}
logVerb("RPC", err, payload)
check(err)
w.Write([]byte("ok"))
}
func checkPath(p string) (string, error) {
p = filepath.Join(initPath, p)
func checkPath(p string) string {
p = filepath.Join(initPath, strings.TrimPrefix(p, *extraPath))
fp, err := filepath.Abs(p)
if err != nil || !strings.HasPrefix(fp, initPath) {
return "", errors.New("error")
panic(errors.New("invalid path"))
}
return fp, nil
return fp
}
func main() {
@@ -194,14 +182,12 @@ func main() {
hostString := *host + ":" + *port
fmt.Println("Gossa startig on directory " + initPath)
fmt.Println("Listening on http://" + hostString)
fmt.Println("Listening on http://" + hostString + *extraPath)
root := http.Dir(initPath)
fs = http.StripPrefix("/", http.FileServer(root))
http.HandleFunc("/rpc", rpc)
http.HandleFunc("/post", upload)
http.HandleFunc(*extraPath+"rpc", rpc)
http.HandleFunc(*extraPath+"post", upload)
http.HandleFunc("/", doContent)
fs = http.StripPrefix(*extraPath, http.FileServer(http.Dir(initPath)))
err = http.ListenAndServe(hostString, nil)
check(err)
}
+36 -23
View File
@@ -8,6 +8,7 @@ import (
"regexp"
"strings"
"testing"
"time"
)
func dieMaybe(t *testing.T, err error) {
@@ -29,10 +30,10 @@ func get(t *testing.T, url string) string {
return trimSpaces(string(body))
}
func postDummyFile(t *testing.T, path string, payload string) string {
func postDummyFile(t *testing.T, url string, path string, payload string) string {
// Generated by curl-to-Go: https://mholt.github.io/curl-to-go
body := strings.NewReader("------WebKitFormBoundarycCRIderiXxJWEUcU\r\nContent-Disposition: form-data; name=\"\u1112\u1161 \u1112\u1161\"; filename=\"\u1112\u1161 \u1112\u1161\"\r\nContent-Type: application/octet-stream\r\n\r\n" + payload)
req, err := http.NewRequest("POST", "http://127.0.0.1:8001/post", body)
req, err := http.NewRequest("POST", url+"post", body)
dieMaybe(t, err)
req.Header.Set("Content-Type", "multipart/form-data; boundary=----WebKitFormBoundarycCRIderiXxJWEUcU")
req.Header.Set("Gossa-Path", path)
@@ -83,54 +84,54 @@ func fetchAndTestDefault(t *testing.T, url string) string {
return bodyStr
}
func TestGetFolder(t *testing.T) {
func doTest(t *testing.T, url string) {
payload := ""
path := ""
bodyStr := ""
// ~~~~~~~~~~~~~~~~~
fmt.Println("\r\n~~~~~~~~~~ test fetching default path")
fetchAndTestDefault(t, "http://127.0.0.1:8001/")
fetchAndTestDefault(t, url)
// ~~~~~~~~~~~~~~~~~
fmt.Println("\r\n~~~~~~~~~~ test fetching an invalid path - redirected to root")
fetchAndTestDefault(t, "http://127.0.0.1:8001/../../")
fetchAndTestDefault(t, "http://127.0.0.1:8001/hols/../../")
fetchAndTestDefault(t, url+"../../")
fetchAndTestDefault(t, url+"hols/../../")
// ~~~~~~~~~~~~~~~~~
fmt.Println("\r\n~~~~~~~~~~ test fetching a regular file")
bodyStr = get(t, "http://127.0.0.1:8001/subdir_with%20space/file_with%20space.html")
bodyStr = get(t, url+"subdir_with%20space/file_with%20space.html")
if !strings.Contains(bodyStr, `<b>spacious!!</b>`) {
t.Fatal("fetching a regular file errored")
}
// ~~~~~~~~~~~~~~~~~
fmt.Println("\r\n~~~~~~~~~~ test fetching a invalid file")
bodyStr = get(t, "http://127.0.0.1:8001/../../../../../../../../../../etc/passwd")
bodyStr = get(t, url+"../../../../../../../../../../etc/passwd")
if !strings.Contains(bodyStr, `error`) {
t.Fatal("fetching a invalid file didnt errored")
}
// ~~~~~~~~~~~~~~~~~
fmt.Println("\r\n~~~~~~~~~~ test mkdir rpc")
bodyStr = postJSON(t, "http://127.0.0.1:8001/rpc", `{"call":"mkdirp","args":["/AAA"]}`)
bodyStr = postJSON(t, url+"rpc", `{"call":"mkdirp","args":["/AAA"]}`)
if !strings.Contains(bodyStr, `ok`) {
t.Fatal("mkdir rpc errored")
}
bodyStr = fetchAndTestDefault(t, "http://127.0.0.1:8001/")
bodyStr = fetchAndTestDefault(t, url)
if !strings.Contains(bodyStr, `href="AAA">AAA/</a>`) {
t.Fatal("mkdir rpc folder not created")
}
// ~~~~~~~~~~~~~~~~~
fmt.Println("\r\n~~~~~~~~~~ test invalid mkdir rpc")
bodyStr = postJSON(t, "http://127.0.0.1:8001/rpc", `{"call":"mkdirp","args":["../BBB"]}`)
bodyStr = postJSON(t, url+"rpc", `{"call":"mkdirp","args":["../BBB"]}`)
if !strings.Contains(bodyStr, `error`) {
t.Fatal("invalid mkdir rpc didnt errored #0")
}
bodyStr = postJSON(t, "http://127.0.0.1:8001/rpc", `{"call":"mkdirp","args":["/../BBB"]}`)
bodyStr = postJSON(t, url+"rpc", `{"call":"mkdirp","args":["/../BBB"]}`)
if !strings.Contains(bodyStr, `error`) {
t.Fatal("invalid mkdir rpc didnt errored #1")
}
@@ -139,36 +140,36 @@ func TestGetFolder(t *testing.T) {
fmt.Println("\r\n~~~~~~~~~~ test post file")
path = "%2F%E1%84%92%E1%85%A1%20%E1%84%92%E1%85%A1" // "하 하" encoded
payload = "123 하"
bodyStr = postDummyFile(t, path, payload)
bodyStr = postDummyFile(t, url, path, payload)
if !strings.Contains(bodyStr, `ok`) {
t.Fatal("post file errored")
}
bodyStr = get(t, "http://127.0.0.1:8001/"+path)
bodyStr = get(t, url+path)
if !strings.Contains(bodyStr, payload) {
t.Fatal("post file errored reaching new file")
}
bodyStr = fetchAndTestDefault(t, "http://127.0.0.1:8001/")
bodyStr = fetchAndTestDefault(t, url)
if !strings.Contains(bodyStr, `href="%E1%84%92%E1%85%A1%20%E1%84%92%E1%85%A1">하 하</a>`) {
t.Fatal("post file errored checking new file row")
}
// ~~~~~~~~~~~~~~~~~
fmt.Println("\r\n~~~~~~~~~~ test post file incorrect path")
bodyStr = postDummyFile(t, "%2E%2E"+path, payload)
bodyStr = postDummyFile(t, url, "%2E%2E"+path, payload)
if !strings.Contains(bodyStr, `err`) {
t.Fatal("post file incorrect path didnt errored")
}
// ~~~~~~~~~~~~~~~~~
fmt.Println("\r\n~~~~~~~~~~ test mv rpc")
bodyStr = postJSON(t, "http://127.0.0.1:8001/rpc", `{"call":"mv","args":["/AAA", "/hols/AAA"]}`)
bodyStr = postJSON(t, url+"rpc", `{"call":"mv","args":["/AAA", "/hols/AAA"]}`)
if !strings.Contains(bodyStr, `ok`) {
t.Fatal("mv rpc errored")
}
bodyStr = fetchAndTestDefault(t, "http://127.0.0.1:8001/")
bodyStr = fetchAndTestDefault(t, url)
if strings.Contains(bodyStr, `href="AAA">AAA/</a></td> </tr>`) {
t.Fatal("mv rpc folder not moved")
}
@@ -176,30 +177,42 @@ func TestGetFolder(t *testing.T) {
// ~~~~~~~~~~~~~~~~~
fmt.Println("\r\n~~~~~~~~~~ test upload in new folder")
payload = "abcdef1234"
bodyStr = postDummyFile(t, "%2Fhols%2FAAA%2Fabcdef", payload)
bodyStr = postDummyFile(t, url, "%2Fhols%2FAAA%2Fabcdef", payload)
if strings.Contains(bodyStr, `err`) {
t.Fatal("upload in new folder errored")
}
bodyStr = get(t, "http://127.0.0.1:8001/hols/AAA/abcdef")
bodyStr = get(t, url+"hols/AAA/abcdef")
if !strings.Contains(bodyStr, payload) {
t.Fatal("upload in new folder error reaching new file")
}
// ~~~~~~~~~~~~~~~~~
fmt.Println("\r\n~~~~~~~~~~ test rm rpc & cleanup")
bodyStr = postJSON(t, "http://127.0.0.1:8001/rpc", `{"call":"rm","args":["/hols/AAA"]}`)
bodyStr = postJSON(t, url+"rpc", `{"call":"rm","args":["/hols/AAA"]}`)
if !strings.Contains(bodyStr, `ok`) {
t.Fatal("cleanup errored #0")
}
bodyStr = get(t, "http://127.0.0.1:8001/hols/AAA")
bodyStr = get(t, url+"hols/AAA")
if !strings.Contains(bodyStr, `error`) {
t.Fatal("cleanup errored #1")
}
bodyStr = postJSON(t, "http://127.0.0.1:8001/rpc", `{"call":"rm","args":["/하 하"]}`)
bodyStr = postJSON(t, url+"rpc", `{"call":"rm","args":["/하 하"]}`)
if !strings.Contains(bodyStr, `ok`) {
t.Fatal("cleanup errored #2")
}
fmt.Println("\r\n\r\n\r\n=========")
}
func TestGetFolder(t *testing.T) {
time.Sleep(6 * time.Second)
fmt.Println("========== testing normal path ============")
doTest(t, "http://127.0.0.1:8001/")
time.Sleep(10 * time.Second)
fancyPath := "/fancy-path/"
fmt.Println("========== testing at " + fancyPath + " path ============")
doTest(t, "http://127.0.0.1:8001"+fancyPath)
}
+1
View File
@@ -0,0 +1 @@
a