Refactoring, early implementation of API

This commit is contained in:
Marc Ole Bulling
2021-05-06 12:35:07 +02:00
parent 733aa55637
commit c9ad7064fa
10 changed files with 420 additions and 104 deletions

View File

@@ -0,0 +1,9 @@
package models
type UploadRequest struct {
AllowedDownloads int
Expiry int
ExpiryTimestamp int64
Password string
ExternalUrl string
}

View File

@@ -26,7 +26,7 @@ import (
// NewFile creates a new file in the system. Called after an upload has been completed. If a file with the same sha256 hash
// already exists, it is deduplicated. This function gathers information about the file, creates an ID and saves
// it into the global configuration.
func NewFile(fileContent io.Reader, fileHeader *multipart.FileHeader, expireAt int64, downloads int, password string) (models.File, error) {
func NewFile(fileContent io.Reader, fileHeader *multipart.FileHeader, uploadRequest models.UploadRequest) (models.File, error) {
fileBytes, err := ioutil.ReadAll(fileContent)
if err != nil {
return models.File{}, err
@@ -39,10 +39,10 @@ func NewFile(fileContent io.Reader, fileHeader *multipart.FileHeader, expireAt i
Name: fileHeader.Filename,
SHA256: hex.EncodeToString(hash.Sum(nil)),
Size: helper.ByteCountSI(fileHeader.Size),
ExpireAt: expireAt,
ExpireAtString: time.Unix(expireAt, 0).Format("2006-01-02 15:04"),
DownloadsRemaining: downloads,
PasswordHash: configuration.HashPassword(password, true),
ExpireAt: uploadRequest.ExpiryTimestamp,
ExpireAtString: time.Unix(uploadRequest.ExpiryTimestamp, 0).Format("2006-01-02 15:04"),
DownloadsRemaining: uploadRequest.AllowedDownloads,
PasswordHash: configuration.HashPassword(uploadRequest.Password, true),
ContentType: fileHeader.Header.Get("Content-Type"),
}
addHotlink(&file)
@@ -175,11 +175,19 @@ func CleanUp(periodic bool) {
}
// DeleteFile is called when an admin requests deletion of a file
func DeleteFile(keyId string) {
// Returns true if file was deleted or false if ID did not exist
func DeleteFile(keyId string) bool {
if keyId == "" {
return false
}
settings := configuration.GetServerSettings()
item := settings.Files[keyId]
item, ok := settings.Files[keyId]
if !ok {
return false
}
item.ExpireAt = 0
settings.Files[keyId] = item
configuration.Release()
CleanUp(false)
return true
}

View File

@@ -78,7 +78,12 @@ func TestNewFile(t *testing.T) {
Header: mimeHeader,
Size: int64(len(content)),
}
file, err := NewFile(bytes.NewReader(content), &header, 2147483600, 1, "")
request := models.UploadRequest{
AllowedDownloads: 1,
Expiry: 999,
ExpiryTimestamp: 2147483600,
}
file, err := NewFile(bytes.NewReader(content), &header, request)
test.IsNil(t, err)
test.IsEqualString(t, file.Name, "test.dat")
test.IsEqualString(t, file.SHA256, "f1474c19eff0fc8998fa6e1b1f7bf31793b103a6")
@@ -183,7 +188,12 @@ func TestDeleteFile(t *testing.T) {
configuration.Release()
test.IsEqualString(t, settings.Files["n1tSTAGj8zan9KaT4u6p"].Name, "picture.jpg")
test.IsEqualBool(t, helper.FileExists("test/data/a8fdc205a9f19cc1c7507a60c4f01b13d11d7fd0"), true)
DeleteFile("n1tSTAGj8zan9KaT4u6p")
result := DeleteFile("n1tSTAGj8zan9KaT4u6p")
test.IsEqualBool(t, result, true)
test.IsEqualString(t, settings.Files["n1tSTAGj8zan9KaT4u6p"].Name, "")
test.IsEqualBool(t, helper.FileExists("test/data/a8fdc205a9f19cc1c7507a60c4f01b13d11d7fd0"), false)
result = DeleteFile("invalid")
test.IsEqualBool(t, result, false)
result = DeleteFile("")
test.IsEqualBool(t, result, false)
}

View File

@@ -2,7 +2,7 @@ package test
import (
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
@@ -137,18 +137,34 @@ func TestHttpPostRequest(t *testing.T) {
Value: "testValue",
}},
})
mockT := MockTest{reference: t}
mockT.WantFail()
HttpPostRequest(mockT, HttpTestConfig{
Url: "http://127.0.0.1:9999/test",
UploadFileName: "testfile",
UploadFieldName: "file",
ExcludedContent: []string{"TestContent"}},
)
mockT.WantFail()
HttpPostRequest(mockT, HttpTestConfig{
Url: "http://127.0.0.1:9999/test",
UploadFileName: "testfile",
UploadFieldName: "file",
RequiredContent: []string{"invalid"}},
)
mockT.Check()
os.Remove("testfile")
}
func startTestServer() {
http.HandleFunc("/test", func(writer http.ResponseWriter, request *http.Request) {
fmt.Fprint(writer, "TestContent\n")
io.WriteString(writer, "TestContent\n")
for _, cookie := range request.Cookies() {
fmt.Fprint(writer, "cookie name: "+cookie.Name+" cookie value: "+cookie.Value+"\n")
io.WriteString(writer, "cookie name: "+cookie.Name+" cookie value: "+cookie.Value+"\n")
}
request.ParseForm()
if request.Form.Get("testPostKey") != "" {
fmt.Fprint(writer, "testPostKey: "+request.Form.Get("testPostKey")+"\n")
io.WriteString(writer, "testPostKey: "+request.Form.Get("testPostKey")+"\n")
}
})
go func() { log.Fatal(http.ListenAndServe("127.0.0.1:9999", nil)) }()

View File

@@ -9,16 +9,18 @@ import (
"Gokapi/internal/helper"
"Gokapi/internal/models"
"Gokapi/internal/storage"
"Gokapi/internal/webserver/api"
"Gokapi/internal/webserver/fileupload"
"Gokapi/internal/webserver/sessionmanager"
"embed"
"fmt"
"html/template"
"io"
"io/fs"
"log"
"net/http"
"os"
"sort"
"strconv"
"strings"
"time"
)
@@ -79,6 +81,7 @@ func Start() {
http.HandleFunc("/delete", deleteFile)
http.HandleFunc("/downloadFile", downloadFile)
http.HandleFunc("/forgotpw", forgotPassword)
http.HandleFunc("/api/", processApi)
fmt.Println("Binding webserver to " + webserverPort)
fmt.Println("Webserver can be accessed at " + webserverExtUrl + "admin")
srv := &http.Server{
@@ -116,7 +119,7 @@ func initTemplates(templateFolderEmbedded embed.FS) {
// Sends a redirect HTTP output to the client. Variable url is used to redirect to ./url
func redirect(w http.ResponseWriter, url string) {
_, _ = fmt.Fprint(w, "<html><head><meta http-equiv=\"Refresh\" content=\"0; URL=./"+url+"\"></head></html>")
_, _ = io.WriteString(w, "<html><head><meta http-equiv=\"Refresh\" content=\"0; URL=./"+url+"\"></head></html>")
}
// Handling of /logout
@@ -143,6 +146,11 @@ func forgotPassword(w http.ResponseWriter, r *http.Request) {
helper.Check(err)
}
// Handling of /api/
func processApi(w http.ResponseWriter, r *http.Request) {
api.Process(w, r)
}
// Handling of /login
// Shows a login form. If username / pw combo is incorrect, client needs to wait for three seconds.
// If correct, a new session is created and the user is redirected to the admin menu
@@ -326,40 +334,18 @@ func (u *UploadView) convertGlobalConfig() *UploadView {
// adds it to the system.
func uploadFile(w http.ResponseWriter, r *http.Request) {
addNoCacheHeader(w)
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
if !isAuthenticated(w, r, true) {
return
}
err := r.ParseMultipartForm(20 * 1024 * 1024)
err := fileupload.Process(w, r, true)
responseError(w, err)
allowedDownloads := r.Form.Get("allowedDownloads")
expiryDays := r.Form.Get("expiryDays")
password := r.Form.Get("password")
allowedDownloadsInt, err := strconv.Atoi(allowedDownloads)
settings := configuration.GetServerSettings()
if err != nil {
allowedDownloadsInt = settings.DefaultDownloads
}
expiryDaysInt, err := strconv.Atoi(expiryDays)
if err != nil {
expiryDaysInt = settings.DefaultExpiry
}
settings.DefaultExpiry = expiryDaysInt
settings.DefaultDownloads = allowedDownloadsInt
settings.DefaultPassword = password
configuration.Release()
file, header, err := r.FormFile("file")
responseError(w, err)
result, err := storage.NewFile(file, header, time.Now().Add(time.Duration(expiryDaysInt)*time.Hour*24).Unix(), allowedDownloadsInt, password)
responseError(w, err)
defer file.Close()
_, err = fmt.Fprint(w, result.ToJsonResult(webserverExtUrl))
helper.Check(err)
}
// Outputs an error in json format
func responseError(w http.ResponseWriter, err error) {
if err != nil {
fmt.Fprint(w, "{\"Result\":\"error\",\"ErrorMessage\":\""+err.Error()+"\"}")
_, _ = io.WriteString(w, "{\"Result\":\"error\",\"ErrorMessage\":\""+err.Error()+"\"}")
helper.Check(err)
}
}
@@ -388,7 +374,7 @@ func isAuthenticated(w http.ResponseWriter, r *http.Request, isUpload bool) bool
return true
}
if isUpload {
_, err := fmt.Fprint(w, "{\"Result\":\"error\",\"ErrorMessage\":\"Not authenticated\"}")
_, err := io.WriteString(w, "{\"Result\":\"error\",\"ErrorMessage\":\"Not authenticated\"}")
helper.Check(err)
} else {
redirect(w, "login")

View File

@@ -2,8 +2,8 @@ package webserver
import (
"Gokapi/internal/configuration"
testconfiguration "Gokapi/internal/test"
testconfiguration2 "Gokapi/internal/test/testconfiguration"
"Gokapi/internal/test"
"Gokapi/internal/test/testconfiguration"
"html/template"
"io/fs"
"os"
@@ -16,12 +16,12 @@ import (
// causes data race. It will be fixed in Go 1.17, see https://github.com/golang/go/issues/39807
func TestMain(m *testing.M) {
testconfiguration2.Create(true)
testconfiguration.Create(true)
configuration.Load()
go Start()
time.Sleep(1 * time.Second)
exitVal := m.Run()
testconfiguration2.Delete()
testconfiguration.Delete()
os.Exit(exitVal)
}
@@ -41,7 +41,7 @@ func TestEmbedFs(t *testing.T) {
func TestIndexRedirect(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/",
RequiredContent: []string{"<html><head><meta http-equiv=\"Refresh\" content=\"0; URL=./index\"></head></html>"},
IsHtml: true,
@@ -50,7 +50,7 @@ func TestIndexRedirect(t *testing.T) {
func TestIndexFile(t *testing.T) {
t.Parallel()
settings := configuration.GetServerSettings()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/index",
RequiredContent: []string{settings.RedirectUrl},
IsHtml: true,
@@ -59,14 +59,14 @@ func TestIndexFile(t *testing.T) {
}
func TestStaticDirs(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/css/cover.css",
RequiredContent: []string{".btn-secondary:hover"},
})
}
func TestLogin(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/login",
RequiredContent: []string{"id=\"uname_hidden\""},
IsHtml: true,
@@ -74,7 +74,7 @@ func TestLogin(t *testing.T) {
}
func TestAdminNoAuth(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/admin",
RequiredContent: []string{"URL=./login\""},
IsHtml: true,
@@ -82,11 +82,11 @@ func TestAdminNoAuth(t *testing.T) {
}
func TestAdminAuth(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/admin",
RequiredContent: []string{"Downloads remaining"},
IsHtml: true,
Cookies: []testconfiguration.Cookie{{
Cookies: []test.Cookie{{
Name: "session_token",
Value: "validsession",
}},
@@ -94,11 +94,11 @@ func TestAdminAuth(t *testing.T) {
}
func TestAdminExpiredAuth(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/admin",
RequiredContent: []string{"URL=./login\""},
IsHtml: true,
Cookies: []testconfiguration.Cookie{{
Cookies: []test.Cookie{{
Name: "session_token",
Value: "expiredsession",
}},
@@ -107,11 +107,11 @@ func TestAdminExpiredAuth(t *testing.T) {
func TestAdminRenewalAuth(t *testing.T) {
t.Parallel()
cookies := testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
cookies := test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/admin",
RequiredContent: []string{"Downloads remaining"},
IsHtml: true,
Cookies: []testconfiguration.Cookie{{
Cookies: []test.Cookie{{
Name: "session_token",
Value: "needsRenewal",
}},
@@ -126,11 +126,11 @@ func TestAdminRenewalAuth(t *testing.T) {
if sessionCookie == "needsRenewal" {
t.Error("Session not renewed")
}
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/admin",
RequiredContent: []string{"Downloads remaining"},
IsHtml: true,
Cookies: []testconfiguration.Cookie{{
Cookies: []test.Cookie{{
Name: "session_token",
Value: sessionCookie,
}},
@@ -139,11 +139,11 @@ func TestAdminRenewalAuth(t *testing.T) {
func TestAdminInvalidAuth(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/admin",
RequiredContent: []string{"URL=./login\""},
IsHtml: true,
Cookies: []testconfiguration.Cookie{{
Cookies: []test.Cookie{{
Name: "session_token",
Value: "invalid",
}},
@@ -152,7 +152,7 @@ func TestAdminInvalidAuth(t *testing.T) {
func TestInvalidLink(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/d?id=123",
RequiredContent: []string{"URL=./error\""},
IsHtml: true,
@@ -160,7 +160,7 @@ func TestInvalidLink(t *testing.T) {
}
func TestError(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/error",
RequiredContent: []string{"this file cannot be found"},
IsHtml: true,
@@ -168,7 +168,7 @@ func TestError(t *testing.T) {
}
func TestForgotPw(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/forgotpw",
RequiredContent: []string{"--reset-pw"},
IsHtml: true,
@@ -176,63 +176,63 @@ func TestForgotPw(t *testing.T) {
}
func TestLoginCorrect(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/login",
RequiredContent: []string{"URL=./admin\""},
IsHtml: true,
Method: "POST",
PostValues: []testconfiguration.PostBody{{"username", "test"}, {"password", "testtest"}},
PostValues: []test.PostBody{{"username", "test"}, {"password", "testtest"}},
})
}
func TestLoginIncorrectPassword(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/login",
RequiredContent: []string{"Incorrect username or password"},
IsHtml: true,
Method: "POST",
PostValues: []testconfiguration.PostBody{{"username", "test"}, {"password", "incorrect"}},
PostValues: []test.PostBody{{"username", "test"}, {"password", "incorrect"}},
})
}
func TestLoginIncorrectUsername(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/login",
RequiredContent: []string{"Incorrect username or password"},
IsHtml: true,
Method: "POST",
PostValues: []testconfiguration.PostBody{{"username", "incorrect"}, {"password", "incorrect"}},
PostValues: []test.PostBody{{"username", "incorrect"}, {"password", "incorrect"}},
})
}
func TestLogout(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/admin",
RequiredContent: []string{"Downloads remaining"},
IsHtml: true,
Cookies: []testconfiguration.Cookie{{
Cookies: []test.Cookie{{
Name: "session_token",
Value: "logoutsession",
}},
})
// Logout
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/logout",
RequiredContent: []string{"URL=./login\""},
IsHtml: true,
Cookies: []testconfiguration.Cookie{{
Cookies: []test.Cookie{{
Name: "session_token",
Value: "logoutsession",
}},
})
// Admin after logout
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://localhost:53843/admin",
RequiredContent: []string{"URL=./login\""},
IsHtml: true,
Cookies: []testconfiguration.Cookie{{
Cookies: []test.Cookie{{
Name: "session_token",
Value: "logoutsession",
}},
@@ -241,12 +241,12 @@ func TestLogout(t *testing.T) {
func TestDownloadHotlink(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/hotlink/PhSs6mFtf8O5YGlLMfNw9rYXx9XRNkzCnJZpQBi7inunv3Z4A.jpg",
RequiredContent: []string{"123"},
})
// Download expired hotlink
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/hotlink/PhSs6mFtf8O5YGlLMfNw9rYXx9XRNkzCnJZpQBi7inunv3Z4A.jpg",
RequiredContent: []string{"Created with GIMP"},
})
@@ -255,24 +255,24 @@ func TestDownloadHotlink(t *testing.T) {
func TestDownloadNoPassword(t *testing.T) {
t.Parallel()
// Show download page
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/d?id=Wzol7LyY2QVczXynJtVo",
IsHtml: true,
RequiredContent: []string{"smallfile2"},
})
// Download
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/downloadFile?id=Wzol7LyY2QVczXynJtVo",
RequiredContent: []string{"789"},
})
// Show download page expired file
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/d?id=Wzol7LyY2QVczXynJtVo",
IsHtml: true,
RequiredContent: []string{"URL=./error\""},
})
// Download expired file
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/downloadFile?id=Wzol7LyY2QVczXynJtVo",
IsHtml: true,
RequiredContent: []string{"URL=./error\""},
@@ -281,7 +281,7 @@ func TestDownloadNoPassword(t *testing.T) {
func TestDownloadPagePassword(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/d?id=jpLXGJKigM4hjtA6T6sN",
IsHtml: true,
RequiredContent: []string{"Password required"},
@@ -289,44 +289,44 @@ func TestDownloadPagePassword(t *testing.T) {
}
func TestDownloadPageIncorrectPassword(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/d?id=jpLXGJKigM4hjtA6T6sN",
IsHtml: true,
RequiredContent: []string{"Incorrect password!"},
Method: "POST",
PostValues: []testconfiguration.PostBody{{"password", "incorrect"}},
PostValues: []test.PostBody{{"password", "incorrect"}},
})
}
func TestDownloadIncorrectPasswordCookie(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/d?id=jpLXGJKigM4hjtA6T6sN",
IsHtml: true,
RequiredContent: []string{"Password required"},
Cookies: []testconfiguration.Cookie{{"pjpLXGJKigM4hjtA6T6sN", "invalid"}},
Cookies: []test.Cookie{{"pjpLXGJKigM4hjtA6T6sN", "invalid"}},
})
}
func TestDownloadIncorrectPassword(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/downloadFile?id=jpLXGJKigM4hjtA6T6sN",
IsHtml: true,
RequiredContent: []string{"URL=./d?id=jpLXGJKigM4hjtA6T6sN"},
Cookies: []testconfiguration.Cookie{{"pjpLXGJKigM4hjtA6T6sN", "invalid"}},
Cookies: []test.Cookie{{"pjpLXGJKigM4hjtA6T6sN", "invalid"}},
})
}
func TestDownloadCorrectPassword(t *testing.T) {
t.Parallel()
// Submit download page correct password
cookies := testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
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: []testconfiguration.PostBody{{"password", "123"}},
PostValues: []test.PostBody{{"password", "123"}},
})
pwCookie := ""
for _, cookie := range cookies {
@@ -339,23 +339,23 @@ func TestDownloadCorrectPassword(t *testing.T) {
t.Error("Cookie not set")
}
// Show download page correct password
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/d?id=jpLXGJKigM4hjtA6T6sN2",
IsHtml: true,
RequiredContent: []string{"smallfile"},
Cookies: []testconfiguration.Cookie{{"pjpLXGJKigM4hjtA6T6sN2", pwCookie}},
Cookies: []test.Cookie{{"pjpLXGJKigM4hjtA6T6sN2", pwCookie}},
})
// Download correct password
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/downloadFile?id=jpLXGJKigM4hjtA6T6sN2",
RequiredContent: []string{"456"},
Cookies: []testconfiguration.Cookie{{"pjpLXGJKigM4hjtA6T6sN2", pwCookie}},
Cookies: []test.Cookie{{"pjpLXGJKigM4hjtA6T6sN2", pwCookie}},
})
}
func TestDeleteFileNonAuth(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/delete?id=e4TjE7CokWK0giiLNxDL",
IsHtml: true,
RequiredContent: []string{"URL=./login"},
@@ -364,20 +364,20 @@ func TestDeleteFileNonAuth(t *testing.T) {
func TestDeleteFileInvalidKey(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/delete",
IsHtml: true,
RequiredContent: []string{"URL=./admin"},
Cookies: []testconfiguration.Cookie{{
Cookies: []test.Cookie{{
Name: "session_token",
Value: "validsession",
}},
})
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/delete?id=",
IsHtml: true,
RequiredContent: []string{"URL=./admin"},
Cookies: []testconfiguration.Cookie{{
Cookies: []test.Cookie{{
Name: "session_token",
Value: "validsession",
}},
@@ -386,11 +386,11 @@ func TestDeleteFileInvalidKey(t *testing.T) {
func TestDeleteFile(t *testing.T) {
t.Parallel()
testconfiguration.HttpPageResult(t, testconfiguration.HttpTestConfig{
test.HttpPageResult(t, test.HttpTestConfig{
Url: "http://127.0.0.1:53843/delete?id=e4TjE7CokWK0giiLNxDL",
IsHtml: true,
RequiredContent: []string{"URL=./admin"},
Cookies: []testconfiguration.Cookie{{
Cookies: []test.Cookie{{
Name: "session_token",
Value: "validsession",
}},
@@ -399,21 +399,22 @@ func TestDeleteFile(t *testing.T) {
func TestPostUploadNoAuth(t *testing.T) {
t.Parallel()
testconfiguration.HttpPostRequest(t, testconfiguration.HttpTestConfig{
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) {
testconfiguration.HttpPostRequest(t, testconfiguration.HttpTestConfig{
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: []testconfiguration.Cookie{{
Cookies: []test.Cookie{{
Name: "session_token",
Value: "validsession",
}},

View File

@@ -0,0 +1,97 @@
package api
import (
"Gokapi/internal/configuration"
"Gokapi/internal/helper"
"Gokapi/internal/storage"
"encoding/json"
"net/http"
"strings"
)
// Process parses the request and executes the API call or returns an error message to the sender
func Process(w http.ResponseWriter, r *http.Request) {
w.Header().Set("cache-control", "no-store")
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
request := parseRequest(r)
if !isAuthorised(w, request) {
return
}
switch request.requestUrl {
case "/files":
list(w)
case "/files/add":
upload(w, request)
case "/files/delete":
deleteFile(w, request)
default:
sendError(w, http.StatusBadRequest, "Invalid request")
}
}
func deleteFile(w http.ResponseWriter, request apiRequest) {
ok := storage.DeleteFile(request.headerId)
if ok {
sendOk(w)
} else {
sendError(w, http.StatusBadRequest, "Invalid id provided.")
}
}
func list(w http.ResponseWriter) {
sendOk(w)
settings := configuration.GetServerSettings()
result, err := json.Marshal(settings.Files)
configuration.Release()
helper.Check(err)
_, _ = w.Write(result)
}
func upload(w http.ResponseWriter, request apiRequest) {
sendOk(w)
// TODO
}
func isValidApiKey(key string) bool {
if key == "" {
return false
}
settings := configuration.GetServerSettings()
savedKey := settings.ApiKeys[key]
configuration.Release()
return savedKey.Id != ""
}
func isAuthorised(w http.ResponseWriter, request apiRequest) bool {
if true {
return true
}
if isValidApiKey(request.apiKey) {
return true
}
sendError(w, http.StatusUnauthorized, "Unauthorized")
return false
}
func sendError(w http.ResponseWriter, errorInt int, errorMessage string) {
w.WriteHeader(errorInt)
_, _ = w.Write([]byte("{\"Result\":\"error\",\"ErrorMessage\":\"" + errorMessage + "\"}"))
}
func sendOk(w http.ResponseWriter) {
w.WriteHeader(http.StatusOK)
}
type apiRequest struct {
apiKey string
requestUrl string
headerId string
}
func parseRequest(r *http.Request) apiRequest {
return apiRequest{
apiKey: r.Header.Get("apikey"),
headerId: r.Header.Get("id"),
requestUrl: strings.Replace(r.URL.String(), "/api", "", 1),
}
}

View File

@@ -0,0 +1,23 @@
package api
import (
"Gokapi/internal/configuration"
"Gokapi/internal/test"
"Gokapi/internal/test/testconfiguration"
"os"
"testing"
)
func TestMain(m *testing.M) {
testconfiguration.Create(false)
configuration.Load()
exitVal := m.Run()
testconfiguration.Delete()
os.Exit(exitVal)
}
func TestIsValidApiKey(t *testing.T) {
test.IsEqualBool(t, isValidApiKey(""), false)
test.IsEqualBool(t, isValidApiKey("invalid"), false)
test.IsEqualBool(t, isValidApiKey("validkey"), true)
}

View File

@@ -0,0 +1,73 @@
package fileupload
import (
"Gokapi/internal/configuration"
"Gokapi/internal/helper"
"Gokapi/internal/models"
"Gokapi/internal/storage"
"io"
"net/http"
"strconv"
"time"
)
func Process(w http.ResponseWriter, r *http.Request, isWeb bool) error {
err := r.ParseMultipartForm(20 * 1024 * 1024)
if err != nil {
return err
}
var config models.UploadRequest
if isWeb {
config = parseConfig(r.Form, true)
} else {
config = parseConfig(r.Header, false)
}
file, header, err := r.FormFile("file")
if err != nil {
return err
}
result, err := storage.NewFile(file, header, config)
if err != nil {
return err
}
defer file.Close()
_, err = io.WriteString(w, result.ToJsonResult(config.ExternalUrl))
if err != nil {
helper.Check(err)
}
return nil
}
func parseConfig(values formOrHeader, setNewDefaults bool) models.UploadRequest {
allowedDownloads := values.Get("allowedDownloads")
expiryDays := values.Get("expiryDays")
password := values.Get("password")
allowedDownloadsInt, err := strconv.Atoi(allowedDownloads)
settings := configuration.GetServerSettings()
if err != nil {
allowedDownloadsInt = settings.DefaultDownloads
}
expiryDaysInt, err := strconv.Atoi(expiryDays)
if err != nil {
expiryDaysInt = settings.DefaultExpiry
}
if setNewDefaults {
settings.DefaultExpiry = expiryDaysInt
settings.DefaultDownloads = allowedDownloadsInt
settings.DefaultPassword = password
}
externalUrl := settings.ServerUrl
configuration.Release()
return models.UploadRequest{
AllowedDownloads: allowedDownloadsInt,
Expiry: expiryDaysInt,
ExpiryTimestamp: time.Now().Add(time.Duration(expiryDaysInt) * time.Hour * 24).Unix(),
Password: password,
ExternalUrl: externalUrl,
}
}
type formOrHeader interface {
Get(key string) string
}

View File

@@ -0,0 +1,93 @@
package fileupload
import (
"Gokapi/internal/configuration"
"Gokapi/internal/models"
"Gokapi/internal/test"
"Gokapi/internal/test/testconfiguration"
"bytes"
"encoding/json"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
"os"
"reflect"
"testing"
)
func TestMain(m *testing.M) {
testconfiguration.Create(false)
configuration.Load()
exitVal := m.Run()
testconfiguration.Delete()
os.Exit(exitVal)
}
func TestParseConfig(t *testing.T) {
settings := configuration.GetServerSettings()
configuration.Release()
data := testData{
allowedDownloads: "9",
expiryDays: "5",
password: "123",
}
config := parseConfig(data, false)
test.IsEqualInt(t, config.AllowedDownloads, 9)
test.IsEqualString(t, config.Password, "123")
test.IsEqualInt(t, config.Expiry, 5)
test.IsEqualInt(t, settings.DefaultDownloads, 3)
config = parseConfig(data, true)
test.IsEqualInt(t, settings.DefaultDownloads, 9)
settings.DefaultDownloads = 3
settings.DefaultExpiry = 20
data.allowedDownloads = ""
data.expiryDays = "invalid"
config = parseConfig(data, false)
test.IsEqualInt(t, config.AllowedDownloads, 3)
test.IsEqualInt(t, config.Expiry, 20)
}
func TestProcess(t *testing.T) {
w := httptest.NewRecorder()
r := getRecorder()
err := Process(w, r, false)
test.IsNil(t, err)
resp := w.Result()
body, _ := io.ReadAll(resp.Body)
result := models.Result{}
err = json.Unmarshal(body, &result)
test.IsNil(t, err)
test.IsEqualString(t, result.Result, "OK")
test.IsEqualString(t, result.Url, "http://127.0.0.1:53843/d?id=")
test.IsEqualString(t, result.HotlinkUrl, "http://127.0.0.1:53843/hotlink/")
test.IsEqualString(t, result.FileInfo.Name, "testFile")
test.IsEqualString(t, result.FileInfo.SHA256, "17513aad503256b7fdc94d613aeb87b8338c433a")
test.IsEqualString(t, result.FileInfo.Size, "11 B")
}
func getRecorder() *http.Request {
var b bytes.Buffer
w := multipart.NewWriter(&b)
writer, _ := w.CreateFormFile("file", "testFile")
io.WriteString(writer, "testContent")
w.Close()
r := httptest.NewRequest("POST", "/upload", &b)
r.Header.Set("Content-Type", w.FormDataContentType())
r.Header.Add("allowedDownloads", "9")
r.Header.Add("expiryDays", "5")
r.Header.Add("password", "123")
return r
}
type testData struct {
allowedDownloads, expiryDays, password string
}
func (t testData) Get(key string) string {
field := reflect.ValueOf(&t).Elem().FieldByName(key)
if field.IsValid() {
return field.String()
}
return ""
}