Fixed CleanUp deleting file that was actively downloaded, which then failed the download, fixed write timeout, made webserver unit tests parallel, serve correct content header.

This commit is contained in:
Marc Ole Bulling
2021-04-21 18:06:23 +02:00
parent 05e4dd3e38
commit 8d1e45f228
12 changed files with 488 additions and 161 deletions

View File

@@ -1,3 +1,3 @@
#!/bin/sh
cd ..
go test ./... -coverprofile=/tmp/coverage.out --tags=test && go tool cover -html=/tmp/coverage.out
go test ./... -parallel 8 -coverprofile=/tmp/coverage.out --tags=test && go tool cover -html=/tmp/coverage.out

View File

@@ -34,26 +34,27 @@ var Environment environment.Environment
var ServerSettings Configuration
// Version of the configuration structure. Used for upgrading
const currentConfigVersion = 4
const currentConfigVersion = 5
// Configuration is a struct that contains the global configuration
type Configuration struct {
Port string `json:"Port"`
AdminName string `json:"AdminName"`
AdminPassword string `json:"AdminPassword"`
ServerUrl string `json:"ServerUrl"`
DefaultDownloads int `json:"DefaultDownloads"`
DefaultExpiry int `json:"DefaultExpiry"`
DefaultPassword string `json:"DefaultPassword"`
RedirectUrl string `json:"RedirectUrl"`
Sessions map[string]sessionstructure.Session `json:"Sessions"`
Files map[string]filestructure.File `json:"Files"`
Hotlinks map[string]filestructure.Hotlink `json:"Hotlinks"`
ConfigVersion int `json:"ConfigVersion"`
SaltAdmin string `json:"SaltAdmin"`
SaltFiles string `json:"SaltFiles"`
LengthId int `json:"LengthId"`
DataDir string `json:"DataDir"`
Port string `json:"Port"`
AdminName string `json:"AdminName"`
AdminPassword string `json:"AdminPassword"`
ServerUrl string `json:"ServerUrl"`
DefaultDownloads int `json:"DefaultDownloads"`
DefaultExpiry int `json:"DefaultExpiry"`
DefaultPassword string `json:"DefaultPassword"`
RedirectUrl string `json:"RedirectUrl"`
Sessions map[string]sessionstructure.Session `json:"Sessions"`
Files map[string]filestructure.File `json:"Files"`
Hotlinks map[string]filestructure.Hotlink `json:"Hotlinks"`
DownloadStatus map[string]filestructure.DownloadStatus `json:"DownloadStatus"`
ConfigVersion int `json:"ConfigVersion"`
SaltAdmin string `json:"SaltAdmin"`
SaltFiles string `json:"SaltFiles"`
LengthId int `json:"LengthId"`
DataDir string `json:"DataDir"`
}
// Load loads the configuration or creates the folder structure and a default configuration
@@ -92,6 +93,16 @@ func updateConfig() {
ServerSettings.Hotlinks = make(map[string]filestructure.Hotlink)
}
// < v1.1.4
if ServerSettings.ConfigVersion < 5 {
ServerSettings.LengthId = 15
ServerSettings.DownloadStatus = make(map[string]filestructure.DownloadStatus)
for _, file := range ServerSettings.Files {
file.ContentType = "application/octet-stream"
ServerSettings.Files[file.Id] = file
}
}
if ServerSettings.ConfigVersion < currentConfigVersion {
fmt.Println("Successfully upgraded database")
ServerSettings.ConfigVersion = currentConfigVersion

View File

@@ -0,0 +1,52 @@
package downloadStatus
import (
"Gokapi/internal/configuration"
"Gokapi/internal/helper"
"Gokapi/internal/storage/filestructure"
"time"
)
// SetDownload creates a new DownloadStatus struct and returns its Id
func SetDownload(file filestructure.File) string {
status := newDownloadStatus(file)
configuration.ServerSettings.DownloadStatus[status.Id] = status
return status.Id
}
// SetComplete removes the download object
func SetComplete(id string) {
delete(configuration.ServerSettings.DownloadStatus, id)
}
// Clean removes all expires status objects
func Clean() {
now := time.Now().Unix()
for _, item := range configuration.ServerSettings.DownloadStatus {
if item.ExpireAt < now {
delete(configuration.ServerSettings.DownloadStatus, item.Id)
}
}
}
// newDownloadStatus initialises the a new DownloadStatus item
func newDownloadStatus(file filestructure.File) filestructure.DownloadStatus {
s := filestructure.DownloadStatus{
Id: helper.GenerateRandomString(30),
FileId: file.Id,
ExpireAt: time.Now().Add(24 * time.Hour).Unix(),
}
return s
}
// IsCurrentlyDownloading returns true if file is currently being downloaded
func IsCurrentlyDownloading(file filestructure.File) bool {
for _, status := range configuration.ServerSettings.DownloadStatus {
if status.FileId == file.Id {
if status.ExpireAt > time.Now().Unix() {
return true
}
}
}
return false
}

View File

@@ -0,0 +1,70 @@
package downloadStatus
import (
"Gokapi/internal/configuration"
"Gokapi/internal/storage/filestructure"
"Gokapi/pkg/test"
"os"
"testing"
"time"
)
var testFile filestructure.File
var statusId string
func TestMain(m *testing.M) {
configuration.ServerSettings.DownloadStatus = make(map[string]filestructure.DownloadStatus)
testFile = filestructure.File{
Id: "test",
Name: "testName",
Size: "3 B",
SHA256: "123456",
ExpireAt: 500,
ExpireAtString: "expire",
DownloadsRemaining: 1,
}
exitVal := m.Run()
os.Exit(exitVal)
}
func TestNewDownloadStatus(t *testing.T) {
status := newDownloadStatus(filestructure.File{Id: "testId"})
test.IsNotEmpty(t, status.Id)
test.IsEqualString(t, status.FileId, "testId")
test.IsEqualBool(t, status.ExpireAt > time.Now().Unix(), true)
}
func TestSetDownload(t *testing.T) {
statusId = SetDownload(testFile)
status := configuration.ServerSettings.DownloadStatus[statusId]
test.IsNotEmpty(t, status.Id)
test.IsEqualString(t, status.Id, statusId)
test.IsEqualString(t, status.FileId, testFile.Id)
test.IsEqualBool(t, status.ExpireAt > time.Now().Unix(), true)
}
func TestSetComplete(t *testing.T) {
status := configuration.ServerSettings.DownloadStatus[statusId]
test.IsNotEmpty(t, status.Id)
SetComplete(statusId)
status = configuration.ServerSettings.DownloadStatus[statusId]
test.IsEmpty(t, status.Id)
}
func TestIsCurrentlyDownloading(t *testing.T) {
statusId = SetDownload(testFile)
test.IsEqualBool(t, IsCurrentlyDownloading(testFile), true)
test.IsEqualBool(t, IsCurrentlyDownloading(filestructure.File{Id: "notDownloading"}), false)
}
func TestClean(t *testing.T) {
test.IsEqualInt(t, len(configuration.ServerSettings.DownloadStatus), 1)
Clean()
test.IsEqualInt(t, len(configuration.ServerSettings.DownloadStatus), 1)
status := configuration.ServerSettings.DownloadStatus[statusId]
status.ExpireAt = 1
configuration.ServerSettings.DownloadStatus[statusId] = status
test.IsEqualInt(t, len(configuration.ServerSettings.DownloadStatus), 1)
Clean()
test.IsEqualInt(t, len(configuration.ServerSettings.DownloadStatus), 0)
}

View File

@@ -6,12 +6,12 @@ Serving and processing uploaded files
import (
"Gokapi/internal/configuration"
"Gokapi/internal/configuration/downloadStatus"
"Gokapi/internal/helper"
"Gokapi/internal/storage/filestructure"
"crypto/sha1"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
@@ -47,6 +47,7 @@ func processUpload(fileContent *[]byte, fileHeader *multipart.FileHeader, expire
ExpireAtString: time.Unix(expireAt, 0).Format("2006-01-02 15:04"),
DownloadsRemaining: downloads,
PasswordHash: configuration.HashPassword(password, true),
ContentType: fileHeader.Header.Get("Content-Type"),
}
addHotlink(&file)
configuration.ServerSettings.Files[id] = file
@@ -111,33 +112,33 @@ func GetFileByHotlink(id string) (filestructure.File, bool) {
func ServeFile(file filestructure.File, w http.ResponseWriter, r *http.Request, forceDownload bool) {
file.DownloadsRemaining = file.DownloadsRemaining - 1
configuration.ServerSettings.Files[file.Id] = file
// Investigate: Possible race condition with clean-up routine?
configuration.Save()
if forceDownload {
w.Header().Set("Content-Disposition", "attachment; filename=\""+file.Name+"\"")
}
w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
storageData, err := os.OpenFile(configuration.ServerSettings.DataDir+"/"+file.SHA256, os.O_RDONLY, 0644)
helper.Check(err)
defer storageData.Close()
size, err := helper.GetFileSize(storageData)
if err == nil {
w.Header().Set("Content-Length", strconv.FormatInt(size, 10))
}
helper.Check(err)
_, _ = io.Copy(w, storageData)
if forceDownload {
w.Header().Set("Content-Disposition", "attachment; filename=\""+file.Name+"\"")
}
w.Header().Set("Content-Length", strconv.FormatInt(size, 10))
w.Header().Set("Content-Type", file.ContentType)
statusId := downloadStatus.SetDownload(file)
configuration.Save()
http.ServeContent(w, r, file.Name, time.Now(), storageData)
downloadStatus.SetComplete(statusId)
configuration.Save()
}
// CleanUp removes expired files from the config and from the filesystem if they are not referenced by other files anymore
// Will be called periodically or after a file has been manually deleted in the admin view.
// If parameter periodic is true, this function is recursive and calls itself every hour.
func CleanUp(periodic bool) {
downloadStatus.Clean()
timeNow := time.Now().Unix()
wasItemDeleted := false
for key, element := range configuration.ServerSettings.Files {
fileExists := helper.FileExists(configuration.ServerSettings.DataDir + "/" + element.SHA256)
if element.ExpireAt < timeNow || element.DownloadsRemaining < 1 || !fileExists {
if (element.ExpireAt < timeNow || element.DownloadsRemaining < 1 || !fileExists) && !downloadStatus.IsCurrentlyDownloading(element) {
deleteFile := true
for _, secondLoopElement := range configuration.ServerSettings.Files {
if element.Id != secondLoopElement.Id && element.SHA256 == secondLoopElement.SHA256 {

View File

@@ -100,6 +100,7 @@ func TestServeFile(t *testing.T) {
test.IsEqualString(t, w.Result().Header.Get("Content-Disposition"), "attachment; filename=\"test.dat\"")
test.IsEqualString(t, w.Result().Header.Get("Content-Length"), "35")
test.IsEqualString(t, w.Result().Header.Get("Content-Type"), "text")
content, err := ioutil.ReadAll(w.Result().Body)
test.IsNil(t, err)
test.IsEqualString(t, string(content), "This is a file for testing purposes")
@@ -107,13 +108,17 @@ func TestServeFile(t *testing.T) {
}
func TestCleanUp(t *testing.T) {
test.IsEqualString(t, configuration.ServerSettings.Files["cleanuptest123456789"].Name, "cleanup")
test.IsEqualString(t, configuration.ServerSettings.Files["Wzol7LyY2QVczXynJtVo"].Name, "smallfile2")
test.IsEqualString(t, configuration.ServerSettings.Files["e4TjE7CokWK0giiLNxDL"].Name, "smallfile2")
test.IsEqualString(t, configuration.ServerSettings.Files["wefffewhtrhhtrhtrhtr"].Name, "smallfile3")
test.IsEqualString(t, configuration.ServerSettings.Files["n1tSTAGj8zan9KaT4u6p"].Name, "picture.jpg")
test.IsEqualString(t, configuration.ServerSettings.Files["deletedfile123456789"].Name, "DeletedFile")
test.IsEqualBool(t, helper.FileExists("test/data/2341354656543213246465465465432456898794"), true)
CleanUp(false)
test.IsEqualString(t, configuration.ServerSettings.Files["cleanuptest123456789"].Name, "cleanup")
test.IsEqualBool(t, helper.FileExists("test/data/2341354656543213246465465465432456898794"), true)
test.IsEqualString(t, configuration.ServerSettings.Files["deletedfile123456789"].Name, "")
test.IsEqualString(t, configuration.ServerSettings.Files["Wzol7LyY2QVczXynJtVo"].Name, "smallfile2")
test.IsEqualString(t, configuration.ServerSettings.Files["e4TjE7CokWK0giiLNxDL"].Name, "smallfile2")
@@ -159,4 +164,10 @@ func TestCleanUp(t *testing.T) {
test.IsEqualString(t, configuration.ServerSettings.Files["e4TjE7CokWK0giiLNxDL"].Name, "")
test.IsEqualString(t, configuration.ServerSettings.Files["wefffewhtrhhtrhtrhtr"].Name, "")
test.IsEqualString(t, configuration.ServerSettings.Files["cleanuptest123456789"].Name, "cleanup")
test.IsEqualBool(t, helper.FileExists("test/data/2341354656543213246465465465432456898794"), true)
configuration.ServerSettings.DownloadStatus = make(map[string]filestructure.DownloadStatus)
CleanUp(false)
test.IsEqualString(t, configuration.ServerSettings.Files["cleanuptest123456789"].Name, "")
test.IsEqualBool(t, helper.FileExists("test/data/2341354656543213246465465465432456898794"), false)
}

View File

@@ -16,6 +16,7 @@ type File struct {
DownloadsRemaining int `json:"DownloadsRemaining"`
PasswordHash string `json:"PasswordHash"`
HotlinkId string `json:"HotlinkId"`
ContentType string `json:"ContentType"`
}
// Hotlink is a struct containing hotlink ids
@@ -47,3 +48,11 @@ type Result struct {
Url string `json:"Url"`
HotlinkUrl string `json:"HotlinkUrl"`
}
// DownloadStatus contains current downloads, so they do not get removed during cleanup
type DownloadStatus struct {
Id string
FileId string
ExpireAt int64
}

View File

@@ -16,6 +16,7 @@ func TestToJsonResult(t *testing.T) {
DownloadsRemaining: 1,
PasswordHash: "pwhash",
HotlinkId: "hotlinkid",
ContentType: "test/html",
}
test.IsEqualString(t, file.ToJsonResult("serverurl/"), `{"Result":"OK","FileInfo":{"Id":"testId","Name":"testName","Size":"10 B","SHA256":"sha256","ExpireAt":50,"ExpireAtString":"future","DownloadsRemaining":1,"PasswordHash":"pwhash","HotlinkId":"hotlinkid"},"Url":"serverurl/d?id=","HotlinkUrl":"serverurl/hotlink/"}`)
test.IsEqualString(t, file.ToJsonResult("serverurl/"), `{"Result":"OK","FileInfo":{"Id":"testId","Name":"testName","Size":"10 B","SHA256":"sha256","ExpireAt":50,"ExpireAtString":"future","DownloadsRemaining":1,"PasswordHash":"pwhash","HotlinkId":"hotlinkid","ContentType":"test/html"},"Url":"serverurl/d?id=","HotlinkUrl":"serverurl/hotlink/"}`)
}

View File

@@ -18,6 +18,7 @@ func Create(initFiles bool) {
os.WriteFile("test/data/a8fdc205a9f19cc1c7507a60c4f01b13d11d7fd0", []byte("123"), 0777)
os.WriteFile("test/data/c4f9375f9834b4e7f0a528cc65c055702bf5f24a", []byte("456"), 0777)
os.WriteFile("test/data/e017693e4a04a59d0b0f400fe98177fe7ee13cf7", []byte("789"), 0777)
os.WriteFile("test/data/2341354656543213246465465465432456898794", []byte("abc"), 0777)
os.WriteFile("test/fileupload.jpg", []byte("abc"), 0777)
}
}
@@ -64,6 +65,10 @@ var configTestFile = []byte(`{
"RenewAt":2147483645,
"ValidUntil":2147483646
},
"logoutsession":{
"RenewAt":2147483645,
"ValidUntil":2147483646
},
"needsRenewal":{
"RenewAt":0,
"ValidUntil":2147483646
@@ -83,6 +88,7 @@ var configTestFile = []byte(`{
"ExpireAtString":"2021-05-04 15:19",
"DownloadsRemaining":1,
"PasswordHash":"",
"ContentType":"text/html",
"HotlinkId":""
},
"e4TjE7CokWK0giiLNxDL":{
@@ -94,6 +100,7 @@ var configTestFile = []byte(`{
"ExpireAtString":"2021-05-04 15:19",
"DownloadsRemaining":2,
"PasswordHash":"",
"ContentType":"text/html",
"HotlinkId":""
},
"wefffewhtrhhtrhtrhtr":{
@@ -105,6 +112,7 @@ var configTestFile = []byte(`{
"ExpireAtString":"2021-05-04 15:19",
"DownloadsRemaining":1,
"PasswordHash":"",
"ContentType":"text/html",
"HotlinkId":""
},
"deletedfile123456789":{
@@ -116,6 +124,7 @@ var configTestFile = []byte(`{
"ExpireAtString":"2021-05-04 15:19",
"DownloadsRemaining":2,
"PasswordHash":"",
"ContentType":"text/html",
"HotlinkId":""
},
"jpLXGJKigM4hjtA6T6sN":{
@@ -126,6 +135,19 @@ var configTestFile = []byte(`{
"ExpireAt":2147483646,
"ExpireAtString":"2021-05-04 15:18",
"DownloadsRemaining":1,
"ContentType":"text/html",
"PasswordHash":"7b30508aa9b233ab4b8a11b2af5816bdb58ca3e7",
"HotlinkId":""
},
"jpLXGJKigM4hjtA6T6sN2":{
"Id":"jpLXGJKigM4hjtA6T6sN2",
"Name":"smallfile",
"Size":"7 B",
"SHA256":"c4f9375f9834b4e7f0a528cc65c055702bf5f24a",
"ExpireAt":2147483646,
"ExpireAtString":"2021-05-04 15:18",
"DownloadsRemaining":1,
"ContentType":"text/html",
"PasswordHash":"7b30508aa9b233ab4b8a11b2af5816bdb58ca3e7",
"HotlinkId":""
},
@@ -138,7 +160,20 @@ var configTestFile = []byte(`{
"ExpireAtString":"2021-05-04 15:19",
"DownloadsRemaining":1,
"PasswordHash":"",
"ContentType":"text/html",
"HotlinkId":"PhSs6mFtf8O5YGlLMfNw9rYXx9XRNkzCnJZpQBi7inunv3Z4A.jpg"
},
"cleanuptest123456789":{
"Id":"cleanuptest123456789",
"Name":"cleanup",
"Size":"4 B",
"SHA256":"2341354656543213246465465465432456898794",
"ExpireAt":2147483646,
"ExpireAtString":"2021-05-04 15:19",
"DownloadsRemaining":0,
"PasswordHash":"",
"ContentType":"text/html",
"HotlinkId":""
}
},
"Hotlinks":{
@@ -147,7 +182,14 @@ var configTestFile = []byte(`{
"FileId":"n1tSTAGj8zan9KaT4u6p"
}
},
"ConfigVersion":4,
"DownloadStatus":{
"69JCbLVxx2KxfvB6FYkrDn3oCU7BWT":{
"Id":"69JCbLVxx2KxfvB6FYkrDn3oCU7BWT",
"FileId":"cleanuptest123456789",
"ExpireAt":2147483646
}
},
"ConfigVersion":5,
"SaltAdmin":"LW6fW4Pjv8GtdWVLSZD66gYEev6NAaXxOVBw7C",
"SaltFiles":"lL5wMTtnVCn5TPbpRaSe4vAQodWW0hgk00WCZE",
"LengthId":20,

View File

@@ -34,7 +34,7 @@ var staticFolderEmbedded embed.FS
//go:embed web/templates
var templateFolderEmbedded embed.FS
const timeOutWebserver = 2 * time.Hour
const timeOutWebserver = 12 * time.Hour
// Variable containing all parsed templates
var templateFolder *template.Template
@@ -74,7 +74,7 @@ func Start() {
srv := &http.Server{
Addr: configuration.ServerSettings.Port,
ReadTimeout: timeOutWebserver,
WriteTimeout: 10 * time.Second,
WriteTimeout: timeOutWebserver,
}
log.Fatal(srv.ListenAndServe())
}

View File

@@ -14,6 +14,9 @@ import (
func TestMain(m *testing.M) {
testconfiguration.Create(true)
configuration.Load()
go Start()
time.Sleep(1 * time.Second)
exitVal := m.Run()
testconfiguration.Delete()
os.Exit(exitVal)
@@ -33,64 +36,75 @@ func TestEmbedFs(t *testing.T) {
}
}
func TestWebserverEmbedFs(t *testing.T) {
configuration.Load()
go Start()
time.Sleep(1 * time.Second)
// Index redirect
func TestIndexRedirect(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/",
RequiredContent: "<html><head><meta http-equiv=\"Refresh\" content=\"0; URL=./index\"></head></html>",
RequiredContent: []string{"<html><head><meta http-equiv=\"Refresh\" content=\"0; URL=./index\"></head></html>"},
IsHtml: true,
})
// Index file
}
func TestIndexFile(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/index",
RequiredContent: configuration.ServerSettings.RedirectUrl,
RequiredContent: []string{configuration.ServerSettings.RedirectUrl},
IsHtml: true,
})
// CSS file
}
func TestStaticDirs(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/css/cover.css",
RequiredContent: ".btn-secondary:hover",
RequiredContent: []string{".btn-secondary:hover"},
})
// Login page
}
func TestLogin(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/login",
RequiredContent: "id=\"uname_hidden\"",
RequiredContent: []string{"id=\"uname_hidden\""},
IsHtml: true,
})
// Admin without auth
}
func TestAdminNoAuth(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/admin",
RequiredContent: "URL=./login\"",
RequiredContent: []string{"URL=./login\""},
IsHtml: true,
})
// Admin with auth
}
func TestAdminAuth(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/admin",
RequiredContent: "Downloads remaining",
RequiredContent: []string{"Downloads remaining"},
IsHtml: true,
Cookies: []test.Cookie{{
Name: "session_token",
Value: "validsession",
}},
})
// Admin with expired session
}
func TestAdminExpiredAuth(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/admin",
RequiredContent: "URL=./login\"",
RequiredContent: []string{"URL=./login\""},
IsHtml: true,
Cookies: []test.Cookie{{
Name: "session_token",
Value: "expiredsession",
}},
})
// Admin with auth needing renewal
}
func TestAdminRenewalAuth(t *testing.T) {
t.Parallel()
cookies := test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/admin",
RequiredContent: "Downloads remaining",
RequiredContent: []string{"Downloads remaining"},
IsHtml: true,
Cookies: []test.Cookie{{
Name: "session_token",
@@ -109,124 +123,209 @@ func TestWebserverEmbedFs(t *testing.T) {
}
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/admin",
RequiredContent: "Downloads remaining",
RequiredContent: []string{"Downloads remaining"},
IsHtml: true,
Cookies: []test.Cookie{{
Name: "session_token",
Value: sessionCookie,
}},
})
}
// Admin with invalid auth
func TestAdminInvalidAuth(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/admin",
RequiredContent: "URL=./login\"",
RequiredContent: []string{"URL=./login\""},
IsHtml: true,
Cookies: []test.Cookie{{
Name: "session_token",
Value: "invalid",
}},
})
// Invalid link
}
func TestInvalidLink(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/d?id=123",
RequiredContent: "URL=./error\"",
RequiredContent: []string{"URL=./error\""},
IsHtml: true,
})
// Error
}
func TestError(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/error",
RequiredContent: "this file cannot be found",
RequiredContent: []string{"this file cannot be found"},
IsHtml: true,
})
// Forgot pw
}
func TestForgotPw(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/forgotpw",
RequiredContent: "--reset-pw",
RequiredContent: []string{"--reset-pw"},
IsHtml: true,
})
// Login correct
}
func TestLoginCorrect(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/login",
RequiredContent: "URL=./admin\"",
RequiredContent: []string{"URL=./admin\""},
IsHtml: true,
Method: "POST",
PostValues: []test.PostBody{{"username", "test"}, {"password", "testtest"}},
})
// Login incorrect
}
func TestLoginIncorrectPassword(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/login",
RequiredContent: "Incorrect username or password",
RequiredContent: []string{"Incorrect username or password"},
IsHtml: true,
Method: "POST",
PostValues: []test.PostBody{{"username", "test"}, {"password", "incorrect"}},
})
// Login incorrect
}
func TestLoginIncorrectUsername(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/login",
RequiredContent: "Incorrect username or password",
RequiredContent: []string{"Incorrect username or password"},
IsHtml: true,
Method: "POST",
PostValues: []test.PostBody{{"username", "incorrect"}, {"password", "incorrect"}},
})
// Download hotlink
}
func TestLogout(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/admin",
RequiredContent: []string{"Downloads remaining"},
IsHtml: true,
Cookies: []test.Cookie{{
Name: "session_token",
Value: "logoutsession",
}},
})
// Logout
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/logout",
RequiredContent: []string{"URL=./login\""},
IsHtml: true,
Cookies: []test.Cookie{{
Name: "session_token",
Value: "logoutsession",
}},
})
// Admin after logout
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/admin",
RequiredContent: []string{"URL=./login\""},
IsHtml: true,
Cookies: []test.Cookie{{
Name: "session_token",
Value: "logoutsession",
}},
})
}
func TestDownloadHotlink(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/hotlink/PhSs6mFtf8O5YGlLMfNw9rYXx9XRNkzCnJZpQBi7inunv3Z4A.jpg",
RequiredContent: "123",
RequiredContent: []string{"123"},
})
// Download expired hotlink
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/hotlink/PhSs6mFtf8O5YGlLMfNw9rYXx9XRNkzCnJZpQBi7inunv3Z4A.jpg",
RequiredContent: "Created with GIMP",
RequiredContent: []string{"Created with GIMP"},
})
// Show download page no password
}
func TestDownloadNoPassword(t *testing.T) {
t.Parallel()
// Show download page
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/d?id=Wzol7LyY2QVczXynJtVo",
IsHtml: true,
RequiredContent: "smallfile2",
RequiredContent: []string{"smallfile2"},
})
// Download file no password
// Download
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/downloadFile?id=Wzol7LyY2QVczXynJtVo",
RequiredContent: "789",
RequiredContent: []string{"789"},
})
// Show download page expired file
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/d?id=Wzol7LyY2QVczXynJtVo",
IsHtml: true,
RequiredContent: "URL=./error\"",
RequiredContent: []string{"URL=./error\""},
})
// Download expired file
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/downloadFile?id=Wzol7LyY2QVczXynJtVo",
IsHtml: true,
RequiredContent: "URL=./error\"",
RequiredContent: []string{"URL=./error\""},
})
// Show download page password
}
func TestDownloadPagePassword(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/d?id=jpLXGJKigM4hjtA6T6sN",
IsHtml: true,
RequiredContent: "Password required",
RequiredContent: []string{"Password required"},
})
// Show download page incorrect password
}
func TestDownloadPageIncorrectPassword(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/d?id=jpLXGJKigM4hjtA6T6sN",
IsHtml: true,
RequiredContent: "Incorrect password!",
RequiredContent: []string{"Incorrect password!"},
Method: "POST",
PostValues: []test.PostBody{{"password", "incorrect"}},
})
// Submit download page correct password
cookies = test.HttpPageResult(t, test.HttpTestConfig{
}
func TestDownloadIncorrectPasswordCookie(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/d?id=jpLXGJKigM4hjtA6T6sN",
IsHtml: true,
RequiredContent: "URL=./d?id=jpLXGJKigM4hjtA6T6sN",
RequiredContent: []string{"Password required"},
Cookies: []test.Cookie{{"pjpLXGJKigM4hjtA6T6sN", "invalid"}},
})
}
func TestDownloadIncorrectPassword(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/downloadFile?id=jpLXGJKigM4hjtA6T6sN",
IsHtml: true,
RequiredContent: []string{"URL=./d?id=jpLXGJKigM4hjtA6T6sN"},
Cookies: []test.Cookie{{"pjpLXGJKigM4hjtA6T6sN", "invalid"}},
})
}
func TestDownloadCorrectPassword(t *testing.T) {
t.Parallel()
// Submit download page correct password
cookies := test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/d?id=jpLXGJKigM4hjtA6T6sN2",
IsHtml: true,
RequiredContent: []string{"URL=./d?id=jpLXGJKigM4hjtA6T6sN2"},
Method: "POST",
PostValues: []test.PostBody{{"password", "123"}},
})
pwCookie := ""
for _, cookie := range cookies {
if (*cookie).Name == "pjpLXGJKigM4hjtA6T6sN" {
if (*cookie).Name == "pjpLXGJKigM4hjtA6T6sN2" {
pwCookie = (*cookie).Value
break
}
@@ -236,79 +335,79 @@ func TestWebserverEmbedFs(t *testing.T) {
}
// Show download page correct password
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/d?id=jpLXGJKigM4hjtA6T6sN",
Url: "http://127.0.0.1:53843/d?id=jpLXGJKigM4hjtA6T6sN2",
IsHtml: true,
RequiredContent: "smallfile",
Cookies: []test.Cookie{{"pjpLXGJKigM4hjtA6T6sN", pwCookie}},
})
// Show download page incorrect password cookie
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/d?id=jpLXGJKigM4hjtA6T6sN",
IsHtml: true,
RequiredContent: "Password required",
Cookies: []test.Cookie{{"pjpLXGJKigM4hjtA6T6sN", "invalid"}},
})
// Download incorrect password
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/downloadFile?id=jpLXGJKigM4hjtA6T6sN",
IsHtml: true,
RequiredContent: "URL=./d?id=jpLXGJKigM4hjtA6T6sN",
Cookies: []test.Cookie{{"pjpLXGJKigM4hjtA6T6sN", "invalid"}},
RequiredContent: []string{"smallfile"},
Cookies: []test.Cookie{{"pjpLXGJKigM4hjtA6T6sN2", pwCookie}},
})
// Download correct password
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/downloadFile?id=jpLXGJKigM4hjtA6T6sN",
RequiredContent: "456",
Cookies: []test.Cookie{{"pjpLXGJKigM4hjtA6T6sN", pwCookie}},
Url: "http://127.0.0.1:53843/downloadFile?id=jpLXGJKigM4hjtA6T6sN2",
RequiredContent: []string{"456"},
Cookies: []test.Cookie{{"pjpLXGJKigM4hjtA6T6sN2", pwCookie}},
})
// Delete file non-auth
}
func TestDeleteFileNonAuth(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/delete?id=e4TjE7CokWK0giiLNxDL",
IsHtml: true,
RequiredContent: "URL=./login",
RequiredContent: []string{"URL=./login"},
})
// Delete file authorised
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/delete?id=e4TjE7CokWK0giiLNxDL",
IsHtml: true,
RequiredContent: "URL=./admin",
Cookies: []test.Cookie{{
Name: "session_token",
Value: "validsession",
}},
})
// Delete file authorised, invalid key
}
func TestDeleteFileInvalidKey(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/delete",
IsHtml: true,
RequiredContent: "URL=./admin",
RequiredContent: []string{"URL=./admin"},
Cookies: []test.Cookie{{
Name: "session_token",
Value: "validsession",
}},
})
// Post upload unauthorized
test.HttpPostRequest(t, "http://127.0.0.1:53843/upload", "test/fileupload.jpg", "file", "{\"Result\":\"error\",\"ErrorMessage\":\"Not authenticated\"}", []test.Cookie{})
// Post upload authorized
test.HttpPostRequest(t, "http://127.0.0.1:53843/upload", "test/fileupload.jpg", "file", "fileupload.jpg", []test.Cookie{{
Name: "session_token",
Value: "validsession",
}})
// Logout
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/logout",
RequiredContent: "URL=./login\"",
IsHtml: true,
Cookies: []test.Cookie{{
Name: "session_token",
Value: "validsession",
}},
})
// Admin after logout
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/admin",
RequiredContent: "URL=./login\"",
Url: "http://127.0.0.1:53843/delete?id=",
IsHtml: true,
RequiredContent: []string{"URL=./admin"},
Cookies: []test.Cookie{{
Name: "session_token",
Value: "validsession",
}},
})
}
func TestDeleteFile(t *testing.T) {
t.Parallel()
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/delete?id=e4TjE7CokWK0giiLNxDL",
IsHtml: true,
RequiredContent: []string{"URL=./admin"},
Cookies: []test.Cookie{{
Name: "session_token",
Value: "validsession",
}},
})
}
func TestPostUploadNoAuth(t *testing.T) {
t.Parallel()
test.HttpPostRequest(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/upload",
UploadFileName: "test/fileupload.jpg",
UploadFieldName: "file",
RequiredContent: []string{"{\"Result\":\"error\",\"ErrorMessage\":\"Not authenticated\"}"},
})
}
func TestPostUpload(t *testing.T) {
test.HttpPostRequest(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/upload",
UploadFileName: "test/fileupload.jpg",
UploadFieldName: "file",
RequiredContent: []string{"{\"Result\":\"OK\"", "\"Name\":\"fileupload.jpg\"", "\"SHA256\":\"a9993e364706816aba3e25717850c26c9cd0d89d\"", "DownloadsRemaining\":3"},
ExcludedContent: []string{"\"Id\":\"\"", "HotlinkId\":\"\""},
Cookies: []test.Cookie{{
Name: "session_token",
Value: "validsession",

View File

@@ -36,6 +36,20 @@ func IsEqualInt(t *testing.T, got, want int) {
}
}
// IsNotEmpty fails test if string is empty
func IsNotEmpty(t *testing.T, s string) {
if s == "" {
t.Errorf("Assertion failed, got: %s, want: empty.", s)
}
}
// IsEmpty fails test if string is not empty
func IsEmpty(t *testing.T, s string) {
if s != "" {
t.Errorf("Assertion failed, got: %s, want: empty.", s)
}
}
// IsNil fails test if error not nil
func IsNil(t *testing.T, got error) {
if got != nil {
@@ -44,22 +58,22 @@ func IsNil(t *testing.T, got error) {
}
// HttpPageResult tests if a http server is outputting the correct result
func HttpPageResult(t *testing.T, configuration HttpTestConfig) []*http.Cookie {
configuration.init()
func HttpPageResult(t *testing.T, config HttpTestConfig) []*http.Cookie {
config.init()
client := &http.Client{}
data := url.Values{}
for _, value := range configuration.PostValues {
for _, value := range config.PostValues {
data.Add(value.Key, value.Value)
}
req, err := http.NewRequest(configuration.Method, configuration.Url, strings.NewReader(data.Encode()))
req, err := http.NewRequest(config.Method, config.Url, strings.NewReader(data.Encode()))
IsNil(t, err)
for _, cookie := range configuration.Cookies {
for _, cookie := range config.Cookies {
req.Header.Set("Cookie", cookie.toString())
}
if len(configuration.PostValues) > 0 {
if len(config.PostValues) > 0 {
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
}
@@ -69,13 +83,20 @@ func HttpPageResult(t *testing.T, configuration HttpTestConfig) []*http.Cookie {
if resp.StatusCode != 200 {
t.Errorf("Status %d != 200", resp.StatusCode)
}
bs, err := ioutil.ReadAll(resp.Body)
content, err := ioutil.ReadAll(resp.Body)
IsNil(t, err)
if configuration.IsHtml && !bytes.Contains(bs, []byte("</html>")) {
t.Error(configuration.Url + ": Incorrect response")
if config.IsHtml && !bytes.Contains(content, []byte("</html>")) {
t.Error(config.Url + ": Incorrect response")
}
if configuration.RequiredContent != "" && !bytes.Contains(bs, []byte(configuration.RequiredContent)) {
t.Error(configuration.Url + ": Incorrect response. Got:\n" + string(bs))
for _, requiredString := range config.RequiredContent {
if !bytes.Contains(content, []byte(requiredString)) {
t.Error(config.Url + ": Incorrect response. Got:\n" + string(content))
}
}
for _, excludedString := range config.ExcludedContent {
if bytes.Contains(content, []byte(excludedString)) {
t.Error(config.Url + ": Incorrect response. Got:\n" + string(content))
}
}
resp.Body.Close()
return resp.Cookies()
@@ -84,11 +105,14 @@ func HttpPageResult(t *testing.T, configuration HttpTestConfig) []*http.Cookie {
// HttpTestConfig is a struct for http test init
type HttpTestConfig struct {
Url string
RequiredContent string
RequiredContent []string
ExcludedContent []string
IsHtml bool
Method string
PostValues []PostBody
Cookies []Cookie
UploadFileName string
UploadFieldName string
}
func (c *HttpTestConfig) init() {
@@ -117,21 +141,21 @@ type PostBody struct {
}
// HttpPostRequest sends a post request
func HttpPostRequest(t *testing.T, url, filename, fieldName, requiredText string, cookies []Cookie) {
file, err := os.Open(filename)
func HttpPostRequest(t *testing.T, config HttpTestConfig) {
file, err := os.Open(config.UploadFileName)
IsNil(t, err)
defer file.Close()
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile(fieldName, filepath.Base(file.Name()))
part, err := writer.CreateFormFile(config.UploadFieldName, filepath.Base(file.Name()))
IsNil(t, err)
io.Copy(part, file)
writer.Close()
request, err := http.NewRequest("POST", url, body)
request, err := http.NewRequest("POST", config.Url, body)
IsNil(t, err)
for _, cookie := range cookies {
for _, cookie := range config.Cookies {
request.Header.Set("Cookie", cookie.toString())
}
request.Header.Add("Content-Type", writer.FormDataContentType())
@@ -144,7 +168,14 @@ func HttpPostRequest(t *testing.T, url, filename, fieldName, requiredText string
content, err := ioutil.ReadAll(response.Body)
IsNil(t, err)
if requiredText != "" && !bytes.Contains(content, []byte(requiredText)) {
t.Error(url + ": Incorrect response. Got:\n" + string(content))
for _, requiredString := range config.RequiredContent {
if !bytes.Contains(content, []byte(requiredString)) {
t.Error(config.Url + ": Incorrect response. Got:\n" + string(content))
}
}
for _, excludedString := range config.ExcludedContent {
if bytes.Contains(content, []byte(excludedString)) {
t.Error(config.Url + ": Incorrect response. Got:\n" + string(content))
}
}
}