mirror of
https://github.com/Forceu/Gokapi.git
synced 2026-01-06 08:59:37 -06:00
Added a CLI tool for uploading that also supports e2e (#280)
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -14,3 +14,5 @@ internal/webserver/web/static/js/min/wasm_exec.min.js
|
||||
custom/
|
||||
.env
|
||||
*.secret
|
||||
gokapi-cli
|
||||
gokapi-cli.json
|
||||
|
||||
@@ -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-*
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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%
|
||||
`
|
||||
|
||||
@@ -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 (
|
||||
|
||||
41
build/go.sum
41
build/go.sum
@@ -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
54
cmd/cli-uploader/Main.go
Normal 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")
|
||||
}
|
||||
378
cmd/cli-uploader/cliapi/cliapi.go
Normal file
378
cmd/cli-uploader/cliapi/cliapi.go
Normal 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
|
||||
}
|
||||
148
cmd/cli-uploader/cliconfig/cliconfig.go
Normal file
148
cmd/cli-uploader/cliconfig/cliconfig.go
Normal 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())
|
||||
}
|
||||
120
cmd/cli-uploader/cliflags/cliflags.go
Normal file
120
cmd/cli-uploader/cliflags/cliflags.go
Normal 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)
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
|
||||
@@ -236,7 +236,6 @@ This option disables Gokapis internal authentication completely, except for API
|
||||
- ``/admin``
|
||||
- ``/apiKeys``
|
||||
- ``/changePassword``
|
||||
- ``/e2eInfo``
|
||||
- ``/e2eSetup``
|
||||
- ``/logs``
|
||||
- ``/uploadChunk``
|
||||
|
||||
9
go.mod
9
go.mod
@@ -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
124
go.sum
@@ -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=
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
5
internal/webserver/api/VersionNumbers.go
Normal file
5
internal/webserver/api/VersionNumbers.go
Normal file
@@ -0,0 +1,5 @@
|
||||
// Code generated by updateApiRouting.go - DO NOT EDIT.
|
||||
package api
|
||||
|
||||
const versionReadable = "2.1.0-dev"
|
||||
const versionInt = 20100
|
||||
@@ -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: ¶mLogsDelete{},
|
||||
},
|
||||
{
|
||||
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: ¶mE2eStore{},
|
||||
},
|
||||
}
|
||||
|
||||
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,
|
||||
|
||||
@@ -636,6 +636,17 @@ func (p *paramUserResetPw) New() requestParser {
|
||||
return ¶mUserResetPw{}
|
||||
}
|
||||
|
||||
// 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 ¶mE2eStore{}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
// go generate ./...
|
||||
|
||||
|
||||
var clipboard = new ClipboardJS('.copyurl');
|
||||
try {
|
||||
var clipboard = new ClipboardJS('.copyurl');
|
||||
} catch (ignored) {
|
||||
}
|
||||
|
||||
var toastId;
|
||||
|
||||
|
||||
@@ -237,7 +237,6 @@ function dropzoneGetFile(uid) {
|
||||
}
|
||||
|
||||
function requestFileInfo(fileId, uid) {
|
||||
|
||||
apiFilesListById(fileId)
|
||||
.then(data => {
|
||||
addRow(data);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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)})}
|
||||
@@ -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)})}
|
||||
@@ -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)})}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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})
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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}}
|
||||
8
makefile
8
makefile
@@ -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
|
||||
|
||||
135
openapi.json
135
openapi.json
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user