package main import ( "archive/zip" "bytes" "fmt" "io/ioutil" "net/http" "regexp" "strings" "testing" ) func dieMaybe(t *testing.T, err error) { if err != nil { t.Fatal(err) } } func trimSpaces(str string) string { space := regexp.MustCompile(`\s+`) return space.ReplaceAllString(str, " ") } func getRaw(t *testing.T, url string) []byte { resp, err := http.Get(url) dieMaybe(t, err) body, err := ioutil.ReadAll(resp.Body) dieMaybe(t, err) return body } func getZip(t *testing.T, needle string, dest string) (int, bool) { b := getRaw(t, dest) unzipped, err := zip.NewReader(bytes.NewReader(b), int64(len(b))) dieMaybe(t, err) length := len(unzipped.File) for _, file := range unzipped.File { if file.Name == needle { return length, true } } return length, false } func get(t *testing.T, url string) string { body := getRaw(t, url) return trimSpaces(string(body)) } 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", url+"post", body) dieMaybe(t, err) req.Header.Set("Content-Type", "multipart/form-data; boundary=----WebKitFormBoundarycCRIderiXxJWEUcU") req.Header.Set("Gossa-Path", path) resp, err := http.DefaultClient.Do(req) dieMaybe(t, err) defer resp.Body.Close() bodyS, err := ioutil.ReadAll(resp.Body) dieMaybe(t, err) return trimSpaces(string(bodyS)) } func postJSON(t *testing.T, url string, what string) string { resp, err := http.Post(url, "application/json", bytes.NewBuffer([]byte(what))) dieMaybe(t, err) body, err := ioutil.ReadAll(resp.Body) dieMaybe(t, err) return trimSpaces(string(body)) } func fetchAndTestDefault(t *testing.T, url string) string { body0 := get(t, url) if !strings.Contains(body0, `/`) { t.Fatal("error title") } if !strings.Contains(body0, `

./

