diff --git a/README.md b/README.md index a9c63d3..9a9023c 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,10 @@ Simple standalone file upload server with api and cli that is: do remove the file when it expires but not the associated db entry. - also serve a html upload page -- add api version in path - add auth options (access key, users, roles, oauth2) - add metrics - add upctl command to remove a file - upd: add short uuid to files, in case multiple files with the same name are being uploaded +- use global map of api endpoints like /file/get/ etc +- use separate group for /file/ diff --git a/upctl/lib/client.go b/upctl/lib/client.go index c05bb9e..b0bf3df 100644 --- a/upctl/lib/client.go +++ b/upctl/lib/client.go @@ -34,6 +34,8 @@ type Response struct { Message string `json:"message"` } +const ApiVersion string = "/v1" + func Runclient(c *cfg.Config, args []string) error { client := req.C() if c.Debug { @@ -42,7 +44,7 @@ func Runclient(c *cfg.Config, args []string) error { client.SetUserAgent("upctl-" + cfg.VERSION) - url := c.Endpoint + "/putfile" + url := c.Endpoint + ApiVersion + "/file/put" rq := client.R() for _, file := range args { diff --git a/upd/api/api.go b/upd/api/api.go new file mode 100644 index 0000000..029907e --- /dev/null +++ b/upd/api/api.go @@ -0,0 +1,63 @@ +/* +Copyright © 2023 Thomas von Dein + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package api + +import ( + "fmt" + "regexp" + "time" +) + +const ApiVersion string = "/v1" + +// used to return to the api client +type Result struct { + Success bool + Message string + Code int +} + +// Binding from JSON, data coming from user, not tainted +type Meta struct { + Expire string `json:"expire" form:"expire"` +} + +// stores 1 upload object, gets into db +type Upload struct { + Id string `json:"id"` + Expire string `json:"expire"` + File string `json:"file"` // final filename (visible to the downloader) + Members []string `json:"members"` // contains multiple files, so File is an archive + Uploaded time.Time `json:"uploaded"` +} + +// vaious helbers +func Log(format string, values ...any) { + fmt.Printf("[DEBUG] "+format+"\n", values...) +} + +func Ts() string { + t := time.Now() + return t.Format("2006-01-02-15-04-") +} + +func NormalizeFilename(file string) string { + r := regexp.MustCompile(`[^\w\d\-_\\.]`) + + return Ts() + r.ReplaceAllString(file, "") +} diff --git a/upd/api/fileio.go b/upd/api/fileio.go new file mode 100644 index 0000000..b3eb1a5 --- /dev/null +++ b/upd/api/fileio.go @@ -0,0 +1,114 @@ +/* +Copyright © 2023 Thomas von Dein + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package api + +import ( + "archive/zip" + "io" + "os" + "path/filepath" + "sync" +) + +// cleanup an upload directory, either because we got an error in the +// middle of an upload or something else went wrong. we fork off a go +// routine because this may block. +func cleanup(dir string) { + go func() { + err := os.RemoveAll(dir) + if err != nil { + Log("Failed to remove dir %s: %s", dir, err.Error()) + } + }() +} + +func ZipSource(source, target string) error { + // 1. Create a ZIP file and zip.Writer + // source must be an absolute path, target a zip file + f, err := os.Create(target) + if err != nil { + return err + } + defer f.Close() + + writer := zip.NewWriter(f) + defer writer.Close() + + var wg sync.WaitGroup + wg.Add(1) + + // don't chdir the server itself + go func() { + defer wg.Done() + + os.Chdir(source) + newDir, err := os.Getwd() + if err != nil { + } + //Log("Current Working Direcotry: %s\n, source: %s", newDir, source) + + err = filepath.Walk(".", func(path string, info os.FileInfo, err error) error { + // 2. Go through all the files of the source + if err != nil { + return err + } + + // 3. Create a local file header + header, err := zip.FileInfoHeader(info) + if err != nil { + return err + } + + // set compression + header.Method = zip.Deflate + + // 4. Set relative path of a file as the header name + header.Name = path + //Log("a: <%s>, b: <%s>, rel: <%s>", filepath.Dir(source), path, header.Name) + if err != nil { + return err + } + if info.IsDir() { + header.Name += "/" + } + + // 5. Create writer for the file header and save content of the file + headerWriter, err := writer.CreateHeader(header) + if err != nil { + return err + } + + if info.IsDir() { + return nil + } + + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + + _, err = io.Copy(headerWriter, f) + return err + }) + }() + + wg.Wait() + + return err +} diff --git a/upd/lib/handlers.go b/upd/api/handlers.go similarity index 86% rename from upd/lib/handlers.go rename to upd/api/handlers.go index 951e8b6..ebbf08e 100644 --- a/upd/lib/handlers.go +++ b/upd/api/handlers.go @@ -15,14 +15,15 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package lib +package api import ( //"archive/zip" "fmt" - "github.com/gin-gonic/gin" + //"github.com/gin-gonic/gin" //"github.com/gin-gonic/gin/binding" "encoding/json" + "github.com/gofiber/fiber/v2" "github.com/google/uuid" "github.com/tlinden/up/upd/cfg" bolt "go.etcd.io/bbolt" @@ -35,7 +36,7 @@ import ( "time" ) -func Putfile(c *gin.Context, cfg *cfg.Config, db *bolt.DB) (string, error) { +func Putfile(c *fiber.Ctx, cfg *cfg.Config, db *bolt.DB) (string, error) { // supports upload of multiple files with: // // curl -X POST localhost:8080/putfile \ @@ -57,7 +58,11 @@ func Putfile(c *gin.Context, cfg *cfg.Config, db *bolt.DB) (string, error) { os.MkdirAll(filepath.Join(cfg.StorageDir, id), os.ModePerm) // fetch auxiliary form data - form, _ := c.MultipartForm() + form, err := c.MultipartForm() + if err != nil { + Log("multipart error %s", err.Error()) + return "", err + } entry := &Upload{Id: id, Uploaded: time.Now()} @@ -71,15 +76,17 @@ func Putfile(c *gin.Context, cfg *cfg.Config, db *bolt.DB) (string, error) { entry.Members = append(entry.Members, filename) Log("Received: %s => %s/%s", file.Filename, id, filename) - if err := c.SaveUploadedFile(file, path); err != nil { + if err := c.SaveFile(file, path); err != nil { cleanup(filepath.Join(cfg.StorageDir, id)) return "", err } } - if err := c.ShouldBind(&formdata); err != nil { + if err := c.BodyParser(&formdata); err != nil { + Log("bodyparser error %s", err.Error()) return "", err } + if len(formdata.Expire) == 0 { entry.Expire = "asap" } else { @@ -95,8 +102,9 @@ func Putfile(c *gin.Context, cfg *cfg.Config, db *bolt.DB) (string, error) { finalzip := filepath.Join(cfg.StorageDir, id, zipfile) iddir := filepath.Join(cfg.StorageDir, id) - if err := zipSource(iddir, tmpzip); err != nil { + if err := ZipSource(iddir, tmpzip); err != nil { cleanup(iddir) + Log("zip error") return "", err } @@ -105,7 +113,7 @@ func Putfile(c *gin.Context, cfg *cfg.Config, db *bolt.DB) (string, error) { return "", err } - returnUrl = strings.Join([]string{cfg.Url + cfg.ApiPrefix, "getfile", id, zipfile}, "/") + returnUrl = strings.Join([]string{cfg.Url + cfg.ApiPrefix + ApiVersion, "file/get", id, zipfile}, "/") entry.File = zipfile // clean up after us diff --git a/upd/api/server.go b/upd/api/server.go new file mode 100644 index 0000000..c0d4799 --- /dev/null +++ b/upd/api/server.go @@ -0,0 +1,93 @@ +/* +Copyright © 2023 Thomas von Dein + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package api + +import ( + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v2/middleware/requestid" + "github.com/tlinden/up/upd/cfg" + bolt "go.etcd.io/bbolt" + "os" + "path/filepath" + "time" +) + +func Runserver(cfg *cfg.Config, args []string) error { + dst := cfg.StorageDir + router := fiber.New() + router.Use(requestid.New()) + router.Use(logger.New(logger.Config{ + // For more options, see the Config section + Format: "${pid} ${locals:requestid} ${status} - ${method} ${path}​\n", + })) + api := router.Group(cfg.ApiPrefix + ApiVersion) + + db, err := bolt.Open(cfg.DbFile, 0600, nil) + if err != nil { + return err + } + defer db.Close() + + { + api.Post("/file/put", func(c *fiber.Ctx) error { + uri, err := Putfile(c, cfg, db) + + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(Result{ + Code: fiber.StatusBadRequest, + Message: err.Error(), + Success: false, + }) + } else { + return c.Status(fiber.StatusOK).JSON(Result{ + Code: fiber.StatusOK, + Message: uri, + Success: true, + }) + } + }) + + api.Get("/file/get/:id/:file", func(c *fiber.Ctx) error { + // deliver a file and delete it after a delay (FIXME: check + // when gin has done delivering it?). Redirect to the static + // handler for actual delivery. + id := c.Params("id") + file := c.Params("file") + + filename := filepath.Join(dst, id, file) + + if _, err := os.Stat(filename); err == nil { + go func() { + time.Sleep(500 * time.Millisecond) + cleanup(filepath.Join(dst, id)) + }() + } + + return c.Download(filename, file) + }) + } + + router.Get("/", func(c *fiber.Ctx) error { + return c.Send([]byte("welcome to upload api, use /api enpoint!")) + }) + + router.Listen(cfg.Listen) + + return nil +} diff --git a/upd/cmd/root.go b/upd/cmd/root.go index 033a13a..2913e88 100644 --- a/upd/cmd/root.go +++ b/upd/cmd/root.go @@ -22,8 +22,8 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" + "github.com/tlinden/up/upd/api" "github.com/tlinden/up/upd/cfg" - "github.com/tlinden/up/upd/lib" "os" "strings" ) @@ -71,7 +71,7 @@ func Execute() { conf.ApplyDefaults() // actual execution starts here - return lib.Runserver(&conf, args) + return api.Runserver(&conf, args) }, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { return initConfig(cmd) diff --git a/upd/go.mod b/upd/go.mod index 5b05b91..69170e0 100644 --- a/upd/go.mod +++ b/upd/go.mod @@ -11,27 +11,40 @@ require ( ) require ( + github.com/andybalholm/brotli v1.0.4 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/validator/v10 v10.11.1 // indirect github.com/goccy/go-json v0.9.11 // indirect + github.com/gofiber/fiber/v2 v2.42.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.15.9 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/philhofer/fwd v1.1.1 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect + github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect github.com/spf13/afero v1.9.3 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect + github.com/tinylib/msgp v1.1.6 // indirect github.com/ugorji/go/codec v1.2.7 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.44.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect go.etcd.io/bbolt v1.3.7 // indirect golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect golang.org/x/net v0.4.0 // indirect diff --git a/upd/go.sum b/upd/go.sum index 8af145c..9252124 100644 --- a/upd/go.sum +++ b/upd/go.sum @@ -38,6 +38,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -77,6 +79,8 @@ github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJ github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofiber/fiber/v2 v2.42.0 h1:Fnp7ybWvS+sjNQsFvkhf4G8OhXswvB6Vee8hM/LyS+8= +github.com/gofiber/fiber/v2 v2.42.0/go.mod h1:3+SGNjqMh5VQH5Vz2Wdi43zTIV16ktlFd3x3R6O1Zlc= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -150,6 +154,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -163,8 +169,14 @@ github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -174,17 +186,25 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ= +github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4= +github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8= +github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d h1:Q+gqLBOPkFGHyCJxXMRqtUgUbTjI8/Ze8vu8GGyNFwo= +github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= @@ -212,9 +232,17 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw= +github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.44.0 h1:R+gLUhldIsfg1HokMuQjdQ5bh9nuXHPIfvkYUu9eR5Q= +github.com/valyala/fasthttp v1.44.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -235,6 +263,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -302,6 +331,7 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -359,6 +389,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= @@ -366,6 +397,7 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -420,6 +452,7 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= diff --git a/upd/lib/server.go b/upd/lib/server.go deleted file mode 100644 index 44945b8..0000000 --- a/upd/lib/server.go +++ /dev/null @@ -1,199 +0,0 @@ -/* -Copyright © 2023 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package lib - -import ( - "archive/zip" - "fmt" - "github.com/gin-gonic/gin" - //"github.com/gin-gonic/gin/binding" - //"encoding/json" - //"github.com/google/uuid" - "github.com/tlinden/up/upd/cfg" - bolt "go.etcd.io/bbolt" - "io" - "net/http" - "os" - "path/filepath" - "regexp" - //"strings" - "time" -) - -type Result struct { - success bool - url string - error string -} - -// Binding from JSON, data coming from user, not tainted -type Meta struct { - Expire string `form:"expire"` -} - -// stores 1 upload object, gets into db -type Upload struct { - Id string `json:"id"` - Expire string `json:"expire"` - File string `json:"file"` // final filename (visible to the downloader) - Members []string `json:"members"` // contains multiple files, so File is an archive - Uploaded time.Time `json:"uploaded"` -} - -func Log(format string, values ...any) { - fmt.Fprintf(gin.DefaultWriter, "[GIN] "+format+"\n", values...) -} - -func Ts() string { - t := time.Now() - return t.Format("2006-01-02-15-04-") -} - -func NormalizeFilename(file string) string { - r := regexp.MustCompile(`[^\w\d\-_\\.]`) - - return Ts() + r.ReplaceAllString(file, "") -} - -func Runserver(cfg *cfg.Config, args []string) error { - dst := cfg.StorageDir - router := gin.Default() - router.SetTrustedProxies(nil) - api := router.Group(cfg.ApiPrefix) - - db, err := bolt.Open(cfg.DbFile, 0600, nil) - if err != nil { - return err - } - defer db.Close() - - { - api.POST("/putfile", func(c *gin.Context) { - uri, err := Putfile(c, cfg, db) - - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - } else { - c.JSON(http.StatusOK, gin.H{ - "code": http.StatusOK, - "message": uri, - "success": true, - }) - } - }) - - api.GET("/getfile/:id/:file", func(c *gin.Context) { - // deliver a file and delete it after a delay (FIXME: check - // when gin has done delivering it?). Redirect to the static - // handler for actual delivery. - id := c.Param("id") - file := c.Param("file") - c.Request.URL.Path = cfg.ApiPrefix + "/static/" + id + "/" + file - filename := filepath.Join(dst, id, file) - - if _, err := os.Stat(filename); err == nil { - go func() { - time.Sleep(500 * time.Millisecond) - cleanup(filepath.Join(dst, id)) - }() - } - - router.HandleContext(c) - }) - - // actual delivery of static files, uri's must be known to the - // user, mostly being redirected here internally from /f - api.Static("/static", dst) - } - - router.GET("/", func(c *gin.Context) { - c.String(http.StatusOK, "welcome to upload api, use /api enpoint!") - }) - - router.Run(cfg.Listen) - - return nil -} - -// cleanup an upload directory, either because we got an error in the -// middle of an upload or something else went wrong. we fork off a go -// routine because this may block. -func cleanup(dir string) { - go func() { - err := os.RemoveAll(dir) - if err != nil { - Log("Failed to remove dir %s: %s", dir, err.Error()) - } - }() -} - -func zipSource(source, target string) error { - // 1. Create a ZIP file and zip.Writer - f, err := os.Create(target) - if err != nil { - return err - } - defer f.Close() - - writer := zip.NewWriter(f) - defer writer.Close() - - // 2. Go through all the files of the source - return filepath.Walk(source, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - // 3. Create a local file header - header, err := zip.FileInfoHeader(info) - if err != nil { - return err - } - - // set compression - header.Method = zip.Deflate - - // 4. Set relative path of a file as the header name - header.Name, err = filepath.Rel(filepath.Dir(source), path) - if err != nil { - return err - } - if info.IsDir() { - header.Name += "/" - } - - // 5. Create writer for the file header and save content of the file - headerWriter, err := writer.CreateHeader(header) - if err != nil { - return err - } - - if info.IsDir() { - return nil - } - - f, err := os.Open(path) - if err != nil { - return err - } - defer f.Close() - - _, err = io.Copy(headerWriter, f) - return err - }) -} diff --git a/upd/testzip.go b/upd/testzip.go new file mode 100644 index 0000000..d7c61d4 --- /dev/null +++ b/upd/testzip.go @@ -0,0 +1,18 @@ +package main + +import ( + "github.com/tlinden/up/upd/api" + "log" + "os" +) + +func main() { + if len(os.Args) > 2 { + dir, err := os.Getwd() + if err != nil { + } + if err := api.ZipSource(dir+"/"+os.Args[1], os.Args[2]); err != nil { + log.Fatal(err) + } + } +}