Added a CLI tool for uploading that also supports e2e (#280)

This commit is contained in:
Marc Bulling
2025-08-08 16:51:54 +02:00
committed by GitHub
parent 90c686de7c
commit 9a574dbed5
48 changed files with 1353 additions and 449 deletions

2
.gitignore vendored
View File

@@ -14,3 +14,5 @@ internal/webserver/web/static/js/min/wasm_exec.min.js
custom/
.env
*.secret
gokapi-cli
gokapi-cli.json

View File

@@ -21,6 +21,21 @@ for target in $targets; do
rm $output
done
for target in $targets; do
os="$(echo $target | cut -d '/' -f1)"
arch="$(echo $target | cut -d '/' -f2)"
output="build/gokapi-cli-${os}_${arch}"
if [ $os = "windows" ]; then
output+='.exe'
fi
echo "----> Building Gokapi CLI for $target"
GOOS=$os GOARCH=$arch CGO_ENABLED=0 go build -ldflags="-s -w -X 'github.com/forceu/gokapi/internal/environment.Builder=Github Release Builder' -X 'github.com/forceu/gokapi/internal/environment.BuildTime=$(date)'" -o $output github.com/forceu/gokapi/cmd/cli-uploader
zip -j $output.zip $output >/dev/null
rm $output
done
echo "----> Build is complete. List of files at build/:"
cd build/
ls -l gokapi-*

View File

@@ -9,25 +9,27 @@ import (
)
func main() {
err := buildWasmModule("github.com/forceu/gokapi/cmd/wasmdownloader", "../../internal/webserver/web/main.wasm")
output, err := buildWasmModule("github.com/forceu/gokapi/cmd/wasmdownloader", "../../internal/webserver/web/main.wasm")
if err != nil {
fmt.Println("ERROR: Could not compile wasmdownloader")
fmt.Println(err)
fmt.Println(string(output))
os.Exit(2)
}
fmt.Println("Compiled Downloader WASM module")
err = buildWasmModule("github.com/forceu/gokapi/cmd/wasme2e", "../../internal/webserver/web/e2e.wasm")
output, err = buildWasmModule("github.com/forceu/gokapi/cmd/wasme2e", "../../internal/webserver/web/e2e.wasm")
if err != nil {
fmt.Println("ERROR: Could not compile wasme2e")
fmt.Println(err)
fmt.Println(string(output))
os.Exit(3)
}
fmt.Println("Compiled E2E WASM module")
}
func buildWasmModule(src string, dst string) error {
func buildWasmModule(src string, dst string) ([]byte, error) {
cmd := exec.Command("go", "build", "-o", dst, src)
cmd.Env = append(os.Environ(),
"GOOS=js", "GOARCH=wasm")
return cmd.Run()
return cmd.CombinedOutput()
}

View File

@@ -137,6 +137,6 @@ func fileExists(filename string) bool {
// Auto-generated content below, do not modify
// Version codes can be changed in updateVersionNumbers.go
const jsAdminVersion = 11
const jsE2EVersion = 6
const jsAdminVersion = 12
const jsE2EVersion = 7
const cssMainVersion = 5

View File

@@ -11,21 +11,24 @@ import (
"strings"
)
const versionJsAdmin = 11
const versionJsAdmin = 12
const versionJsDropzone = 5
const versionJsE2EAdmin = 6
const versionJsE2EAdmin = 7
const versionCssMain = 5
const fileMain = "../../cmd/gokapi/Main.go"
const fileMinify = "../../build/go-generate/minifyStaticContent.go"
const fileVersionConstants = "../../internal/webserver/web/templates/string_constants.tmpl"
const fileVersionCApi = "../../internal/webserver/api/VersionNumbers.go"
func main() {
checkFileExists(fileMain)
checkFileExists(fileMinify)
checkFileExists(fileVersionConstants)
checkFileExists(fileVersionCApi)
writeVersionTemplates()
writeMinify()
writeApiVersion()
}
func writeVersionTemplates() {
@@ -38,6 +41,16 @@ func writeVersionTemplates() {
}
fmt.Println("Updated version template")
}
func writeApiVersion() {
template := insertVersionNumbers(templateVersionApi)
err := os.WriteFile(fileVersionCApi, []byte(template), 0664)
if err != nil {
fmt.Println("FAIL: Updating version API template")
fmt.Println(err)
os.Exit(1)
}
fmt.Println("Updated API version template")
}
func writeMinify() {
file, err := os.Open(fileMinify)
@@ -97,6 +110,7 @@ func checkFileExists(filename string) {
func insertVersionNumbers(input string) string {
versionGokapi := parseGokapiVersion()
result := strings.ReplaceAll(input, "%gokapiversion%", versionGokapi)
result = strings.ReplaceAll(result, "%gokapiversionInt%", parseVersionAsInt(versionGokapi))
result = strings.ReplaceAll(result, "%jsadmin%", strconv.Itoa(versionJsAdmin))
result = strings.ReplaceAll(result, "%jsdropzone%", strconv.Itoa(versionJsDropzone))
result = strings.ReplaceAll(result, "%jse2e%", strconv.Itoa(versionJsE2EAdmin))
@@ -104,6 +118,26 @@ func insertVersionNumbers(input string) string {
return result
}
func parseVersionAsInt(version string) string {
versionNoAppendix := strings.Split(version, "-")[0]
versionSplit := strings.Split(versionNoAppendix, ".")
versionMajor := versionSplit[0]
versionMinor := versionSplit[1]
versionPatch := versionSplit[2]
if len(versionMinor) < 2 {
versionMinor = "0" + versionMinor
}
if len(versionPatch) < 2 {
versionPatch = "0" + versionPatch
}
versionInt, err := strconv.Atoi(versionMajor + versionMinor + versionPatch)
if err != nil {
fmt.Println("ERROR: Cannot parse version number")
os.Exit(1)
}
return strconv.Itoa(versionInt)
}
func parseGokapiVersion() string {
file, err := os.Open(fileMain)
if err != nil {
@@ -144,3 +178,10 @@ const jsAdminVersion = %jsadmin%
const jsE2EVersion = %jse2e%
const cssMainVersion = %css_main%
`
const templateVersionApi = `// Code generated by updateApiRouting.go - DO NOT EDIT.
package api
const versionReadable = "%gokapiversion%"
const versionInt = %gokapiversionInt%
`

View File

@@ -11,6 +11,7 @@ require (
github.com/jinzhu/copier v0.4.0
github.com/johannesboyne/gofakes3 v0.0.0-20250106100439-5c39aecd6999
github.com/juju/ratelimit v1.0.2
github.com/schollz/progressbar/v3 v3.18.0
github.com/secure-io/sio-go v0.3.1
golang.org/x/crypto v0.35.0
golang.org/x/oauth2 v0.27.0
@@ -27,11 +28,13 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
github.com/tdewolff/minify/v2 v2.20.34 // indirect
github.com/tdewolff/parse/v2 v2.7.15 // indirect
github.com/tdewolff/minify/v2 v2.23.11 // indirect
github.com/tdewolff/parse/v2 v2.8.2-0.20250806174018-50048bb39781 // indirect
github.com/yuin/gopher-lua v1.1.1 // indirect
go.shabbyrobe.org/gocovmerge v0.0.0-20230507111327-fa4f82cfbf4d // indirect
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect
@@ -40,8 +43,6 @@ require (
modernc.org/libc v1.61.13 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.8.2 // indirect
modernc.org/strutil v1.2.1 // indirect
modernc.org/token v1.1.0 // indirect
)
require (

View File

@@ -1,48 +1,44 @@
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.33.0/go.mod h1:MhP4a3EU7aENRi9aO+tHfTBZicLqQevyi/DJpoj6mi0=
github.com/alicebob/miniredis/v2 v2.34.0/go.mod h1:kWShP4b58T1CW0Y5dViCd5ztzrDqRWqM3nksiyXk5s8=
github.com/aws/aws-sdk-go v1.44.256/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc=
github.com/cevatbarisyilmaz/ara v0.0.4/go.mod h1:BfFOxnUd6Mj6xmcvRxHN3Sr21Z1T3U2MYkYOmoQe4Ts=
github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/johannesboyne/gofakes3 v0.0.0-20241026070602-0da3aa9c32ca/go.mod h1:t6osVdP++3g4v2awHz4+HFccij23BbdT1rX3W7IijqQ=
github.com/johannesboyne/gofakes3 v0.0.0-20250106100439-5c39aecd6999/go.mod h1:t6osVdP++3g4v2awHz4+HFccij23BbdT1rX3W7IijqQ=
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
github.com/secure-io/sio-go v0.3.1/go.mod h1:+xbkjDzPjwh4Axd07pRKSNriS9SCiYksWnZqdnfpQxs=
github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/tdewolff/minify/v2 v2.20.34 h1:XueI6sQtgS7du45fyBCNkNfPQ9SINaYavMFNOxp37SA=
github.com/tdewolff/minify/v2 v2.20.34/go.mod h1:L1VYef/jwKw6Wwyk5A+T0mBjjn3mMPgmjjA688RNsxU=
github.com/tdewolff/parse/v2 v2.7.15 h1:hysDXtdGZIRF5UZXwpfn3ZWRbm+ru4l53/ajBRGpCTw=
github.com/tdewolff/parse/v2 v2.7.15/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/tdewolff/minify/v2 v2.23.11 h1:cZqTVCtuVvPC8/GbCvYgIcdAQGmoxEObZzKeKIUixTE=
github.com/tdewolff/minify/v2 v2.23.11/go.mod h1:vmkbfGQ5hp/eYB+TswNWKma67S0a+32HBL+mFWxjZ2Q=
github.com/tdewolff/parse/v2 v2.8.2-0.20250806174018-50048bb39781 h1:2qicgFovKg1XtX7Wf6GwexUdpb7q/jMIE2IgkYsVAvE=
github.com/tdewolff/parse/v2 v2.8.2-0.20250806174018-50048bb39781/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo=
github.com/tdewolff/test v1.0.11 h1:FdLbwQVHxqG16SlkGveC0JVyrJN62COWTRyUFzfbtBE=
github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
@@ -50,10 +46,8 @@ go.shabbyrobe.org/gocovmerge v0.0.0-20230507111327-fa4f82cfbf4d/go.mod h1:92Uoe3
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU=
golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4=
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -65,12 +59,10 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -84,14 +76,12 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -99,15 +89,12 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190829051458-42f498d34c4d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -116,15 +103,7 @@ gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3M
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.61.4/go.mod h1:VfXVuM/Shh5XsMNrh3C6OkfL78G3loa4ZC/Ljv9k7xc=
modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=
modernc.org/sqlite v1.34.2/go.mod h1:dnR723UrTtjKpoHCAMN0Q/gZ9MT4r+iRvIBb9umWFkU=
modernc.org/sqlite v1.35.0/go.mod h1:9cr2sicr7jIaWTBKQmAxQLfBv9LL0su4ZTEV+utt3ic=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

54
cmd/cli-uploader/Main.go Normal file
View File

@@ -0,0 +1,54 @@
package main
import (
"encoding/json"
"fmt"
"github.com/forceu/gokapi/cmd/cli-uploader/cliapi"
"github.com/forceu/gokapi/cmd/cli-uploader/cliconfig"
"github.com/forceu/gokapi/cmd/cli-uploader/cliflags"
"os"
)
func main() {
mode := cliflags.Parse()
switch mode {
case cliflags.ModeLogin:
cliconfig.CreateLogin()
case cliflags.ModeLogout:
doLogout()
case cliflags.ModeUpload:
processUpload()
case cliflags.ModeInvalid:
os.Exit(3)
}
}
func processUpload() {
cliconfig.Load()
uploadParam := cliflags.GetUploadParameters()
result, err := cliapi.UploadFile(uploadParam)
if err != nil {
fmt.Println("ERROR: Could not upload file")
fmt.Println(err)
os.Exit(1)
}
if uploadParam.JsonOutput {
jsonStr, _ := json.Marshal(result)
fmt.Println(string(jsonStr))
} else {
fmt.Println("File uploaded successfully")
fmt.Println("File ID: " + result.Id)
fmt.Println("File Download URL: " + result.UrlDownload)
}
}
func doLogout() {
err := cliconfig.Delete()
if err != nil {
fmt.Println("ERROR: Could not delete configuration file")
fmt.Println(err)
os.Exit(2)
}
fmt.Println("Logged out. To login again, run: gokapi-cli login")
}

View File

@@ -0,0 +1,378 @@
package cliapi
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/forceu/gokapi/cmd/cli-uploader/cliflags"
"github.com/forceu/gokapi/internal/encryption"
"github.com/forceu/gokapi/internal/encryption/end2end"
"github.com/forceu/gokapi/internal/helper"
"github.com/forceu/gokapi/internal/models"
"github.com/schollz/progressbar/v3"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
var gokapiUrl string
var apiKey string
var e2eKey []byte
const megaByte = 1024 * 1024
type header struct {
Key string
Value string
}
var EUnauthorised = errors.New("unauthorised")
var EFileTooBig = errors.New("file too big")
var EE2eKeyIncorrect = errors.New("e2e key incorrect")
func Init(url, key string, end2endKey []byte) {
gokapiUrl = strings.TrimSuffix(url, "/") + "/api"
apiKey = key
e2eKey = end2endKey
}
func GetVersion() (string, int, error) {
result, err := getUrl(gokapiUrl+"/info/version", []header{}, false)
if err != nil {
return "", 0, err
}
type expectedFormat struct {
Version string
VersionInt int
}
var parsedResult expectedFormat
err = json.Unmarshal([]byte(result), &parsedResult)
if err != nil {
return "", 0, err
}
return parsedResult.Version, parsedResult.VersionInt, nil
}
func GetConfig() (int, int, bool, error) {
result, err := getUrl(gokapiUrl+"/info/config", []header{}, false)
if err != nil {
return 0, 0, false, err
}
type expectedFormat struct {
MaxFilesize int
MaxChunksize int
EndToEndEncryptionEnabled bool
}
var parsedResult expectedFormat
err = json.Unmarshal([]byte(result), &parsedResult)
if err != nil {
return 0, 0, false, err
}
return parsedResult.MaxFilesize, parsedResult.MaxChunksize, parsedResult.EndToEndEncryptionEnabled, nil
}
func getUrl(url string, headers []header, longTimeout bool) (string, error) {
timeout := 30 * time.Second
if longTimeout {
timeout = 30 * time.Minute
}
client := &http.Client{
Timeout: timeout,
}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
req.Header.Add("apikey", apiKey)
for _, addHeader := range headers {
req.Header.Add(addHeader.Key, addHeader.Value)
}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode == 401 {
return "", EUnauthorised
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(body), nil
}
func UploadFile(uploadParams cliflags.UploadConfig) (models.FileApiOutput, error) {
var progressBar *progressbar.ProgressBar
file, err := os.OpenFile(uploadParams.File, os.O_RDONLY, 0664)
if err != nil {
fmt.Println("ERROR: Could not open file to upload")
fmt.Println(err)
os.Exit(4)
}
maxSize, chunkSize, isE2e, err := GetConfig()
if err != nil {
return models.FileApiOutput{}, err
}
// TODO check for 401
if len(e2eKey) == 0 || !isE2e || uploadParams.DisableE2e {
isE2e = false
}
fileStat, err := file.Stat()
if err != nil {
return models.FileApiOutput{}, err
}
sizeBytes := fileStat.Size()
realSize := fileStat.Size()
if isE2e {
sizeBytes = encryption.CalculateEncryptedFilesize(sizeBytes)
}
if sizeBytes > int64(maxSize)*megaByte {
return models.FileApiOutput{}, EFileTooBig
}
uuid := helper.GenerateRandomString(30)
if !uploadParams.JsonOutput {
progressBar = progressbar.DefaultBytes(-1, "uploading")
}
if isE2e {
cipher, err := encryption.GetRandomCipher()
if err != nil {
return models.FileApiOutput{}, err
}
stream, err := encryption.GetEncryptReader(cipher, file)
if err != nil {
return models.FileApiOutput{}, err
}
for i := int64(0); i < sizeBytes; i = i + (int64(chunkSize) * megaByte) {
err = uploadChunk(stream, uuid, i, int64(chunkSize)*megaByte, sizeBytes, progressBar)
if err != nil {
return models.FileApiOutput{}, err
}
}
metaData, err := completeChunk(uuid, "Encrypted File", sizeBytes, realSize, true, uploadParams, progressBar)
if err != nil {
return models.FileApiOutput{}, err
}
e2eFile := models.E2EFile{
Uuid: uuid,
Id: metaData.Id,
Filename: getFileName(file),
Cipher: cipher,
}
err = addE2EFileInfo(e2eFile)
if err != nil {
return models.FileApiOutput{}, err
}
hashContent, err := getHashContent(e2eFile)
metaData.UrlDownload = metaData.UrlDownload + "#" + hashContent
metaData.Name = getFileName(file)
return metaData, err
}
for i := int64(0); i < sizeBytes; i = i + (int64(chunkSize) * megaByte) {
err = uploadChunk(file, uuid, i, int64(chunkSize)*megaByte, sizeBytes, progressBar)
if err != nil {
return models.FileApiOutput{}, err
}
}
metaData, err := completeChunk(uuid, nameToBase64(file), sizeBytes, realSize, false, uploadParams, progressBar)
if err != nil {
return models.FileApiOutput{}, err
}
return metaData, nil
}
func nameToBase64(f *os.File) string {
return "base64:" + base64.StdEncoding.EncodeToString([]byte(getFileName(f)))
}
func getFileName(f *os.File) string {
return filepath.Base(f.Name())
}
func uploadChunk(f io.Reader, uuid string, offset, chunkSize, filesize int64, progressBar *progressbar.ProgressBar) error {
body := new(bytes.Buffer)
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", "uploadedfile")
if err != nil {
return err
}
buffer, err := io.ReadAll(io.LimitReader(f, chunkSize))
if err != nil {
return err
}
_, err = part.Write(buffer)
if err != nil {
return err
}
err = writer.WriteField("filesize", strconv.FormatInt(filesize, 10))
if err != nil {
return err
}
err = writer.WriteField("offset", strconv.FormatInt(offset, 10))
if err != nil {
return err
}
err = writer.WriteField("uuid", uuid)
if err != nil {
return err
}
err = writer.Close()
if err != nil {
return err
}
var bodyReader io.Reader
if progressBar != nil {
bodyReader = io.TeeReader(body, progressBar)
} else {
bodyReader = body
}
r, err := http.NewRequest("POST", gokapiUrl+"/chunk/add", bodyReader)
if err != nil {
return err
}
r.Header.Set("Content-Type", writer.FormDataContentType())
r.Header.Set("apikey", apiKey)
client := &http.Client{}
resp, err := client.Do(r)
if err != nil {
return err
}
bodyContent, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
response := string(bodyContent)
if resp.StatusCode != http.StatusOK {
return errors.New("failed to upload chunk: status code " + strconv.Itoa(resp.StatusCode) + ", response: " + response)
}
if response != "{\"result\":\"OK\"}" {
return errors.New("failed to upload chunk: unexpected response: " + response)
}
return nil
}
func completeChunk(uid, filename string, filesize, realsize int64, useE2e bool, uploadParams cliflags.UploadConfig, progressBar *progressbar.ProgressBar) (models.FileApiOutput, error) {
type expectedFormat struct {
FileInfo models.FileApiOutput `json:"FileInfo"`
}
if progressBar != nil {
_ = progressBar.Finish()
}
if !uploadParams.JsonOutput {
fmt.Println("Finalising...")
}
result, err := getUrl(gokapiUrl+"/chunk/complete", []header{
{"uuid", uid},
{"filename", filename},
{"filesize", strconv.FormatInt(filesize, 10)},
{"realsize", strconv.FormatInt(realsize, 10)},
{"isE2E", strconv.FormatBool(useE2e)},
{"allowedDownloads", strconv.Itoa(uploadParams.ExpiryDownloads)},
{"expiryDays", strconv.Itoa(uploadParams.ExpiryDays)},
{"password", uploadParams.Password},
{"contenttype", "application/octet-stream"},
}, true)
if err != nil {
return models.FileApiOutput{}, err
}
var parsedResult expectedFormat
err = json.Unmarshal([]byte(result), &parsedResult)
if err != nil {
return models.FileApiOutput{}, err
}
return parsedResult.FileInfo, nil
}
func GetE2eInfo() (models.E2EInfoPlainText, error) {
var result models.E2EInfoEncrypted
var fileInfo models.E2EInfoPlainText
resultJson, err := getUrl(gokapiUrl+"/e2e/get", []header{}, false)
if err != nil {
return models.E2EInfoPlainText{}, err
}
err = json.Unmarshal([]byte(resultJson), &result)
if err != nil {
return models.E2EInfoPlainText{}, err
}
fileInfo, err = end2end.DecryptData(result, e2eKey)
if err != nil {
return models.E2EInfoPlainText{}, EE2eKeyIncorrect
}
return fileInfo, nil
}
func addE2EFileInfo(file models.E2EFile) error {
infoPlain, err := GetE2eInfo()
if err != nil {
return err
}
infoPlain.Files = append(infoPlain.Files, file)
output, err := end2end.EncryptData(infoPlain.Files, e2eKey)
if err != nil {
return err
}
return setE2eInfo(output)
}
func setE2eInfo(input models.E2EInfoEncrypted) error {
outputJson, err := json.Marshal(input)
if err != nil {
return err
}
content := base64.StdEncoding.EncodeToString(outputJson)
apiURL := gokapiUrl + "/e2e/set"
bodyData := map[string]string{
"content": content,
}
bodyBytes, err := json.Marshal(bodyData)
if err != nil {
return fmt.Errorf("failed to marshal JSON: %w", err)
}
req, err := http.NewRequest(http.MethodPost, apiURL, bytes.NewBuffer(bodyBytes))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("apikey", apiKey)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("failed to send request: %w", err)
}
_ = resp.Body.Close()
return nil
}
func getHashContent(input models.E2EFile) (string, error) {
output, err := json.Marshal(models.E2EHashContent{
Filename: input.Filename,
Cipher: base64.StdEncoding.EncodeToString(input.Cipher),
})
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(output), nil
}

View File

@@ -0,0 +1,148 @@
package cliconfig
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/forceu/gokapi/cmd/cli-uploader/cliapi"
"github.com/forceu/gokapi/cmd/cli-uploader/cliflags"
"github.com/forceu/gokapi/internal/helper"
"os"
"strings"
)
const minGokapiVersionInt = 20100
const minGokapiVersionStr = "2.1.0"
type configFile struct {
Url string `json:"Url"`
Apikey string `json:"Apikey"`
E2ekey []byte `json:"E2Ekey"`
}
func CreateLogin() {
fmt.Print("Gokapi URL: ")
url := helper.ReadLine()
if (!strings.HasPrefix(url, "http://")) && (!strings.HasPrefix(url, "https://")) {
fmt.Println("ERROR: URL must start with http:// or https://")
os.Exit(1)
}
url = strings.TrimSuffix(url, "/admin")
if strings.HasPrefix(url, "http://") {
fmt.Println("WARNING: This URL uses an insecure connection. All data, including your API key, will be sent in plain text. This is not recommended for production use.")
}
fmt.Print("API key: ")
apikey := helper.ReadLine()
if len(apikey) < 3 {
fmt.Println("ERROR: Invalid API key")
os.Exit(1)
}
fmt.Println("")
fmt.Print("Testing connection...")
cliapi.Init(url, apikey, []byte{})
vstr, vint, err := cliapi.GetVersion()
if err != nil {
fmt.Println()
if errors.Is(cliapi.EUnauthorised, err) {
fmt.Println("ERROR: Unauthorised API key")
} else {
fmt.Println(err)
}
os.Exit(1)
}
if vint < minGokapiVersionInt {
fmt.Println("\nERROR: Gokapi version must be at least " + minGokapiVersionStr)
fmt.Println("Your version is " + vstr)
os.Exit(1)
}
fmt.Print("OK\nDownloading configuration...")
_, _, isE2E, err := cliapi.GetConfig()
if err != nil {
fmt.Println("FAIL")
if errors.Is(cliapi.EUnauthorised, err) {
fmt.Println("ERROR: API key does not have the permission to upload new files.")
} else {
fmt.Println(err)
}
os.Exit(1)
}
fmt.Println("OK")
var e2ekey []byte
if isE2E {
fmt.Print("End-to-end encryption key: ")
e2ekeyString := helper.ReadLine()
e2ekey, err = base64.StdEncoding.DecodeString(e2ekeyString)
if err != nil {
fmt.Println("ERROR: Invalid end-to-end encryption key")
os.Exit(1)
}
cliapi.Init(url, apikey, e2ekey)
_, err = cliapi.GetE2eInfo()
if err != nil {
if errors.Is(cliapi.EE2eKeyIncorrect, err) {
fmt.Println("ERROR: Incorrect end-to-end encryption key")
} else {
fmt.Println(err)
}
os.Exit(1)
}
// TODO check if key has not been generated yet
// TODO warn user not to upload e2e simultaneously
}
err = save(url, apikey, e2ekey)
if err != nil {
fmt.Println("ERROR: Could not save login information")
fmt.Println(err)
os.Exit(1)
}
fmt.Println("Login successful")
}
func save(url, apikey string, e2ekey []byte) error {
configData := configFile{
Url: url,
Apikey: apikey,
E2ekey: e2ekey,
}
jsonData, err := json.Marshal(configData)
if err != nil {
return err
}
return os.WriteFile(cliflags.GetConfigLocation(), jsonData, 0600)
}
func Load() {
if !helper.FileExists(cliflags.GetConfigLocation()) {
fmt.Println("ERROR: No login information found")
fmt.Println("Please run 'gokapi-cli login' to create a login")
os.Exit(1)
}
data, err := os.ReadFile(cliflags.GetConfigLocation())
if err != nil {
fmt.Println("ERROR: Could not read login information")
os.Exit(1)
}
var config configFile
err = json.Unmarshal(data, &config)
if err != nil {
fmt.Println("ERROR: Could not read login information")
os.Exit(1)
}
cliapi.Init(config.Url, config.Apikey, config.E2ekey)
}
func Delete() error {
if !helper.FileExists(cliflags.GetConfigLocation()) {
return nil
}
return os.Remove(cliflags.GetConfigLocation())
}

View File

@@ -0,0 +1,120 @@
package cliflags
import (
"fmt"
"os"
"strconv"
)
const (
ModeLogin = iota
ModeLogout
ModeUpload
ModeInvalid
)
type UploadConfig struct {
File string
JsonOutput bool
DisableE2e bool
ExpiryDays int
ExpiryDownloads int
Password string
}
func Parse() int {
if len(os.Args) < 2 {
printUsage()
return ModeInvalid
}
switch os.Args[1] {
case "login":
return ModeLogin
case "logout":
return ModeLogout
case "upload":
return ModeUpload
default:
printUsage()
return ModeInvalid
}
}
func GetUploadParameters() UploadConfig {
result := UploadConfig{}
for i := 2; i < len(os.Args); i++ {
switch os.Args[i] {
case "--json":
result.JsonOutput = true
case "--disable-e2e":
result.DisableE2e = true
case "-f":
result.File = getParameter(&i)
case "--expiry-days":
result.ExpiryDays = requireInt(getParameter(&i))
case "--expiry-downloads":
result.ExpiryDownloads = requireInt(getParameter(&i))
case "--password":
result.Password = getParameter(&i)
}
}
if result.File == "" {
fmt.Println("ERROR: Missing parameter -f")
os.Exit(2)
}
if result.ExpiryDownloads < 0 {
result.ExpiryDownloads = 0
}
if result.ExpiryDays < 0 {
result.ExpiryDays = 0
}
return result
}
func GetConfigLocation() string {
for i := 2; i < len(os.Args); i++ {
switch os.Args[i] {
case "-c":
return getParameter(&i)
}
}
return "gokapi-cli.json"
}
func getParameter(position *int) string {
newPosition := *position + 1
position = &newPosition
if newPosition >= len(os.Args) {
printUsage()
os.Exit(3)
}
return os.Args[newPosition]
}
func requireInt(input string) int {
result, err := strconv.Atoi(input)
if err != nil {
fmt.Println("ERROR: " + input + " is not a valid integer")
os.Exit(2)
}
return result
}
func printUsage() {
fmt.Println("Gokapi CLI v1.0")
fmt.Println()
fmt.Println("Valid options are:")
fmt.Println(" gokapi-cli login [-c /path/to/config]")
fmt.Println(" gokapi-cli logout [-c /path/to/config]")
fmt.Println(" gokapi-cli upload --f /file/to/upload [--json] [--disable-e2e]\n" +
" [--expiry-days INT] [--expiry-downloads INT]\n" +
" [--password STRING] [-c /path/to/config]")
fmt.Println()
fmt.Println("gokapi-cli upload:")
fmt.Println("--json Outputs the result as JSON only")
fmt.Println("--disable-e2e Disables end-to-end encryption")
fmt.Println("--expiry-days Sets the expiry date of the file in days, otherwise unlimited")
fmt.Println("--expiry-downloads Sets the allowed downloads, otherwise unlimited")
fmt.Println("--password Sets a password")
os.Exit(3)
}

View File

@@ -1,4 +1,4 @@
//go:build go1.22
//go:build go1.24
package main
@@ -34,7 +34,7 @@ import (
// versionGokapi is the current version in readable form.
// Other version numbers can be modified in /build/go-generate/updateVersionNumbers.go
const versionGokapi = "2.0.1"
const versionGokapi = "2.1.0-dev"
// The following calls update the version numbers, update documentation, minify Js/CSS and build the WASM modules
//go:generate go run "../../build/go-generate/updateVersionNumbers.go"

View File

@@ -1,4 +1,4 @@
//go:build js && wasm
//go:build js && wasm && go1.24
package main
@@ -204,6 +204,7 @@ func InfoParse(this js.Value, args []js.Value) interface{} {
}
func DecryptMenu(this js.Value, args []js.Value) interface{} {
updates := js.Global().Get("Object").New()
for _, file := range fileInfo.Files {
cipher := base64.StdEncoding.EncodeToString(file.Cipher)
hashContent, err := json.Marshal(models.E2EHashContent{
@@ -214,12 +215,18 @@ func DecryptMenu(this js.Value, args []js.Value) interface{} {
return jsError(err.Error())
}
hashBase64 := base64.StdEncoding.EncodeToString(hashContent)
js.Global().Call("decryptFileEntry", file.Id, file.Filename, hashBase64)
entry := js.Global().Get("Array").New()
entry.Call("push", file.Filename)
entry.Call("push", hashBase64)
updates.Set(file.Id, entry)
}
js.Global().Call("decryptFileEntries", updates)
return nil
}
func removeExpiredFiles(encInfo models.E2EInfoEncrypted) []models.E2EFile {
fileMutex.Lock()
defer fileMutex.Unlock()
cleanedFiles := make([]models.E2EFile, 0)
for _, id := range encInfo.AvailableFiles {
for _, file := range fileInfo.Files {

View File

@@ -201,6 +201,54 @@ Migrating Redis (``127.0.0.1:6379, User: test, Password: 1234, Prefix: gokapi_,
gokapi --migrate "redis://test:1234@127.0.0.1:6379?prefix=gokapi_&ssl=true" sqlite://./data/gokapi.sqlite
.. _clitool:
********************************
CLI Tool
********************************
Gokapi also has a CLI tool that allows uploads from the command line. Binaries are avaible on the release page (``gokapi-cli``) for Linux, Windows and MacOs. To compile it yourself, download the repository and run ``make build-cli`` in the top directory.
Login
=================================
First you need to login with the command ``gokapi-cli login``. You will then be asked for your server URL and a valid API key with upload permission. If end-to-end encryption is enabled, you will also need to enter your encyption key. By default the login data is saved to ``gokapi-cli.json``, but you can define a different location with the ``-c`` parameter.
To logout, either delete the configuration file or run ``gokapi-cli logout``.
.. warning::
The configuration file contains the login data as plain text.
Upload
=================================
To upload a file, simply run ``gokapi-cli upload -f /path/to/file``. By default the files are encrypted (if enabled) and stored without any expiration. These additional parameters are available:
+-----------------------------+---------------------------------------------------+
| --json | Only outputs in JSON format, unless upload failed |
+-----------------------------+---------------------------------------------------+
| --disable-e2e | Disables end-to-end encryption for this upload |
+-----------------------------+---------------------------------------------------+
| --expiry-days [number] | Sets the expiry date of the file in days |
+-----------------------------+---------------------------------------------------+
| --expiry-downloads [number] | Sets the allowed downloads |
+-----------------------------+---------------------------------------------------+
| --password [string] | Sets a password |
+-----------------------------+---------------------------------------------------+
| -c [path] | Use the configuration file specified |
+-----------------------------+---------------------------------------------------+
.. warning::
If you are using end-to-end encryption, do not upload other encrypted files simultaneously to avoid race conditions.
.. _api:

View File

@@ -236,7 +236,6 @@ This option disables Gokapis internal authentication completely, except for API
- ``/admin``
- ``/apiKeys``
- ``/changePassword``
- ``/e2eInfo``
- ``/e2eSetup``
- ``/logs``
- ``/uploadChunk``

9
go.mod
View File

@@ -11,6 +11,7 @@ require (
github.com/jinzhu/copier v0.4.0
github.com/johannesboyne/gofakes3 v0.0.0-20250106100439-5c39aecd6999
github.com/juju/ratelimit v1.0.2
github.com/schollz/progressbar/v3 v3.18.0
github.com/secure-io/sio-go v0.3.1
golang.org/x/crypto v0.35.0
golang.org/x/oauth2 v0.27.0
@@ -27,11 +28,13 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
github.com/tdewolff/minify/v2 v2.20.34 // indirect
github.com/tdewolff/parse/v2 v2.7.15 // indirect
github.com/tdewolff/minify/v2 v2.23.11 // indirect
github.com/tdewolff/parse/v2 v2.8.2-0.20250806174018-50048bb39781 // indirect
github.com/yuin/gopher-lua v1.1.1 // indirect
go.shabbyrobe.org/gocovmerge v0.0.0-20230507111327-fa4f82cfbf4d // indirect
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect
@@ -40,8 +43,6 @@ require (
modernc.org/libc v1.61.13 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.8.2 // indirect
modernc.org/strutil v1.2.1 // indirect
modernc.org/token v1.1.0 // indirect
)
require (

124
go.sum
View File

@@ -1,27 +1,18 @@
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.33.0 h1:uvTF0EDeu9RLnUEG27Db5I68ESoIxTiXbNUiji6lZrA=
github.com/alicebob/miniredis/v2 v2.33.0/go.mod h1:MhP4a3EU7aENRi9aO+tHfTBZicLqQevyi/DJpoj6mi0=
github.com/alicebob/miniredis/v2 v2.34.0 h1:mBFWMaJSNL9RwdGRyEDoAAv8OQc5UlEhLDQggTglU/0=
github.com/alicebob/miniredis/v2 v2.34.0/go.mod h1:kWShP4b58T1CW0Y5dViCd5ztzrDqRWqM3nksiyXk5s8=
github.com/aws/aws-sdk-go v1.44.256/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.54.11 h1:Zxuv/R+IVS0B66yz4uezhxH9FN9/G2nbxejYqAMFjxk=
github.com/aws/aws-sdk-go v1.54.11/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk=
github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II=
github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc=
github.com/cevatbarisyilmaz/ara v0.0.4 h1:SGH10hXpBJhhTlObuZzTuFn1rrdmjQImITXnZVPSodc=
github.com/cevatbarisyilmaz/ara v0.0.4/go.mod h1:BfFOxnUd6Mj6xmcvRxHN3Sr21Z1T3U2MYkYOmoQe4Ts=
github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU=
github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac=
github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo=
github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -29,10 +20,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/gomodule/redigo v1.9.2 h1:HrutZBLhSIU8abiSfW8pj8mPhOyMYjZT/wcA4/L9L9s=
@@ -43,18 +30,12 @@ github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlG
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/johannesboyne/gofakes3 v0.0.0-20240513200200-99de01ee122d h1:9dIJ/sx3yapvuq3kvTSVQ6UVS2HxfOB4MCwWiH8JcvQ=
github.com/johannesboyne/gofakes3 v0.0.0-20240513200200-99de01ee122d/go.mod h1:AxgWC4DDX54O2WDoQO1Ceabtn6IbktjU/7bigor+66g=
github.com/johannesboyne/gofakes3 v0.0.0-20241026070602-0da3aa9c32ca h1:aLV7i5W7KKNHUwcmPZKDKXut6ZnJ8sdQWYDTKwhIzBU=
github.com/johannesboyne/gofakes3 v0.0.0-20241026070602-0da3aa9c32ca/go.mod h1:t6osVdP++3g4v2awHz4+HFccij23BbdT1rX3W7IijqQ=
github.com/johannesboyne/gofakes3 v0.0.0-20250106100439-5c39aecd6999 h1:CMbkEl1h9JvRURFFprSbyy2f4Gf71SFz9h74iSAETGo=
github.com/johannesboyne/gofakes3 v0.0.0-20250106100439-5c39aecd6999/go.mod h1:t6osVdP++3g4v2awHz4+HFccij23BbdT1rX3W7IijqQ=
github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
@@ -66,6 +47,10 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -73,24 +58,25 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
github.com/secure-io/sio-go v0.3.1 h1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc=
github.com/secure-io/sio-go v0.3.1/go.mod h1:+xbkjDzPjwh4Axd07pRKSNriS9SCiYksWnZqdnfpQxs=
github.com/shabbyrobe/gocovmerge v0.0.0-20190829150210-3e036491d500/go.mod h1:+njLrG5wSeoG4Ds61rFgEzKvenR2UHbjMoDHsczxly0=
github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df h1:S77Pf5fIGMa7oSwp8SQPp7Hb4ZiI38K3RNBKD2LLeEM=
github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df/go.mod h1:dcuzJZ83w/SqN9k4eQqwKYMgmKWzg/KzJAURBhRL1tc=
github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tdewolff/minify/v2 v2.20.34 h1:XueI6sQtgS7du45fyBCNkNfPQ9SINaYavMFNOxp37SA=
github.com/tdewolff/minify/v2 v2.20.34/go.mod h1:L1VYef/jwKw6Wwyk5A+T0mBjjn3mMPgmjjA688RNsxU=
github.com/tdewolff/parse/v2 v2.7.15 h1:hysDXtdGZIRF5UZXwpfn3ZWRbm+ru4l53/ajBRGpCTw=
github.com/tdewolff/parse/v2 v2.7.15/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tdewolff/minify/v2 v2.23.11 h1:cZqTVCtuVvPC8/GbCvYgIcdAQGmoxEObZzKeKIUixTE=
github.com/tdewolff/minify/v2 v2.23.11/go.mod h1:vmkbfGQ5hp/eYB+TswNWKma67S0a+32HBL+mFWxjZ2Q=
github.com/tdewolff/parse/v2 v2.8.2-0.20250806174018-50048bb39781 h1:2qicgFovKg1XtX7Wf6GwexUdpb7q/jMIE2IgkYsVAvE=
github.com/tdewolff/parse/v2 v2.8.2-0.20250806174018-50048bb39781/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo=
github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
@@ -100,27 +86,15 @@ go.shabbyrobe.org/gocovmerge v0.0.0-20230507111327-fa4f82cfbf4d/go.mod h1:92Uoe3
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w=
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM=
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU=
golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4=
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@@ -128,19 +102,11 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -155,10 +121,6 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -166,10 +128,6 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -184,10 +142,6 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -200,52 +154,26 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.21.3 h1:2mhBdWKtivdFlLR1ecKXTljPG1mfvbByX7QKztAIJl8=
modernc.org/cc/v4 v4.21.3/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.18.2 h1:PUQPShG4HwghpOekNujL0sFavdkRvmxzTbI4rGJ5mg0=
modernc.org/ccgo/v4 v4.18.2/go.mod h1:ao1fAxf9a2KEOL15WY8+yP3wnpaOpP/QuyFOZ9HJolM=
modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=
modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo=
modernc.org/ccgo/v4 v4.23.16/go.mod h1:nNma8goMTY7aQZQNTyN9AIoJfxav4nvTnvKThAeMDdo=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M=
modernc.org/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw=
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8=
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 h1:IYXPPTTjjoSHvUClZIYexDiO7g+4x+XveKT4gCIAwiY=
modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/gc/v3 v3.0.0-20250225134559-fd9931328834 h1:Qv+IG+6zQZSvUPRwUOgnMoAhy6rAhBB7WYY8IAfKG+c=
modernc.org/gc/v3 v3.0.0-20250225134559-fd9931328834/go.mod h1:LG5UO1Ran4OO0JRKz2oNiXhR5nNrgz0PzH7UKhz0aMU=
modernc.org/libc v1.53.4 h1:YAgFS7tGIFBfqje2UOqiXtIwuDUCF8AUonYw0seup34=
modernc.org/libc v1.53.4/go.mod h1:aGsLofnkcct8lTJnKQnCqJO37ERAXSHamSuWLFoF2Cw=
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
modernc.org/libc v1.61.4 h1:wVyqEx6tlltte9lPTjq0kDAdtdM9c4JH8rU6M1ZVawA=
modernc.org/libc v1.61.4/go.mod h1:VfXVuM/Shh5XsMNrh3C6OkfL78G3loa4ZC/Ljv9k7xc=
modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8=
modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=
modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sqlite v1.30.1 h1:YFhPVfu2iIgUf9kuA1CR7iiHdcEEsI2i+yjRYHscyxk=
modernc.org/sqlite v1.30.1/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU=
modernc.org/sqlite v1.34.2 h1:J9n76TPsfYYkFkZ9Uy1QphILYifiVEwwOT7yP5b++2Y=
modernc.org/sqlite v1.34.2/go.mod h1:dnR723UrTtjKpoHCAMN0Q/gZ9MT4r+iRvIBb9umWFkU=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.35.0 h1:yQps4fegMnZFdphtzlfQTCNBWtS0CZv48pRpW3RFHRw=
modernc.org/sqlite v1.35.0/go.mod h1:9cr2sicr7jIaWTBKQmAxQLfBv9LL0su4ZTEV+utt3ic=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

View File

@@ -152,9 +152,7 @@ func SaveEnd2EndInfo(info models.E2EInfoEncrypted, userId int) {
// GetEnd2EndInfo retrieves the encrypted e2e info
func GetEnd2EndInfo(userId int) models.E2EInfoEncrypted {
info := db.GetEnd2EndInfo(userId)
info.AvailableFiles = GetAllMetaDataIds()
return info
return db.GetEnd2EndInfo(userId)
}
// DeleteEnd2EndInfo resets the encrypted e2e info
@@ -191,11 +189,6 @@ func GetAllMetadata() map[string]models.File {
return db.GetAllMetadata()
}
// GetAllMetaDataIds returns all Ids that contain metadata
func GetAllMetaDataIds() []string {
return db.GetAllMetaDataIds()
}
// GetMetaDataById returns a models.File from the ID passed or false if the id is not valid
func GetMetaDataById(id string) (models.File, bool) {
return db.GetMetaDataById(id)

View File

@@ -128,10 +128,10 @@ func TestE2E(t *testing.T) {
AvailableFiles: []string{"should", "not", "be", "saved"},
}
runAllTypesNoOutput(t, func() { SaveEnd2EndInfo(input, 3) })
input.AvailableFiles = []string{}
input.AvailableFiles = nil
runAllTypesCompareOutput(t, func() any { return GetEnd2EndInfo(3) }, input)
runAllTypesNoOutput(t, func() { DeleteEnd2EndInfo(3) })
runAllTypesCompareOutput(t, func() any { return GetEnd2EndInfo(3) }, models.E2EInfoEncrypted{AvailableFiles: []string{}})
runAllTypesCompareOutput(t, func() any { return GetEnd2EndInfo(3) }, models.E2EInfoEncrypted{})
}
func TestSessions(t *testing.T) {
@@ -208,7 +208,6 @@ func TestHotlinks(t *testing.T) {
}
func TestMetaData(t *testing.T) {
runAllTypesCompareOutput(t, func() any { return GetAllMetaDataIds() }, []string{})
runAllTypesCompareOutput(t, func() any { return GetAllMetadata() }, map[string]models.File{})
runAllTypesCompareTwoOutputs(t, func() (any, any) { return GetMetaDataById("testid") }, models.File{}, false)
file := models.File{
@@ -238,10 +237,8 @@ func TestMetaData(t *testing.T) {
}
runAllTypesNoOutput(t, func() { SaveMetaData(file) })
runAllTypesCompareTwoOutputs(t, func() (any, any) { return GetMetaDataById("testid") }, file, true)
runAllTypesCompareOutput(t, func() any { return GetAllMetaDataIds() }, []string{"testid"})
runAllTypesCompareOutput(t, func() any { return GetAllMetadata() }, map[string]models.File{"testid": file})
runAllTypesNoOutput(t, func() { DeleteMetaData("testid") })
runAllTypesCompareOutput(t, func() any { return GetAllMetaDataIds() }, []string{})
runAllTypesCompareOutput(t, func() any { return GetAllMetadata() }, map[string]models.File{})
runAllTypesCompareTwoOutputs(t, func() (any, any) { return GetMetaDataById("testid") }, models.File{}, false)

View File

@@ -66,8 +66,6 @@ type Database interface {
// GetAllMetadata returns a map of all available files
GetAllMetadata() map[string]models.File
// GetAllMetaDataIds returns all Ids that contain metadata
GetAllMetaDataIds() []string
// GetMetaDataById returns a models.File from the ID passed or false if the id is not valid
GetMetaDataById(id string) (models.File, bool)
// SaveMetaData stores the metadata of a file to the disk

View File

@@ -509,19 +509,6 @@ func TestMetaData(t *testing.T) {
_ = dbInstance.GetAllMetadata()
}
func TestGetAllMetaDataIds(t *testing.T) {
instance, err := New(config)
test.IsNil(t, err)
ids := instance.GetAllMetaDataIds()
test.IsEqualString(t, ids[0], "test2")
test.IsEqualString(t, ids[1], "test3")
instance.Close()
defer test.ExpectPanic(t)
_ = instance.GetAllMetaDataIds()
}
func TestUsers(t *testing.T) {
instance, err := New(config)
test.IsNil(t, err)

View File

@@ -63,15 +63,6 @@ func unmarshalEncryptionInfo(f models.File) (models.File, error) {
return f, nil
}
// GetAllMetaDataIds returns all Ids that contain metadata
func (p DatabaseProvider) GetAllMetaDataIds() []string {
result := make([]string, 0)
for _, key := range p.getAllKeysWithPrefix(prefixMetaData) {
result = append(result, strings.Replace(key, prefixMetaData, "", 1))
}
return result
}
// GetMetaDataById returns a models.File from the ID passed or false if the id is not valid
func (p DatabaseProvider) GetMetaDataById(id string) (models.File, bool) {
result, ok := p.getHashMap(prefixMetaData + id)

View File

@@ -128,20 +128,6 @@ func TestDatabaseProvider_GetType(t *testing.T) {
test.IsEqualInt(t, dbInstance.GetType(), 0)
}
func TestGetAllMetaDataIds(t *testing.T) {
instance, err := New(config)
test.IsNil(t, err)
dbInstance = instance
ids := dbInstance.GetAllMetaDataIds()
test.IsEqualString(t, ids[0], "test2")
test.IsEqualString(t, ids[1], "test3")
dbInstance.Close()
defer test.ExpectPanic(t)
_ = dbInstance.GetAllMetaDataIds()
}
func TestHotlink(t *testing.T) {
instance, err := New(config)
test.IsNil(t, err)

View File

@@ -81,21 +81,6 @@ func (p DatabaseProvider) GetAllMetadata() map[string]models.File {
return result
}
// GetAllMetaDataIds returns all Ids that contain metadata
func (p DatabaseProvider) GetAllMetaDataIds() []string {
keys := make([]string, 0)
rows, err := p.sqliteDb.Query("SELECT Id FROM FileMetaData")
helper.Check(err)
defer rows.Close()
for rows.Next() {
rowData := schemaMetaData{}
err = rows.Scan(&rowData.Id)
helper.Check(err)
keys = append(keys, rowData.Id)
}
return keys
}
// GetMetaDataById returns a models.File from the ID passed or false if the id is not valid
func (p DatabaseProvider) GetMetaDataById(id string) (models.File, bool) {
result := models.File{}

View File

@@ -6,4 +6,4 @@ package setup
// protectedUrls contains a list of URLs that need to be protected if authentication is disabled.
// This list will be displayed during the setup
var protectedUrls = []string{"/admin", "/apiKeys", "/changePassword", "/e2eInfo", "/e2eSetup", "/logs", "/uploadChunk", "/uploadStatus", "/users"}
var protectedUrls = []string{"/admin", "/apiKeys", "/changePassword", "/e2eSetup", "/logs", "/uploadChunk", "/uploadStatus", "/users"}

View File

@@ -254,9 +254,6 @@ func getStream(cipherKey []byte) *sio.Stream {
log.Fatal(err)
}
stream := sio.NewStream(gcm, sio.BufSize)
if err != nil {
log.Fatal(err)
}
return stream
}

View File

@@ -36,6 +36,7 @@ func ParseChunkInfo(r *http.Request, isApiCall bool) (ChunkInfo, error) {
return ChunkInfo{}, err
}
// If these are changed, make sure to edit End2End.go as well
formTotalSize := "dztotalfilesize"
formOffset := "dzchunkbyteoffset"
formUuid := "dzuuid"

View File

@@ -9,7 +9,6 @@ import (
"context"
"embed"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/NYTimes/gziphandler"
@@ -95,7 +94,6 @@ func Start() {
mux.HandleFunc("/changePassword", requireLogin(changePassword, true, true))
mux.HandleFunc("/d", showDownload)
mux.HandleFunc("/downloadFile", downloadFile)
mux.HandleFunc("/e2eInfo", requireLogin(e2eInfo, false, false))
mux.HandleFunc("/e2eSetup", requireLogin(showE2ESetup, true, false))
mux.HandleFunc("/error", showError)
mux.HandleFunc("/error-auth", showErrorAuth)
@@ -543,63 +541,6 @@ func showHotlink(w http.ResponseWriter, r *http.Request) {
storage.ServeFile(file, w, r, false)
}
// Handling of /e2eInfo
// User needs to be admin. Receives or stores end2end encryption info
func e2eInfo(w http.ResponseWriter, r *http.Request) {
action, ok := r.URL.Query()["action"]
if !ok || len(action) < 1 {
responseError(w, errors.New("invalid action specified"))
return
}
user, err := authentication.GetUserFromRequest(r)
if err != nil {
responseError(w, err)
return
}
switch action[0] {
case "get":
getE2eInfo(w, user.Id)
case "store":
storeE2eInfo(w, r, user.Id)
default:
responseError(w, errors.New("invalid action specified"))
}
}
func storeE2eInfo(w http.ResponseWriter, r *http.Request, userId int) {
err := r.ParseForm()
if err != nil {
responseError(w, err)
return
}
uploadedInfoBase64 := r.Form.Get("info")
if uploadedInfoBase64 == "" {
responseError(w, errors.New("empty info sent"))
return
}
uploadedInfo, err := base64.StdEncoding.DecodeString(uploadedInfoBase64)
if err != nil {
responseError(w, err)
return
}
var info models.E2EInfoEncrypted
err = json.Unmarshal(uploadedInfo, &info)
if err != nil {
responseError(w, err)
return
}
database.SaveEnd2EndInfo(info, userId)
_, _ = w.Write([]byte("\"result\":\"OK\""))
}
func getE2eInfo(w http.ResponseWriter, userId int) {
info := database.GetEnd2EndInfo(userId)
bytesE2e, err := json.Marshal(info)
helper.Check(err)
_, _ = w.Write(bytesE2e)
}
// Checks if a file is associated with the GET parameter from the current URL
// Stops for 500ms to limit brute forcing if invalid key and redirects to redirectUrl
func queryUrl(w http.ResponseWriter, r *http.Request, redirectUrl string) string {
@@ -663,6 +604,7 @@ func showE2ESetup(w http.ResponseWriter, r *http.Request) {
err = templateFolder.ExecuteTemplate(w, "e2esetup", e2ESetupView{
HasBeenSetup: e2einfo.HasBeenSetUp(),
PublicName: configuration.Get().PublicName,
SystemKey: api.GetSystemKey(user.Id),
CustomContent: customStaticInfo})
helper.CheckIgnoreTimeout(err)
}
@@ -690,6 +632,7 @@ type e2ESetupView struct {
IsDownloadView bool
HasBeenSetup bool
PublicName string
SystemKey string
CustomContent customStatic
}

View File

@@ -5,6 +5,7 @@ import (
"errors"
"github.com/forceu/gokapi/internal/configuration"
"github.com/forceu/gokapi/internal/configuration/database"
"github.com/forceu/gokapi/internal/encryption"
"github.com/forceu/gokapi/internal/helper"
"github.com/forceu/gokapi/internal/logging"
"github.com/forceu/gokapi/internal/models"
@@ -421,7 +422,39 @@ func doBlockingPartCompleteChunk(w http.ResponseWriter, request *paramChunkCompl
outputFileJson(w, file)
}
func apiVersionInfo(w http.ResponseWriter, _ requestParser, _ models.User) {
type versionInfo struct {
Version string
VersionInt int
}
result, err := json.Marshal(versionInfo{versionReadable, versionInt})
helper.Check(err)
_, _ = w.Write(result)
}
func apiConfigInfo(w http.ResponseWriter, _ requestParser, _ models.User) {
type configInfo struct {
MaxFilesize int
MaxChunksize int
EndToEndEncryptionEnabled bool
}
config := configuration.Get()
result, err := json.Marshal(configInfo{
MaxFilesize: config.MaxFileSizeMB,
MaxChunksize: config.ChunkSize,
EndToEndEncryptionEnabled: config.Encryption.Level == encryption.EndToEndEncryption,
})
helper.Check(err)
_, _ = w.Write(result)
}
func apiList(w http.ResponseWriter, _ requestParser, user models.User) {
validFiles := getFilesForUser(user)
result, err := json.Marshal(validFiles)
helper.Check(err)
_, _ = w.Write(result)
}
func getFilesForUser(user models.User) []models.FileApiOutput {
var validFiles []models.FileApiOutput
timeNow := time.Now().Unix()
config := configuration.Get()
@@ -434,9 +467,7 @@ func apiList(w http.ResponseWriter, _ requestParser, user models.User) {
}
}
}
result, err := json.Marshal(validFiles)
helper.Check(err)
_, _ = w.Write(result)
return validFiles
}
func apiListSingle(w http.ResponseWriter, r requestParser, user models.User) {
@@ -738,6 +769,28 @@ func apiLogsDelete(_ http.ResponseWriter, r requestParser, user models.User) {
logging.DeleteLogs(user.Name, user.Id, request.Timestamp, request.Request)
}
func apiE2eGet(w http.ResponseWriter, _ requestParser, user models.User) {
info := database.GetEnd2EndInfo(user.Id)
files := getFilesForUser(user)
ids := make([]string, len(files))
for i, file := range files {
ids[i] = file.Id
}
info.AvailableFiles = ids
bytesE2e, err := json.Marshal(info)
helper.Check(err)
_, _ = w.Write(bytesE2e)
}
func apiE2eSet(w http.ResponseWriter, r requestParser, user models.User) {
request, ok := r.(*paramE2eStore)
if !ok {
panic("invalid parameter passed")
}
database.SaveEnd2EndInfo(request.EncryptedInfo, user.Id)
_, _ = w.Write([]byte("\"result\":\"OK\""))
}
func isAuthorisedForApi(r *http.Request, routing apiRoute) (models.User, bool) {
apiKey := r.Header.Get("apikey")
user, _, ok := isValidApiKey(apiKey, true, routing.ApiPerm)

View File

@@ -0,0 +1,5 @@
// Code generated by updateApiRouting.go - DO NOT EDIT.
package api
const versionReadable = "2.1.0-dev"
const versionInt = 20100

View File

@@ -2,6 +2,7 @@ package api
import (
"encoding/base64"
"encoding/json"
"errors"
"github.com/forceu/gokapi/internal/models"
"github.com/forceu/gokapi/internal/storage"
@@ -28,6 +29,18 @@ func (r apiRoute) Continue(w http.ResponseWriter, request requestParser, user mo
type apiFunc func(w http.ResponseWriter, request requestParser, user models.User)
var routes = []apiRoute{
{
Url: "/info/version",
ApiPerm: models.ApiPermNone,
execution: apiVersionInfo,
RequestParser: nil,
},
{
Url: "/info/config",
ApiPerm: models.ApiPermUpload,
execution: apiConfigInfo,
RequestParser: nil,
},
{
Url: "/files/list",
ApiPerm: models.ApiPermView,
@@ -149,6 +162,18 @@ var routes = []apiRoute{
execution: apiLogsDelete,
RequestParser: &paramLogsDelete{},
},
{
Url: "/e2e/get", // not published in API documentation
ApiPerm: models.ApiPermUpload,
execution: apiE2eGet,
RequestParser: nil,
},
{
Url: "/e2e/set", // not published in API documentation
ApiPerm: models.ApiPermUpload,
execution: apiE2eSet,
RequestParser: &paramE2eStore{},
},
}
func getRouting(requestUrl string) (apiRoute, bool) {
@@ -419,6 +444,28 @@ type paramUserResetPw struct {
func (p *paramUserResetPw) ProcessParameter(_ *http.Request) error { return nil }
type paramE2eStore struct {
EncryptedInfo models.E2EInfoEncrypted
foundHeaders map[string]bool
}
func (p *paramE2eStore) ProcessParameter(r *http.Request) error {
type expectedInput struct {
Content string `json:"content"`
}
var input expectedInput
err := json.NewDecoder(r.Body).Decode(&input)
if err != nil {
return err
}
content, err := base64.StdEncoding.DecodeString(input.Content)
if err != nil {
return err
}
return json.Unmarshal(content, &p.EncryptedInfo)
}
type paramLogsDelete struct {
Timestamp int64 `header:"timestamp"`
Request *http.Request
@@ -443,12 +490,12 @@ type paramChunkComplete struct {
Uuid string `header:"uuid" required:"true"`
FileName string `header:"filename" required:"true"`
FileSize int64 `header:"filesize" required:"true"`
RealSize int64 `header:"realsize"`
RealSize int64 `header:"realsize"` // not published in API documentation
ContentType string `header:"contenttype"`
AllowedDownloads int `header:"allowedDownloads"`
ExpiryDays int `header:"expiryDays"`
Password string `header:"password"`
IsE2E bool `header:"isE2E"`
IsE2E bool `header:"isE2E"` // not published in API documentation
IsNonBlocking bool `header:"nonblocking"`
UnlimitedDownloads bool
UnlimitedTime bool
@@ -490,6 +537,9 @@ func (p *paramChunkComplete) ProcessParameter(_ *http.Request) error {
p.FileName = string(decoded)
}
if p.ContentType == "" {
p.ContentType = "application/octet-stream"
}
p.FileHeader = chunking.FileHeader{
Filename: p.FileName,
ContentType: p.ContentType,

View File

@@ -636,6 +636,17 @@ func (p *paramUserResetPw) New() requestParser {
return &paramUserResetPw{}
}
// ParseRequest parses the header file. As paramE2eStore has no fields with the
// tag header, this method does nothing, except calling ProcessParameter()
func (p *paramE2eStore) ParseRequest(r *http.Request) error {
return p.ProcessParameter(r)
}
// New returns a new instance of paramE2eStore struct
func (p *paramE2eStore) New() requestParser {
return &paramE2eStore{}
}
// ParseRequest reads r and saves the passed header values in the paramLogsDelete struct
// In the end, ProcessParameter() is called
func (p *paramLogsDelete) ParseRequest(r *http.Request) error {

View File

@@ -17,6 +17,9 @@
},
],
"tags": [
{
"name": "info"
},
{
"name": "files"
},
@@ -34,6 +37,67 @@
}
],
"paths": {
"/info/version": {
"get": {
"tags": [
"info"
],
"summary": "Outputs Gokapi version",
"description": "This API call returns the Gokapi server version in a readable format and as an integer.",
"operationId": "version",
"responses": {
"200": {
"description": "Operation successful",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/VersionInfo"
}
}
}
}
},
"401": {
"description": "Invalid API key provided for authentication"
}
}
}
},
"/info/config": {
"get": {
"tags": [
"info"
],
"summary": "Outputs Gokapi upload parameters",
"description": "This API call returns the Gokapi server upload parameters. All filesizes are in unit Megabyte. Requires API permission UPLOAD",
"operationId": "uploadconfig",
"security": [
{
"apikey": ["UPLOAD"]
},
],
"responses": {
"200": {
"description": "Operation successful",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ConfigInfo"
}
}
}
}
},
"401": {
"description": "Invalid API key provided for authentication or API key does not have the required permission"
}
}
}
},
"/files/list": {
"get": {
"tags": [
@@ -125,7 +189,7 @@
"chunk"
],
"summary": "Uploads a new chunk",
"description": "Uploads a file in chunks, in case a reverse proxy does not support upload of larger files. Parallel uploading is supported. Must call /chunk/complete after all chunks have been uploaded. WARNING: Does not support end-to-end encryption! If server is setup to utilise end-to-end encryption, file will be stored in plain-text! Requires API permission UPLOAD",
"description": "Uploads a file in chunks, in case a reverse proxy does not support upload of larger files. Parallel uploading is supported. Must call /chunk/complete after all chunks have been uploaded. WARNING: Does not support end-to-end encryption! If server is setup to utilise end-to-end encryption, file will be stored in plain-text! To upload an end-to-end encrypted file, use gokapi-cli. Requires API permission UPLOAD",
"operationId": "chunkadd",
"security": [
{
@@ -303,7 +367,7 @@
"files"
],
"summary": "Adds a new file without chunking",
"description": "Uploads the submitted file to Gokapi. Please note: This method does not use chunking, therefore if you are behind a reverse proxy or have a provider that limits upload filesizes, this might not work for bigger files (e.g. Cloudflare). WARNING: Does not support end-to-end encryption! If server is setup to utilise end-to-end encryption, file will be stored in plain-text! Requires API permission UPLOAD",
"description": "Uploads the submitted file to Gokapi. Please note: This method does not use chunking, therefore if you are behind a reverse proxy or have a provider that limits upload filesizes, this might not work for bigger files (e.g. Cloudflare). WARNING: Does not support end-to-end encryption! If server is setup to utilise end-to-end encryption, file will be stored in plain-text! To upload an end-to-end encrypted file, use gokapi-cli. Requires API permission UPLOAD",
"operationId": "add",
"security": [
{
@@ -1312,6 +1376,38 @@
"description": "NewUser is the struct used for the result after creating a new API key",
"x-go-package": "Gokapi/internal/models"
},
"VersionInfo": {
"type": "object",
"properties": {
"Version": {
"type": "string",
"example": "2.1.0"
},
"VersionInt": {
"type": "integer",
"example": 20100
},
"EndToEndEncryptionEnabled": {
"type": "boolean",
"example": false
}
},
"description": "VersionInfo is the struct used for returning version numbers",
},
"ConfigInfo": {
"type": "object",
"properties": {
"MaxFilesize": {
"type": "integer",
"example": "100"
},
"MaxChunksize": {
"type": "integer",
"example": 40
}
},
"description": "ConfigInfo is the struct used for returning configuration data",
},
"PasswordReset": {
"type": "object",
"properties": {
@@ -1407,41 +1503,6 @@
"description": "The chunk's offset starting at the beginning of the file"
}
}
},"chunkingcomplete": {
"required": [
"uuid","filename","filesize"
],
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "The unique ID that was used for the uploaded chunks"
},
"filename": {
"type": "string",
"description": "The filename of the uploaded file"
},
"filesize": {
"type": "integer",
"description": "The total filesize of the uploaded file in bytes"
},
"contenttype": {
"type": "string",
"description": "The MIME content type. If empty, application/octet-stream will be used."
},
"allowedDownloads": {
"type": "integer",
"description": "How many downloads are allowed. Default of 1 will be used if empty. Unlimited if 0 is passed."
},
"expiryDays": {
"type": "integer",
"description": "How many days the file will be stored. Default of 14 will be used if empty. Unlimited if 0 is passed."
},
"password": {
"type": "string",
"description": "Password for this file to be set. No password will be used if empty"
}
}
}
},
"securitySchemes": {

View File

@@ -114,14 +114,14 @@ async function apiAuthCreate() {
async function apiChunkComplete(uuid, filename, filesize, realsize, contenttype, allowedDownloads, expiryDays, password, isE2E, nonblocking) {
const apiUrl = './api/chunk/complete';
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'apikey': systemKey,
'uuid': uuid,
'filename': 'base64:'+Base64.encode(filename),
'filename': 'base64:' + Base64.encode(filename),
'filesize': filesize,
'realsize': realsize,
'contenttype': contenttype,
@@ -135,20 +135,20 @@ async function apiChunkComplete(uuid, filename, filesize, realsize, contenttype,
try {
const response = await fetch(apiUrl, requestOptions);
if (!response.ok) {
let errorMessage;
if (!response.ok) {
let errorMessage;
// Attempt to parse JSON, fallback to text if parsing fails
try {
const errorResponse = await response.json();
errorMessage = errorResponse.ErrorMessage || `Request failed with status: ${response.status}`;
} catch {
// Handle non-JSON error
const errorText = await response.text();
errorMessage = errorText || `Request failed with status: ${response.status}`;
}
throw new Error(errorMessage);
}
// Attempt to parse JSON, fallback to text if parsing fails
try {
const errorResponse = await response.json();
errorMessage = errorResponse.ErrorMessage || `Request failed with status: ${response.status}`;
} catch {
// Handle non-JSON error
const errorText = await response.text();
errorMessage = errorText || `Request failed with status: ${response.status}`;
}
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
@@ -314,9 +314,9 @@ async function apiUserCreate(userName) {
try {
const response = await fetch(apiUrl, requestOptions);
if (!response.ok) {
if (response.status==409) {
throw new Error("duplicate");
}
if (response.status == 409) {
throw new Error("duplicate");
}
throw new Error(`Request failed with status: ${response.status}`);
}
const data = await response.json();
@@ -410,7 +410,7 @@ async function apiUserResetPassword(id, generatePw) {
const apiUrl = './api/user/resetPassword';
const requestOptions = {
method: 'POST',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'apikey': systemKey,
@@ -438,7 +438,7 @@ async function apiLogsDelete(timestamp) {
const apiUrl = './api/logs/delete';
const requestOptions = {
method: 'POST',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'apikey': systemKey,
@@ -457,4 +457,55 @@ async function apiLogsDelete(timestamp) {
}
}
// E2E
async function apiE2eGet() {
const apiUrl = './api/e2e/get';
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'apikey': systemKey
},
};
try {
const response = await fetch(apiUrl, requestOptions);
if (!response.ok) {
throw new Error(`Request failed with status: ${response.status}`);
}
return await response.text();
// return await response.json();
} catch (error) {
console.error("Error in apiE2eGet:", error);
throw error;
}
}
async function apiE2eStore(content) {
const apiUrl = './api/e2e/set';
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'apikey': systemKey
},
body: JSON.stringify({
content: content
}),
};
try {
const response = await fetch(apiUrl, requestOptions);
if (!response.ok) {
throw new Error(`Request failed with status: ${response.status}`);
}
} catch (error) {
console.error("Error in apiE2eStore:", error);
throw error;
}
}

View File

@@ -3,7 +3,10 @@
// go generate ./...
var clipboard = new ClipboardJS('.copyurl');
try {
var clipboard = new ClipboardJS('.copyurl');
} catch (ignored) {
}
var toastId;

View File

@@ -237,7 +237,6 @@ function dropzoneGetFile(uid) {
}
function requestFileInfo(fileId, uid) {
apiFilesListById(fileId)
.then(data => {
addRow(data);

View File

@@ -25,7 +25,6 @@ function checkIfE2EKeyIsSet() {
return;
}
getE2EInfo();
GokapiE2EDecryptMenu();
dropzoneObject.enable();
document.getElementsByClassName("dz-button")[0].innerText = "Drop files, paste or click here to upload (end-to-end encrypted)";
});
@@ -33,50 +32,40 @@ function checkIfE2EKeyIsSet() {
}
function getE2EInfo() {
var xhr = new XMLHttpRequest();
xhr.open("GET", "./e2eInfo?action=get", false);
xhr.onreadystatechange = function() {
if (this.readyState == 4) {
if (this.status == 200) {
let err = GokapiE2EInfoParse(xhr.response);
if (err !== null) {
displayError(err);
if (err.message === "cipher: message authentication failed") {
invalidCipherRedirectConfim();
}
}
apiE2eGet()
.then(data => {
let err = GokapiE2EInfoParse(data);
if (err === null) {
GokapiE2EDecryptMenu();
} else {
displayError("Trying to get E2E info: " + xhr.statusText);
displayError(err);
if (err.message === "cipher: message authentication failed") {
invalidCipherRedirectConfim();
} else {
displayError("Trying to get E2E info: " + data);
}
}
}
};
xhr.send();
})
.catch(error => {
displayError("Trying to get E2E info: " + error);
});
}
function storeE2EInfo(data) {
apiE2eStore(data)
.catch(error => {
displayError("Trying to store E2E info: " + error);
});
}
function invalidCipherRedirectConfim() {
if (confirm('It appears that an invalid end-to-end encryption key has been entered. Would you like to enter the correct one?')) {
window.location = './e2eSetup';
}
}
function storeE2EInfo(data) {
var xhr = new XMLHttpRequest();
xhr.open("POST", "./e2eInfo?action=store", false);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function() {
if (this.readyState == 4) {
if (this.status != 200) {
displayError("Trying to store E2E info: " + xhr.statusText);
}
}
};
let formData = new FormData();
formData.append("info", data);
xhr.send(urlencodeFormData(formData));
}
function isE2EKeySet() {
let key = localStorage.getItem("e2ekey");
return key !== null && key !== "";
@@ -166,38 +155,49 @@ function setE2eUpload() {
}
function decryptFileEntry(id, filename, cipher) {
function decryptFileEntries(fileMap) {
let datatable = $('#maintable').DataTable();
const rows = datatable.rows().nodes();
for (let i = 0; i < rows.length; i++) {
const cell = datatable.cell(i, 0).node();
if ("cell-name-" + id === $(cell).attr("id")) {
let cellNode = datatable.cell(i, 0).node();
let urlNode = datatable.cell(i, 5).node();
let urlLink = urlNode.querySelector("a");
let url = urlLink.getAttribute("href");
cellNode.textContent = filename;
if (!url.includes(cipher)) {
if (IncludeFilename) {
url = url.replace("/Encrypted%20File", "/" + encodeURIComponent(filename));
}
url = url + "#" + cipher;
urlLink.setAttribute("href", url);
const idAttr = $(cell).attr("id");
if (!idAttr) continue;
const id = idAttr.substring("cell-name-".length);
const entry = fileMap[id];
if (!entry) continue; // no matching file for this row
const filename = entry[0];
const cipher = entry[1];
// Column 0: filename
cell.textContent = filename;
// Column 5: update link
let urlNode = datatable.cell(i, 5).node();
let urlLink = urlNode.querySelector("a");
let url = urlLink.getAttribute("href");
if (!url.includes(cipher)) {
if (IncludeFilename) {
url = url.replace("/Encrypted%20File", "/" + encodeURIComponent(filename));
}
datatable.cell(i, 5).node(urlNode);
let buttonNode = datatable.cell(i, 6).node();
let button = buttonNode.querySelector("button");
button.setAttribute("data-clipboard-text", url);
document.getElementById("qrcode-"+id).onclick = function() {showQrCode(url);};
document.getElementById("email-"+id).href = "mailto:?body="+encodeURIComponent(url);
datatable.cell(i, 6).node(buttonNode);
break;
url += "#" + cipher;
urlLink.setAttribute("href", url);
}
// Column 6: update button and QR/email
let buttonNode = datatable.cell(i, 6).node();
let button = buttonNode.querySelector("button");
button.setAttribute("data-clipboard-text", url);
let qrElem = document.getElementById("qrcode-" + id);
if (qrElem) qrElem.onclick = () => showQrCode(url);
let emailElem = document.getElementById("email-" + id);
if (emailElem) emailElem.href = "mailto:?body=" + encodeURIComponent(url);
}
}

View File

@@ -1 +0,0 @@
function displayError(e){document.getElementById("errordiv").style.display="block",document.getElementById("errormessage").innerHTML="<b>Error: </b> "+e.toString().replace(/^Error:/gi,""),console.error("Caught exception",e)}function checkIfE2EKeyIsSet(){isE2EKeySet()?loadWasm(function(){let t=localStorage.getItem("e2ekey"),e=GokapiE2ESetCipher(t);if(e!==null){displayError(e);return}getE2EInfo(),GokapiE2EDecryptMenu(),dropzoneObject.enable(),document.getElementsByClassName("dz-button")[0].innerText="Drop files, paste or click here to upload (end-to-end encrypted)"}):window.location="./e2eSetup"}function getE2EInfo(){var e=new XMLHttpRequest;e.open("GET","./e2eInfo?action=get",!1),e.onreadystatechange=function(){if(this.readyState==4)if(this.status==200){let t=GokapiE2EInfoParse(e.response);t!==null&&(displayError(t),t.message==="cipher: message authentication failed"&&invalidCipherRedirectConfim())}else displayError("Trying to get E2E info: "+e.statusText)},e.send()}function invalidCipherRedirectConfim(){confirm("It appears that an invalid end-to-end encryption key has been entered. Would you like to enter the correct one?")&&(window.location="./e2eSetup")}function storeE2EInfo(e){var t=new XMLHttpRequest;t.open("POST","./e2eInfo?action=store",!1),t.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),t.onreadystatechange=function(){this.readyState==4&&this.status!=200&&displayError("Trying to store E2E info: "+t.statusText)};let n=new FormData;n.append("info",e),t.send(urlencodeFormData(n))}function isE2EKeySet(){let e=localStorage.getItem("e2ekey");return e!==null&&e!==""}function loadWasm(e){const t=new Go,s="e2e.wasm?v=1";var n;try{"instantiateStreaming"in WebAssembly?WebAssembly.instantiateStreaming(fetch(s),t.importObject).then(function(s){n=s.instance,t.run(n),e()}):fetch(s).then(e=>e.arrayBuffer()).then(s=>WebAssembly.instantiate(s,t.importObject).then(function(s){n=s.instance,t.run(n),e()}))}catch(e){displayError(e)}}function urlencodeFormData(e){let t="";function s(e){return encodeURIComponent(e).replace(/%20/g,"+")}for(var n of e.entries())typeof n[1]=="string"&&(t+=(t?"&":"")+s(n[0])+"="+s(n[1]));return t}function setE2eUpload(){dropzoneObject.uploadFiles=function(e){this._transformFiles(e,t=>{let o=t[0];e[0].upload.chunked=!0,e[0].isEndToEndEncrypted=!0;let i=e[0].upload.filename,s=o.size,r=0,n=GokapiE2EEncryptNew(e[0].upload.uuid,s,i);if(n instanceof Error){displayError(n);return}e[0].upload.totalChunkCount=Math.ceil(n/this.options.chunkSize),e[0].sizeEncrypted=n;let a=e[0],c=0,l=0,d=!1,u=0;uploadChunk(a,0,n,s,dropzoneObject.options.chunkSize,0)})}}function decryptFileEntry(e,t,n){let s=$("#maintable").DataTable();const o=s.rows().nodes();for(let i=0;i<o.length;i++){const a=s.cell(i,0).node();if("cell-name-"+e===$(a).attr("id")){s.cell(i,0).data(t);let a=s.cell(i,5).node(),r=a.querySelector("a"),o=r.getAttribute("href");o.includes(n)||(IncludeFilename&&(o=o.replace("/Encrypted%20File","/"+encodeURI(t))),o=o+"#"+n,r.setAttribute("href",o)),s.cell(i,5).node(a);let c=s.cell(i,6).node(),l=c.querySelector("button");l.setAttribute("data-clipboard-text",o),document.getElementById("qrcode-"+e).onclick=function(){showQrCode(o)},s.cell(i,6).node(c);break}}}async function uploadChunk(e,t,n,s,o,i){let c=!1,l=t*o,d=l+o;t===e.upload.totalChunkCount-1&&(c=!0,d=s);let u=e.webkitSlice?e.webkitSlice(l,d):e.slice(l,d),a=await u.arrayBuffer(),r=await GokapiE2EUploadChunk(e.upload.uuid,a.byteLength,c,new Uint8Array(a));if(r instanceof Error){displayError(a);return}let h=await postChunk(e.upload.uuid,i,n,r,e);if(h!==null){e.accepted=!1,dropzoneObject._errorProcessing([e],h);return}i=i+r.byteLength,a=null,r=null,u=null,c?(e.status=Dropzone.SUCCESS,dropzoneObject.emit("success",e,"success",null),dropzoneObject.emit("complete",e),dropzoneObject.processQueue(),dropzoneObject.options.chunksUploaded(e,()=>{})):await uploadChunk(e,t+1,n,s,o,i)}async function postChunk(e,t,n,s,o){return new Promise(i=>{let r=new FormData;r.append("dztotalfilesize",n),r.append("dzchunkbyteoffset",t),r.append("dzuuid",e),r.append("file",new Blob([s]),"encrypted.file");let a=new XMLHttpRequest;a.open("POST","./uploadChunk");let c=a.upload!=null?a.upload:a;c.onprogress=e=>{try{dropzoneObject.emit("uploadprogress",o,100*(e.loaded+t)/n,e.loaded+t)}catch(e){console.log(e)}},a.onreadystatechange=function(){this.readyState==4&&(this.status==200?i(null):(console.log(a.responseText),i(a.responseText)))},a.send(r)})}

View File

@@ -1 +0,0 @@
function displayError(e){document.getElementById("errordiv").style.display="block";const t=document.getElementById("errormessage");t.innerText="";const n=document.createElement("b");n.innerText="Error: ";const s=document.createTextNode(e.toString().replace(/^Error:/gi,""));t.appendChild(n),t.appendChild(s),console.error("Caught exception",e)}function checkIfE2EKeyIsSet(){isE2EKeySet()?loadWasm(function(){let t=localStorage.getItem("e2ekey"),e=GokapiE2ESetCipher(t);if(e!==null){displayError(e);return}getE2EInfo(),GokapiE2EDecryptMenu(),dropzoneObject.enable(),document.getElementsByClassName("dz-button")[0].innerText="Drop files, paste or click here to upload (end-to-end encrypted)"}):window.location="./e2eSetup"}function getE2EInfo(){var e=new XMLHttpRequest;e.open("GET","./e2eInfo?action=get",!1),e.onreadystatechange=function(){if(this.readyState==4)if(this.status==200){let t=GokapiE2EInfoParse(e.response);t!==null&&(displayError(t),t.message==="cipher: message authentication failed"&&invalidCipherRedirectConfim())}else displayError("Trying to get E2E info: "+e.statusText)},e.send()}function invalidCipherRedirectConfim(){confirm("It appears that an invalid end-to-end encryption key has been entered. Would you like to enter the correct one?")&&(window.location="./e2eSetup")}function storeE2EInfo(e){var t=new XMLHttpRequest;t.open("POST","./e2eInfo?action=store",!1),t.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),t.onreadystatechange=function(){this.readyState==4&&this.status!=200&&displayError("Trying to store E2E info: "+t.statusText)};let n=new FormData;n.append("info",e),t.send(urlencodeFormData(n))}function isE2EKeySet(){let e=localStorage.getItem("e2ekey");return e!==null&&e!==""}function loadWasm(e){const t=new Go,s="e2e.wasm?v=1";var n;try{"instantiateStreaming"in WebAssembly?WebAssembly.instantiateStreaming(fetch(s),t.importObject).then(function(s){n=s.instance,t.run(n),e()}):fetch(s).then(e=>e.arrayBuffer()).then(s=>WebAssembly.instantiate(s,t.importObject).then(function(s){n=s.instance,t.run(n),e()}))}catch(e){displayError(e)}}function urlencodeFormData(e){let t="";function s(e){return encodeURIComponent(e).replace(/%20/g,"+")}for(var n of e.entries())typeof n[1]=="string"&&(t+=(t?"&":"")+s(n[0])+"="+s(n[1]));return t}function setE2eUpload(){dropzoneObject.uploadFiles=function(e){this._transformFiles(e,t=>{let o=t[0];e[0].upload.chunked=!0,e[0].isEndToEndEncrypted=!0;let i=e[0].upload.filename,s=o.size,r=0,n=GokapiE2EEncryptNew(e[0].upload.uuid,s,i);if(n instanceof Error){displayError(n);return}e[0].upload.totalChunkCount=Math.ceil(n/this.options.chunkSize),e[0].sizeEncrypted=n;let a=e[0],c=0,l=0,d=!1,u=0;uploadChunk(a,0,n,s,dropzoneObject.options.chunkSize,0)})}}function decryptFileEntry(e,t,n){let s=$("#maintable").DataTable();const o=s.rows().nodes();for(let i=0;i<o.length;i++){const a=s.cell(i,0).node();if("cell-name-"+e===$(a).attr("id")){let l=s.cell(i,0).node(),a=s.cell(i,5).node(),r=a.querySelector("a"),o=r.getAttribute("href");l.textContent=t,o.includes(n)||(IncludeFilename&&(o=o.replace("/Encrypted%20File","/"+encodeURIComponent(t))),o=o+"#"+n,r.setAttribute("href",o)),s.cell(i,5).node(a);let c=s.cell(i,6).node(),d=c.querySelector("button");d.setAttribute("data-clipboard-text",o),document.getElementById("qrcode-"+e).onclick=function(){showQrCode(o)},document.getElementById("email-"+e).href="mailto:?body="+encodeURIComponent(o),s.cell(i,6).node(c);break}}}async function uploadChunk(e,t,n,s,o,i){let c=!1,l=t*o,d=l+o;t===e.upload.totalChunkCount-1&&(c=!0,d=s);let u=e.webkitSlice?e.webkitSlice(l,d):e.slice(l,d),a=await u.arrayBuffer(),r=await GokapiE2EUploadChunk(e.upload.uuid,a.byteLength,c,new Uint8Array(a));if(r instanceof Error){displayError(a);return}let h=await postChunk(e.upload.uuid,i,n,r,e);if(h!==null){e.accepted=!1,dropzoneObject._errorProcessing([e],h);return}i=i+r.byteLength,a=null,r=null,u=null,c?(e.status=Dropzone.SUCCESS,dropzoneObject.emit("success",e,"success",null),dropzoneObject.emit("complete",e),dropzoneObject.processQueue(),dropzoneObject.options.chunksUploaded(e,()=>{})):await uploadChunk(e,t+1,n,s,o,i)}async function postChunk(e,t,n,s,o){return new Promise(i=>{let r=new FormData;r.append("dztotalfilesize",n),r.append("dzchunkbyteoffset",t),r.append("dzuuid",e),r.append("file",new Blob([s]),"encrypted.file");let a=new XMLHttpRequest;a.open("POST","./uploadChunk");let c=a.upload!=null?a.upload:a;c.onprogress=e=>{try{dropzoneObject.emit("uploadprogress",o,100*(e.loaded+t)/n,e.loaded+t)}catch(e){console.log(e)}},a.onreadystatechange=function(){this.readyState==4&&(this.status==200?i(null):(console.log(a.responseText),i(a.responseText)))},a.send(r)})}

View File

@@ -0,0 +1 @@
function displayError(e){document.getElementById("errordiv").style.display="block";const t=document.getElementById("errormessage");t.innerText="";const n=document.createElement("b");n.innerText="Error: ";const s=document.createTextNode(e.toString().replace(/^Error:/gi,""));t.appendChild(n),t.appendChild(s),console.error("Caught exception",e)}function checkIfE2EKeyIsSet(){isE2EKeySet()?loadWasm(function(){let t=localStorage.getItem("e2ekey"),e=GokapiE2ESetCipher(t);if(e!==null){displayError(e);return}getE2EInfo(),dropzoneObject.enable(),document.getElementsByClassName("dz-button")[0].innerText="Drop files, paste or click here to upload (end-to-end encrypted)"}):window.location="./e2eSetup"}function getE2EInfo(){apiE2eGet().then(e=>{let t=GokapiE2EInfoParse(e);t===null?GokapiE2EDecryptMenu():(displayError(t),t.message==="cipher: message authentication failed"?invalidCipherRedirectConfim():displayError("Trying to get E2E info: "+e))}).catch(e=>{displayError("Trying to get E2E info: "+e)})}function storeE2EInfo(e){apiE2eStore(e).catch(e=>{displayError("Trying to store E2E info: "+e)})}function invalidCipherRedirectConfim(){confirm("It appears that an invalid end-to-end encryption key has been entered. Would you like to enter the correct one?")&&(window.location="./e2eSetup")}function isE2EKeySet(){let e=localStorage.getItem("e2ekey");return e!==null&&e!==""}function loadWasm(e){const t=new Go,s="e2e.wasm?v=1";var n;try{"instantiateStreaming"in WebAssembly?WebAssembly.instantiateStreaming(fetch(s),t.importObject).then(function(s){n=s.instance,t.run(n),e()}):fetch(s).then(e=>e.arrayBuffer()).then(s=>WebAssembly.instantiate(s,t.importObject).then(function(s){n=s.instance,t.run(n),e()}))}catch(e){displayError(e)}}function urlencodeFormData(e){let t="";function s(e){return encodeURIComponent(e).replace(/%20/g,"+")}for(var n of e.entries())typeof n[1]=="string"&&(t+=(t?"&":"")+s(n[0])+"="+s(n[1]));return t}function setE2eUpload(){dropzoneObject.uploadFiles=function(e){this._transformFiles(e,t=>{let o=t[0];e[0].upload.chunked=!0,e[0].isEndToEndEncrypted=!0;let i=e[0].upload.filename,s=o.size,r=0,n=GokapiE2EEncryptNew(e[0].upload.uuid,s,i);if(n instanceof Error){displayError(n);return}e[0].upload.totalChunkCount=Math.ceil(n/this.options.chunkSize),e[0].sizeEncrypted=n;let a=e[0],c=0,l=0,d=!1,u=0;uploadChunk(a,0,n,s,dropzoneObject.options.chunkSize,0)})}}function decryptFileEntries(e){let t=$("#maintable").DataTable();const n=t.rows().nodes();for(let o=0;o<n.length;o++){const u=t.cell(o,0).node(),r=$(u).attr("id");if(!r)continue;const i=r.substring("cell-name-".length),a=e[i];if(!a)continue;const c=a[0],l=a[1];u.textContent=c;let f=t.cell(o,5).node(),d=f.querySelector("a"),s=d.getAttribute("href");s.includes(l)||(IncludeFilename&&(s=s.replace("/Encrypted%20File","/"+encodeURIComponent(c))),s+="#"+l,d.setAttribute("href",s));let p=t.cell(o,6).node(),g=p.querySelector("button");g.setAttribute("data-clipboard-text",s);let h=document.getElementById("qrcode-"+i);h&&(h.onclick=()=>showQrCode(s));let m=document.getElementById("email-"+i);m&&(m.href="mailto:?body="+encodeURIComponent(s))}}async function uploadChunk(e,t,n,s,o,i){let c=!1,l=t*o,d=l+o;t===e.upload.totalChunkCount-1&&(c=!0,d=s);let u=e.webkitSlice?e.webkitSlice(l,d):e.slice(l,d),a=await u.arrayBuffer(),r=await GokapiE2EUploadChunk(e.upload.uuid,a.byteLength,c,new Uint8Array(a));if(r instanceof Error){displayError(a);return}let h=await postChunk(e.upload.uuid,i,n,r,e);if(h!==null){e.accepted=!1,dropzoneObject._errorProcessing([e],h);return}i=i+r.byteLength,a=null,r=null,u=null,c?(e.status=Dropzone.SUCCESS,dropzoneObject.emit("success",e,"success",null),dropzoneObject.emit("complete",e),dropzoneObject.processQueue(),dropzoneObject.options.chunksUploaded(e,()=>{})):await uploadChunk(e,t+1,n,s,o,i)}async function postChunk(e,t,n,s,o){return new Promise(i=>{let r=new FormData;r.append("dztotalfilesize",n),r.append("dzchunkbyteoffset",t),r.append("dzuuid",e),r.append("file",new Blob([s]),"encrypted.file");let a=new XMLHttpRequest;a.open("POST","./uploadChunk");let c=a.upload!=null?a.upload:a;c.onprogress=e=>{try{dropzoneObject.emit("uploadprogress",o,100*(e.loaded+t)/n,e.loaded+t)}catch(e){console.log(e)}},a.onreadystatechange=function(){this.readyState==4&&(this.status==200?i(null):(console.log(a.responseText),i(a.responseText)))},a.send(r)})}

View File

@@ -1 +0,0 @@
function parseHashValue(e){let t=sessionStorage.getItem("key-"+e),n=sessionStorage.getItem("fn-"+e);if(t===null||n===null){if(hash=window.location.hash.substr(1),hash.length<50){redirectToE2EError();return}let t;try{let e=atob(hash);t=JSON.parse(e)}catch{redirectToE2EError();return}if(!isCorrectJson(t)){redirectToE2EError();return}sessionStorage.setItem("key-"+e,t.c),sessionStorage.setItem("fn-"+e,t.f)}}function isCorrectJson(e){return e.f!==void 0&&e.c!==void 0&&typeof e.f=="string"&&typeof e.c=="string"&&e.f!=""&&e.c!=""}function redirectToE2EError(){window.location="./error?e2e"}

View File

@@ -1 +1 @@
function parseHashValue(e){let t=sessionStorage.getItem("key-"+e),n=sessionStorage.getItem("fn-"+e);if(t===null||n===null){if(hash=window.location.hash.substr(1),hash.length<50){redirectToE2EError();return}let t;try{let e=atob(hash);t=JSON.parse(e)}catch{redirectToE2EError();return}if(!isCorrectJson(t)){redirectToE2EError();return}sessionStorage.setItem("key-"+e,t.c),sessionStorage.setItem("fn-"+e,t.f)}}function isCorrectJson(e){return e.f!==void 0&&e.c!==void 0&&typeof e.f=="string"&&typeof e.c=="string"&&e.f!=""&&e.c!=""}function redirectToE2EError(){window.location="./error?e2e"}
function parseHashValue(e){let t=sessionStorage.getItem("key-"+e),n=sessionStorage.getItem("fn-"+e);if(t===null||n===null){if(hash=window.location.hash.substr(1),hash.length<50){redirectToE2EError();return}let t;try{let e=atob(hash);t=JSON.parse(e)}catch{redirectToE2EError();return}if(!isCorrectJson(t)){redirectToE2EError();return}sessionStorage.setItem("key-"+e,t.c),sessionStorage.setItem("fn-"+e,t.f)}}function isCorrectJson(e){return e.f!==0[0]&&e.c!==0[0]&&typeof e.f=="string"&&typeof e.c=="string"&&e.f!=""&&e.c!=""}function redirectToE2EError(){window.location="./error?e2e"}

View File

@@ -1 +1 @@
/*! streamsaver. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */((e,t)=>{typeof module!="undefined"?module.exports=t():typeof define=="function"&&typeof define.amd=="object"?define(t):this[e]=t()})("streamSaver",()=>{"use strict";const t=typeof window=="object"?window:this;t.HTMLElement||console.warn("streamsaver is meant to run on browsers main thread");let e=null,r=!1;const l=e=>{try{e()}catch{}},c=t.WebStreamsPolyfill||{},i=t.isSecureContext;let s=/constructor/i.test(t.HTMLElement)||!!t.safari||!!t.WebKitPoint;const a=i||"MozAppearance"in document.documentElement.style?"iframe":"navigate",n={createWriteStream:h,WritableStream:t.WritableStream||c.WritableStream,supported:!0,version:{full:"2.0.5",major:2,minor:0,dot:5},mitm:"https://jimmywarting.github.io/StreamSaver.js/mitm.html?version=2.0.0"};function o(e){if(!e)throw new Error("meh");const t=document.createElement("iframe");return t.hidden=!0,t.src=e,t.loaded=!1,t.name="iframe",t.isIframe=!0,t.postMessage=(...e)=>t.contentWindow.postMessage(...e),t.addEventListener("load",()=>{t.loaded=!0},{once:!0}),document.body.appendChild(t),t}function d(e){const i="width=200,height=100",s=document.createDocumentFragment(),n={frame:t.open(e,"popup",i),loaded:!1,isIframe:!1,isPopup:!0,remove(){n.frame.close()},addEventListener(...e){s.addEventListener(...e)},dispatchEvent(...e){s.dispatchEvent(...e)},removeEventListener(...e){s.removeEventListener(...e)},postMessage(...e){n.frame.postMessage(...e)}},o=e=>{e.source===n.frame&&(n.loaded=!0,t.removeEventListener("message",o),n.dispatchEvent(new Event("load")))};return t.addEventListener("message",o),n}try{new Response(new ReadableStream),i&&!("serviceWorker"in navigator)&&(s=!0)}catch{s=!0}l(()=>{const{readable:t}=new TransformStream,e=new MessageChannel;e.port1.postMessage(t,[t]),e.port1.close(),e.port2.close(),r=!0,Object.defineProperty(n,"TransformStream",{configurable:!1,writable:!1,value:TransformStream})});function u(){e||(e=i?o(n.mitm):d(n.mitm))}function h(t,i,c){let d={size:null,pathname:null,writableStrategy:void 0,readableStrategy:void 0},p=0,h=null,l=null,m=null;if(Number.isFinite(i)?([c,i]=[i,c],console.warn("[StreamSaver] Deprecated pass an object as 2nd argument when creating a write stream"),d.size=c,d.writableStrategy=i):i&&i.highWaterMark?(console.warn("[StreamSaver] Deprecated pass an object as 2nd argument when creating a write stream"),d.size=c,d.writableStrategy=i):d=i||{},!s){u(),l=new MessageChannel,t=encodeURIComponent(t.replace(/\//g,":")).replace(/['()]/g,escape).replace(/\*/g,"%2A");const s={transferringReadable:r,pathname:d.pathname||Math.random().toString().slice(-6)+"/"+t,headers:{"Content-Type":"application/octet-stream; charset=utf-8","Content-Disposition":"attachment; filename*=UTF-8''"+t}};d.size&&(s.headers["Content-Length"]=d.size);const i=[s,"*",[l.port2]];if(r){const t=a==="iframe"?void 0:{transform(e,t){if(!(e instanceof Uint8Array))throw new TypeError("Can only write Uint8Arrays");p+=e.length,t.enqueue(e),h&&(location.href=h,h=null)},flush(){h&&(location.href=h)}};m=new n.TransformStream(t,d.writableStrategy,d.readableStrategy);const e=m.readable;l.port1.postMessage({readableStream:e},[e])}l.port1.onmessage=t=>{t.data.download?a==="navigate"?(e.remove(),e=null,p?location.href=t.data.download:h=t.data.download):(e.isPopup&&(e.remove(),e=null,a==="iframe"&&o(n.mitm)),o(t.data.download)):t.data.abort&&(f=[],l.port1.postMessage("abort"),l.port1.onmessage=null,l.port1.close(),l.port2.close(),l=null)},e.loaded?e.postMessage(...i):e.addEventListener("load",()=>{e.postMessage(...i)},{once:!0})}let f=[];return!s&&m&&m.writable||new n.WritableStream({write(e){if(!(e instanceof Uint8Array))throw new TypeError("Can only write Uint8Arrays");if(s){f.push(e);return}l.port1.postMessage(e),p+=e.length,h&&(location.href=h,h=null)},close(){if(s){const n=new Blob(f,{type:"application/octet-stream; charset=utf-8"}),e=document.createElement("a");e.href=URL.createObjectURL(n),e.download=t,e.click()}else l.port1.postMessage("end")},abort(){f=[],l.port1.postMessage("abort"),l.port1.onmessage=null,l.port1.close(),l.port2.close(),l=null}},d.writableStrategy)}return n})
/*! streamsaver. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */((e,t)=>{typeof module!="undefined"?module.exports=t():typeof define=="function"&&typeof define.amd=="object"?define(t):this[e]=t()})("streamSaver",()=>{"use strict";const t=typeof window=="object"?window:this;t.HTMLElement||console.warn("streamsaver is meant to run on browsers main thread");let e=null,r=!1;const l=e=>{try{e()}catch{}},c=t.WebStreamsPolyfill||{},i=t.isSecureContext;let s=/constructor/i.test(t.HTMLElement)||!!t.safari||!!t.WebKitPoint;const a=i||"MozAppearance"in document.documentElement.style?"iframe":"navigate",n={createWriteStream:h,WritableStream:t.WritableStream||c.WritableStream,supported:!0,version:{full:"2.0.5",major:2,minor:0,dot:5},mitm:"https://jimmywarting.github.io/StreamSaver.js/mitm.html?version=2.0.0"};function o(e){if(!e)throw new Error("meh");const t=document.createElement("iframe");return t.hidden=!0,t.src=e,t.loaded=!1,t.name="iframe",t.isIframe=!0,t.postMessage=(...e)=>t.contentWindow.postMessage(...e),t.addEventListener("load",()=>{t.loaded=!0},{once:!0}),document.body.appendChild(t),t}function d(e){const i="width=200,height=100",s=document.createDocumentFragment(),n={frame:t.open(e,"popup",i),loaded:!1,isIframe:!1,isPopup:!0,remove(){n.frame.close()},addEventListener(...e){s.addEventListener(...e)},dispatchEvent(...e){s.dispatchEvent(...e)},removeEventListener(...e){s.removeEventListener(...e)},postMessage(...e){n.frame.postMessage(...e)}},o=e=>{e.source===n.frame&&(n.loaded=!0,t.removeEventListener("message",o),n.dispatchEvent(new Event("load")))};return t.addEventListener("message",o),n}try{new Response(new ReadableStream),i&&!("serviceWorker"in navigator)&&(s=!0)}catch{s=!0}l(()=>{const{readable:t}=new TransformStream,e=new MessageChannel;e.port1.postMessage(t,[t]),e.port1.close(),e.port2.close(),r=!0,Object.defineProperty(n,"TransformStream",{configurable:!1,writable:!1,value:TransformStream})});function u(){e||(e=i?o(n.mitm):d(n.mitm))}function h(t,i,c){let d={size:null,pathname:null,writableStrategy:0[0],readableStrategy:0[0]},p=0,h=null,l=null,m=null;if(Number.isFinite(i)?([c,i]=[i,c],console.warn("[StreamSaver] Deprecated pass an object as 2nd argument when creating a write stream"),d.size=c,d.writableStrategy=i):i&&i.highWaterMark?(console.warn("[StreamSaver] Deprecated pass an object as 2nd argument when creating a write stream"),d.size=c,d.writableStrategy=i):d=i||{},!s){u(),l=new MessageChannel,t=encodeURIComponent(t.replace(/\//g,":")).replace(/['()]/g,escape).replace(/\*/g,"%2A");const s={transferringReadable:r,pathname:d.pathname||Math.random().toString().slice(-6)+"/"+t,headers:{"Content-Type":"application/octet-stream; charset=utf-8","Content-Disposition":"attachment; filename*=UTF-8''"+t}};d.size&&(s.headers["Content-Length"]=d.size);const i=[s,"*",[l.port2]];if(r){const t=a==="iframe"?0[0]:{transform(e,t){if(!(e instanceof Uint8Array))throw new TypeError("Can only write Uint8Arrays");p+=e.length,t.enqueue(e),h&&(location.href=h,h=null)},flush(){h&&(location.href=h)}};m=new n.TransformStream(t,d.writableStrategy,d.readableStrategy);const e=m.readable;l.port1.postMessage({readableStream:e},[e])}l.port1.onmessage=t=>{t.data.download?a==="navigate"?(e.remove(),e=null,p?location.href=t.data.download:h=t.data.download):(e.isPopup&&(e.remove(),e=null,a==="iframe"&&o(n.mitm)),o(t.data.download)):t.data.abort&&(f=[],l.port1.postMessage("abort"),l.port1.onmessage=null,l.port1.close(),l.port2.close(),l=null)},e.loaded?e.postMessage(...i):e.addEventListener("load",()=>{e.postMessage(...i)},{once:!0})}let f=[];return!s&&m&&m.writable||new n.WritableStream({write(e){if(!(e instanceof Uint8Array))throw new TypeError("Can only write Uint8Arrays");if(s){f.push(e);return}l.port1.postMessage(e),p+=e.length,h&&(location.href=h,h=null)},close(){if(s){const n=new Blob(f,{type:"application/octet-stream; charset=utf-8"}),e=document.createElement("a");e.href=URL.createObjectURL(n),e.download=t,e.click()}else l.port1.postMessage("end")},abort(){f=[],l.port1.postMessage("abort"),l.port1.onmessage=null,l.port1.close(),l.port2.close(),l=null}},d.writableStrategy)}return n})

View File

@@ -4,6 +4,7 @@
<script src="./js/min/end2end_admin.min.{{ template "js_e2eversion"}}.js"></script>
<script src="./js/min/streamsaver.min.js"></script>
<script src="./js/min/wasm_exec.min.js"></script>
<script src="./js/min/admin.min.{{ template "js_admin_version"}}.js"></script>
<script>
document.title = "Gokapi - E2E Setup";
</script>
@@ -73,6 +74,7 @@
}
loadWasm(newKey);
var systemKey = "{{.SystemKey}}";
</script>
@@ -121,6 +123,7 @@
});
var systemKey = "{{.SystemKey}}";
</script>
{{ end }}

View File

@@ -1,9 +1,9 @@
// File contains auto-generated values. Do not change manually
{{define "version"}}2.0.1{{end}}
{{define "version"}}2.1.0-dev{{end}}
// Specifies the version of JS files, so that the browser doesn't
// use a cached version, if the file has been updated
{{define "js_admin_version"}}11{{end}}
{{define "js_admin_version"}}12{{end}}
{{define "js_dropzone_version"}}5{{end}}
{{define "js_e2eversion"}}6{{end}}
{{define "js_e2eversion"}}7{{end}}
{{define "css_main"}}5{{end}}

View File

@@ -16,6 +16,14 @@ build :
@echo
go generate ./...
CGO_ENABLED=0 go build $(BUILD_FLAGS) -o ./gokapi $(GOPACKAGE)/cmd/gokapi
.PHONY: build-cli
# Build Gokapi-CLI binary
build-cli :
@echo "Building CLI binary..."
@echo
go generate ./...
CGO_ENABLED=0 go build $(BUILD_FLAGS) -o ./gokapi-cli $(GOPACKAGE)/cmd/cli-uploader
.PHONY: build-debug
# Build Gokapi binary

View File

@@ -17,6 +17,9 @@
},
],
"tags": [
{
"name": "info"
},
{
"name": "files"
},
@@ -34,6 +37,67 @@
}
],
"paths": {
"/info/version": {
"get": {
"tags": [
"info"
],
"summary": "Outputs Gokapi version",
"description": "This API call returns the Gokapi server version in a readable format and as an integer.",
"operationId": "version",
"responses": {
"200": {
"description": "Operation successful",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/VersionInfo"
}
}
}
}
},
"401": {
"description": "Invalid API key provided for authentication"
}
}
}
},
"/info/config": {
"get": {
"tags": [
"info"
],
"summary": "Outputs Gokapi upload parameters",
"description": "This API call returns the Gokapi server upload parameters. All filesizes are in unit Megabyte. Requires API permission UPLOAD",
"operationId": "uploadconfig",
"security": [
{
"apikey": ["UPLOAD"]
},
],
"responses": {
"200": {
"description": "Operation successful",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ConfigInfo"
}
}
}
}
},
"401": {
"description": "Invalid API key provided for authentication or API key does not have the required permission"
}
}
}
},
"/files/list": {
"get": {
"tags": [
@@ -125,7 +189,7 @@
"chunk"
],
"summary": "Uploads a new chunk",
"description": "Uploads a file in chunks, in case a reverse proxy does not support upload of larger files. Parallel uploading is supported. Must call /chunk/complete after all chunks have been uploaded. WARNING: Does not support end-to-end encryption! If server is setup to utilise end-to-end encryption, file will be stored in plain-text! Requires API permission UPLOAD",
"description": "Uploads a file in chunks, in case a reverse proxy does not support upload of larger files. Parallel uploading is supported. Must call /chunk/complete after all chunks have been uploaded. WARNING: Does not support end-to-end encryption! If server is setup to utilise end-to-end encryption, file will be stored in plain-text! To upload an end-to-end encrypted file, use gokapi-cli. Requires API permission UPLOAD",
"operationId": "chunkadd",
"security": [
{
@@ -303,7 +367,7 @@
"files"
],
"summary": "Adds a new file without chunking",
"description": "Uploads the submitted file to Gokapi. Please note: This method does not use chunking, therefore if you are behind a reverse proxy or have a provider that limits upload filesizes, this might not work for bigger files (e.g. Cloudflare). WARNING: Does not support end-to-end encryption! If server is setup to utilise end-to-end encryption, file will be stored in plain-text! Requires API permission UPLOAD",
"description": "Uploads the submitted file to Gokapi. Please note: This method does not use chunking, therefore if you are behind a reverse proxy or have a provider that limits upload filesizes, this might not work for bigger files (e.g. Cloudflare). WARNING: Does not support end-to-end encryption! If server is setup to utilise end-to-end encryption, file will be stored in plain-text! To upload an end-to-end encrypted file, use gokapi-cli. Requires API permission UPLOAD",
"operationId": "add",
"security": [
{
@@ -1312,6 +1376,38 @@
"description": "NewUser is the struct used for the result after creating a new API key",
"x-go-package": "Gokapi/internal/models"
},
"VersionInfo": {
"type": "object",
"properties": {
"Version": {
"type": "string",
"example": "2.1.0"
},
"VersionInt": {
"type": "integer",
"example": 20100
},
"EndToEndEncryptionEnabled": {
"type": "boolean",
"example": false
}
},
"description": "VersionInfo is the struct used for returning version numbers",
},
"ConfigInfo": {
"type": "object",
"properties": {
"MaxFilesize": {
"type": "integer",
"example": "100"
},
"MaxChunksize": {
"type": "integer",
"example": 40
}
},
"description": "ConfigInfo is the struct used for returning configuration data",
},
"PasswordReset": {
"type": "object",
"properties": {
@@ -1407,41 +1503,6 @@
"description": "The chunk's offset starting at the beginning of the file"
}
}
},"chunkingcomplete": {
"required": [
"uuid","filename","filesize"
],
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "The unique ID that was used for the uploaded chunks"
},
"filename": {
"type": "string",
"description": "The filename of the uploaded file"
},
"filesize": {
"type": "integer",
"description": "The total filesize of the uploaded file in bytes"
},
"contenttype": {
"type": "string",
"description": "The MIME content type. If empty, application/octet-stream will be used."
},
"allowedDownloads": {
"type": "integer",
"description": "How many downloads are allowed. Default of 1 will be used if empty. Unlimited if 0 is passed."
},
"expiryDays": {
"type": "integer",
"description": "How many days the file will be stored. Default of 14 will be used if empty. Unlimited if 0 is passed."
},
"password": {
"type": "string",
"description": "Password for this file to be set. No password will be used if empty"
}
}
}
},
"securitySchemes": {