diff --git a/Makefile b/Makefile index 5c247cb..5fae0f7 100755 --- a/Makefile +++ b/Makefile @@ -9,20 +9,24 @@ run: make build ./gossa test-fixture -run-prefix: +run-extra: make build - ./gossa -prefix="/fancy-path/" test-fixture + ./gossa -prefix="/fancy-path/" -symlinks=true test-fixture ci: + -@cd test-fixture && ln -s ../docker . timeout 10 make run & - sleep 11 && timeout 10 make run-prefix & + sleep 11 && timeout 10 make run-extra & cp src/gossa_test.go . && go test rm gossa_test.go watch: ls src/* gossa-ui/* | entr -rc make run -ci-watch: +watch-extra: + ls src/* gossa-ui/* | entr -rc make run-extra + +watch-ci: ls src/* gossa-ui/* | entr -rc make ci build-all: diff --git a/src/gossa.go b/src/gossa.go index 92576ff..4fceb93 100755 --- a/src/gossa.go +++ b/src/gossa.go @@ -21,6 +21,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 symlinks = flag.Bool("symlinks", false, "follow symlinks \033[4mWARNING\033[0m: symlinks will by nature allow to escape the defined path (default: false)") var verb = flag.Bool("verb", true, "verbosity") var skipHidden = flag.Bool("k", true, "skip hidden files") var initPath = "." @@ -54,21 +55,20 @@ func check(e error) { } func exitPath(w http.ResponseWriter, s ...interface{}) { - if recover() != nil { - log.Println("error", s) + if r := recover(); r != nil { + log.Println("error", s, r) w.Write([]byte("error")) } else if *verb { log.Println(s...) } } -func sizeToString(bytes int64) string { - units := [9]string{"B", "k", "M", "G", "T", "P", "E", "Z", "Y"} +func humanize(bytes int64) string { b := float64(bytes) u := 0 for { if b < 1024 { - return strconv.FormatFloat(b, 'f', 1, 64) + units[u] + return strconv.FormatFloat(b, 'f', 1, 64) + [9]string{"B", "k", "M", "G", "T", "P", "E", "Z", "Y"}[u] } b = b / 1024 u++ @@ -85,27 +85,27 @@ func replyList(w http.ResponseWriter, fullPath string, path string) { title := "/" + strings.TrimPrefix(path, *extraPath) p := pageTemplate{} - if !(path == "/" || path == *extraPath) { + if 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() - href := url.PathEscape(name) - if *skipHidden && strings.HasPrefix(name, ".") { + if *skipHidden && strings.HasPrefix(el.Name(), ".") { continue } + el, _ = os.Stat(fullPath + "/" + el.Name()) + href := url.PathEscape(el.Name()) if el.IsDir() && strings.HasPrefix(href, "/") { href = strings.Replace(href, "/", "", 1) } if el.IsDir() { - p.RowsFolders = append(p.RowsFolders, rowTemplate{name + "/", template.HTML(href), "", "folder"}) + p.RowsFolders = append(p.RowsFolders, rowTemplate{el.Name() + "/", template.HTML(href), "", "folder"}) } else { - sl := strings.Split(name, ".") + sl := strings.Split(el.Name(), ".") ext := strings.ToLower(sl[len(sl)-1]) - p.RowsFiles = append(p.RowsFiles, rowTemplate{name, template.HTML(href), sizeToString(el.Size()), ext}) + p.RowsFiles = append(p.RowsFiles, rowTemplate{el.Name(), template.HTML(href), humanize(el.Size()), ext}) } } @@ -115,6 +115,7 @@ func replyList(w http.ResponseWriter, fullPath string, path string) { func doContent(w http.ResponseWriter, r *http.Request) { if !strings.HasPrefix(r.URL.Path, *extraPath) { http.Redirect(w, r, *extraPath, 302) + return } path := html.UnescapeString(r.URL.Path) @@ -162,8 +163,9 @@ func rpc(w http.ResponseWriter, r *http.Request) { func checkPath(p string) string { p = filepath.Join(initPath, strings.TrimPrefix(p, *extraPath)) fp, err := filepath.Abs(p) + sl, _ := filepath.EvalSymlinks(fp) - if err != nil || !strings.HasPrefix(fp, initPath) { + if err != nil || !strings.HasPrefix(fp, initPath) || len(sl) > 0 && !*symlinks && !strings.HasPrefix(sl, initPath) { panic(errors.New("invalid path")) } diff --git a/src/gossa_test.go b/src/gossa_test.go index c5fcd11..8847794 100644 --- a/src/gossa_test.go +++ b/src/gossa_test.go @@ -84,7 +84,7 @@ func fetchAndTestDefault(t *testing.T, url string) string { return bodyStr } -func doTest(t *testing.T, url string) { +func doTest(t *testing.T, url string, symlinkEnabled bool) { payload := "" path := "" bodyStr := "" @@ -99,9 +99,11 @@ func doTest(t *testing.T, url string) { fetchAndTestDefault(t, url+"hols/../../") // ~~~~~~~~~~~~~~~~~ - fmt.Println("\r\n~~~~~~~~~~ test fetching a regular file") + fmt.Println("\r\n~~~~~~~~~~ test fetching regular files") bodyStr = get(t, url+"subdir_with%20space/file_with%20space.html") - if !strings.Contains(bodyStr, `spacious!!`) { + bodyStr2 := get(t, url+"fancy-path/a") + fmt.Println(bodyStr2) + if !strings.Contains(bodyStr, `spacious!!`) || !strings.Contains(bodyStr2, `fancy!`) { t.Fatal("fetching a regular file errored") } @@ -187,6 +189,24 @@ func doTest(t *testing.T, url string) { t.Fatal("upload in new folder error reaching new file") } + // ~~~~~~~~~~~~~~~~~ + fmt.Println("\r\n~~~~~~~~~~ test symlink, should succeed: ", symlinkEnabled) + bodyStr = get(t, url+"/docker/readme.md") + hasReadme := strings.Contains(bodyStr, `the master branch is automatically built and pushed`) + if !symlinkEnabled && hasReadme { + t.Fatal("error symlink reached where illegal") + } else if symlinkEnabled && !hasReadme { + t.Fatal("error symlink unreachable") + } + + if symlinkEnabled { + fmt.Println("\r\n~~~~~~~~~~ test symlink mkdir") + bodyStr = postJSON(t, url+"rpc", `{"call":"mkdirp","args":["/docker/testfolder"]}`) + if !strings.Contains(bodyStr, `ok`) { + t.Fatal("error symlink mkdir") + } + } + // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test rm rpc & cleanup") bodyStr = postJSON(t, url+"rpc", `{"call":"rm","args":["/hols/AAA"]}`) @@ -204,15 +224,26 @@ func doTest(t *testing.T, url string) { t.Fatal("cleanup errored #2") } - fmt.Println("\r\n\r\n\r\n=========") + if symlinkEnabled { + bodyStr = postJSON(t, url+"rpc", `{"call":"rm","args":["/docker/testfolder"]}`) + if !strings.Contains(bodyStr, `ok`) { + t.Fatal("error symlink rm") + } + } } + func TestGetFolder(t *testing.T) { time.Sleep(6 * time.Second) fmt.Println("========== testing normal path ============") - doTest(t, "http://127.0.0.1:8001/") + url := "http://127.0.0.1:8001/" + doTest(t, url, false) + fmt.Printf("\r\n=========\r\n") time.Sleep(10 * time.Second) - fancyPath := "/fancy-path/" - fmt.Println("========== testing at " + fancyPath + " path ============") - doTest(t, "http://127.0.0.1:8001"+fancyPath) + + url = "http://127.0.0.1:8001/fancy-path/" + fmt.Println("========== testing at fancy path ============") + doTest(t, url, true) + + fmt.Printf("\r\n=========\r\n") } diff --git a/test-fixture/fancy-path/a b/test-fixture/fancy-path/a index 7898192..e04498f 100644 --- a/test-fixture/fancy-path/a +++ b/test-fixture/fancy-path/a @@ -1 +1 @@ -a +fancy!