`) { t.Fatal("error header") } if !strings.Contains(body0, `href="hols">hols/`) { t.Fatal("error hols folder") } if !strings.Contains(body0, `href="curimit@gmail.com%20%2840%25%29">curimit@gmail.com (40%)/`) { t.Fatal("error curimit@gmail.com (40%) folder") } if !strings.Contains(body0, `href="%E4%B8%AD%E6%96%87">中文/`) { t.Fatal("error 中文 folder") } if !strings.Contains(body0, `href="custom_mime_type.types">custom_mime_type.types`) { t.Fatal("error row custom_mime_type") } return body0 } func doTestRegular(t *testing.T, url string, testExtra bool) { var payload, path, body0, body1, body2 string // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test fetching default path") fetchAndTestDefault(t, url) // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test fetching another page") body0 = get(t, url+"/hols") if !strings.Contains(body0, "glasgow.jpg") { t.Fatal("fetching a subfolder failed") } // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test fetching an invalid path - redirected to root") fetchAndTestDefault(t, url+"../../") fetchAndTestDefault(t, url+"hols/../../") // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test fetching regular files") body0 = get(t, url+"subdir_with%20space/file_with%20space.html") body1 = get(t, url+"fancy-path/a") if body0 != `spacious!! ` || body1 != `fancy! ` { t.Fatal("fetching a regular file errored") } // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test fetching a invalid file") path = "../../../../../../../../../../etc/passwd" if !testExtra && get(t, url+path) != `error` { t.Fatal("fetching a invalid file didnt errored") } else if testExtra { fetchAndTestDefault(t, url+path) // extra path will just redirect to root dir } // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test zipping of folder 中文") len, foundFile := getZip(t, "檔案.html", url+"zip?zipPath=%2F%E4%B8%AD%E6%96%87%2F&zipName=%E4%B8%AD%E6%96%87") if len != 1 || !foundFile { t.Fatal("invalid zip generated") } // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test zipping of folder with hidden file") _, foundHidden := getZip(t, ".hidden-folder/some-file", url+"zip?zipPath=%2fhols%2f&zipName=hols") if foundHidden && !testExtra { t.Fatal("invalid zip generated - shouldnt contain hidden folder") } else if !foundHidden && testExtra { t.Fatal("invalid zip generated - should contain hidden folder") } // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test zip invalid path") body0 = get(t, url+"zip?zipPath=%2Ftmp&zipName=subdir") println(body0) if body0 != `error` { t.Fatal("zip passed for invalid path") } // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test mkdir rpc") body0 = postJSON(t, url+"rpc", `{"call":"mkdirp","args":["/AAA"]}`) if body0 != `ok` { t.Fatal("mkdir rpc errored") } body0 = fetchAndTestDefault(t, url) if !strings.Contains(body0, `href="AAA">AAA/`) { t.Fatal("mkdir rpc folder not created") } // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test invalid mkdir rpc") body0 = postJSON(t, url+"rpc", `{"call":"mkdirp","args":["../BBB"]}`) if body0 != `error` { t.Fatal("invalid mkdir rpc didnt errored #0") } body0 = postJSON(t, url+"rpc", `{"call":"mkdirp","args":["/../BBB"]}`) if body0 != `error` { t.Fatal("invalid mkdir rpc didnt errored #1") } // ~~~~~~~~~~~~~~~~~ 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 하" body0 = postDummyFile(t, url, path, payload) body1 = get(t, url+path) body2 = fetchAndTestDefault(t, url) if body0 != `ok` || body1 != payload || !strings.Contains(body2, `href="%E1%84%92%E1%85%A1%20%E1%84%92%E1%85%A1">하 하`) { t.Fatal("post file errored") } // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test post file incorrect path") body0 = postDummyFile(t, url, "%2E%2E"+path, payload) if !strings.Contains(body0, `err`) { t.Fatal("post file incorrect path didnt errored") } // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test post file") path = "2024-01-02-10:36:58.png" payload = "123123123123123123123123" body0 = postDummyFile(t, url, path, payload) body1 = get(t, url+path) body2 = fetchAndTestDefault(t, url) if body0 != `ok` || body1 != payload || !strings.Contains(body2, `href="2024-01-02-10:36:58.png"`) { t.Fatal("post file errored") } // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test mv rpc") body0 = postJSON(t, url+"rpc", `{"call":"mv","args":["/AAA", "/hols/AAA"]}`) body1 = fetchAndTestDefault(t, url) if body0 != `ok` || strings.Contains(body1, `href="AAA">AAA/ `) { t.Fatal("mv rpc errored") } // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test upload in new folder") payload = "test" body0 = postDummyFile(t, url, "%2Fhols%2FAAA%2Fabcdef", payload) body1 = get(t, url+"hols/AAA/abcdef") if body0 != `ok` || body1 != payload { t.Fatal("upload in new folder errored") } // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test symlink, should succeed: ", testExtra) body0 = get(t, url+"/support/") hasListing := strings.Contains(body0, `readme.md`) body1 = get(t, url+"/support/readme.md") hasReadme := strings.Contains(body1, `the master branch is automatically built and pushed`) body2 = get(t, url) hasMainListing := strings.Contains(body2, `href="support">support/`) if !testExtra && hasReadme { t.Fatal("error symlink file reached where illegal") } else if testExtra && !hasReadme { t.Fatal("error symlink file unreachable") } if !testExtra && hasListing { t.Fatal("error symlink folder reached where illegal") } else if testExtra && !hasListing { t.Fatal("error symlink folder unreachable") } if !testExtra && hasMainListing { t.Fatal("error symlink folder where illegal") } else if testExtra && !hasMainListing { t.Fatal("error symlink folder unreachable") } if testExtra { fmt.Println("\r\n~~~~~~~~~~ test symlink mkdir & cleanup") body0 = postJSON(t, url+"rpc", `{"call":"mkdirp","args":["/support/testfolder"]}`) if body0 != `ok` { t.Fatal("error symlink mkdir") } body0 = postJSON(t, url+"rpc", `{"call":"rm","args":["/support/testfolder"]}`) if body0 != `ok` { t.Fatal("error symlink rm") } } fmt.Println("\r\n~~~~~~~~~~ test hidden file, should succeed: ", testExtra) body0 = get(t, url+"/.testhidden") hasHidden := strings.Contains(body0, `test`) if !testExtra && hasHidden { t.Fatal("error hidden file reached where illegal") } else if testExtra && !hasHidden { t.Fatal("error hidden file unreachable") } // fmt.Println("\r\n~~~~~~~~~~ test upload in new folder") payload = "test" body0 = postDummyFile(t, url, "%2Fhols%2FAAA%2Fabcdef", payload) body1 = get(t, url+"hols/AAA/abcdef") if body0 != `ok` || body1 != payload { t.Fatal("upload in new folder errored") } // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test rm rpc & cleanup") body0 = postJSON(t, url+"rpc", `{"call":"rm","args":["/hols/AAA"]}`) if body0 != `ok` { t.Fatal("cleanup errored #0") } body0 = get(t, url+"hols/AAA") if !strings.Contains(body0, `error`) { t.Fatal("cleanup errored #1") } body0 = postJSON(t, url+"rpc", `{"call":"rm","args":["/하 하"]}`) if body0 != `ok` { t.Fatal("cleanup errored #2") } fmt.Printf("\r\n=========\r\n") } func doTestReadonly(t *testing.T, url string) { var payload, path, body0, body1 string // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test fetching default path") fetchAndTestDefault(t, url) // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test fetching an invalid path - redirected to root") fetchAndTestDefault(t, url+"../../") fetchAndTestDefault(t, url+"hols/../../") // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test fetching regular files") body0 = get(t, url+"subdir_with%20space/file_with%20space.html") body1 = get(t, url+"fancy-path/a") if body0 != `spacious!! ` || body1 != `fancy! ` { t.Fatal("fetching a regular file errored") } // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test fetching a invalid file") path = "../../../../../../../../../../etc/passwd" if get(t, url+path) != `error` { t.Fatal("fetching a invalid file didnt errored") } // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test mkdir rpc") body0 = postJSON(t, url+"rpc", `{"call":"mkdirp","args":["/AAA"]}`) if body0 == `ok` { t.Fatal("mkdir rpc passed - should not be allowed") } // ~~~~~~~~~~~~~~~~~ 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 하" body0 = postDummyFile(t, url, path, payload) get(t, url+path) if body0 == `ok` { t.Fatal("post file passed - should not be allowed") } // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test mv rpc") body0 = postJSON(t, url+"rpc", `{"call":"mv","args":["/AAA", "/hols/AAA"]}`) fetchAndTestDefault(t, url) if body0 == `ok` { t.Fatal("mv rpc passed - should not be allowed") } // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test rm rpc & cleanup") body0 = postJSON(t, url+"rpc", `{"call":"rm","args":["/hols/AAA"]}`) if body0 == `ok` { t.Fatal("cleanup passed - should not be allowed") } fmt.Printf("\r\n=========\r\n") } func TestNormal(t *testing.T) { fmt.Println("========== testing normal path ============") doTestRegular(t, "http://127.0.0.1:8001/", false) } func TestExtra(t *testing.T) { fmt.Println("========== testing extras options ============") doTestRegular(t, "http://127.0.0.1:8001/fancy-path/", true) } func TestRo(t *testing.T) { fmt.Println("========== testing read only ============") doTestReadonly(t, "http://127.0.0.1:8001/") } func TestRunMain(t *testing.T) { main() }