add download, fix empty apicontext, add url to describe

This commit is contained in:
2023-03-17 13:04:52 +01:00
parent 11802a56e9
commit f5e0284c40
10 changed files with 145 additions and 6 deletions

49
upctl/cmd/download.go Normal file
View File

@@ -0,0 +1,49 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
package cmd
import (
"errors"
"github.com/spf13/cobra"
"github.com/tlinden/up/upctl/cfg"
"github.com/tlinden/up/upctl/lib"
)
func DownloadCommand(conf *cfg.Config) *cobra.Command {
var listCmd = &cobra.Command{
Use: "download [options] upload-id",
Long: "Download the file associated with an upload object.",
Short: `Download a file.`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("No id specified to delete!")
}
// errors at this stage do not cause the usage to be shown
cmd.SilenceUsage = true
return lib.Download(conf, args)
},
}
listCmd.Aliases = append(listCmd.Aliases, "down")
listCmd.Aliases = append(listCmd.Aliases, "get")
listCmd.Aliases = append(listCmd.Aliases, "g")
listCmd.Aliases = append(listCmd.Aliases, "fetch")
return listCmd
}

View File

@@ -90,6 +90,7 @@ func Execute() {
rootCmd.AddCommand(ListCommand(&conf))
rootCmd.AddCommand(DeleteCommand(&conf))
rootCmd.AddCommand(DescribeCommand(&conf))
rootCmd.AddCommand(DownloadCommand(&conf))
err := rootCmd.Execute()
if err != nil {

View File

@@ -10,6 +10,7 @@ require (
)
require (
github.com/alecthomas/repr v0.2.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/golang/mock v1.6.0 // indirect

View File

@@ -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/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
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=

View File

@@ -21,11 +21,14 @@ import (
"encoding/json"
"errors"
"fmt"
//"github.com/alecthomas/repr"
"github.com/imroc/req/v3"
"github.com/schollz/progressbar/v3"
"github.com/tlinden/up/upctl/cfg"
"mime"
"os"
"path/filepath"
"regexp"
"time"
)
@@ -51,6 +54,7 @@ type Upload struct {
Members []string `json:"members"` // contains multiple files, so File is an archive
Uploaded Timestamp `json:"uploaded"`
Context string `json:"context"`
Url string `json:"url"`
}
type Uploads struct {
@@ -147,8 +151,6 @@ func UploadFiles(c *cfg.Config, args []string) error {
SetUploadCallbackWithInterval(func(info req.UploadInfo) {
left = float64(info.UploadedSize) / float64(info.FileSize) * 100.0
bar.Add(int(left))
//fmt.Printf("\r%q uploaded %.2f%%", info.FileName, float64(info.UploadedSize)/float64(info.FileSize)*100.0)
//fmt.Println()
}, 10*time.Millisecond).
Post(rq.Url)
@@ -242,3 +244,72 @@ func Describe(c *cfg.Config, args []string) error {
return RespondExtended(resp)
}
func Download(c *cfg.Config, args []string) error {
id := args[0]
// progres bar
bar := progressbar.Default(100)
callback := func(info req.DownloadInfo) {
if info.Response.Response != nil {
bar.Add(1)
}
}
rq := Setup(c, "/file/"+id+"/")
resp, err := rq.R.
SetOutputFile(id).
SetDownloadCallback(callback).
Get(rq.Url)
if err != nil {
return err
}
_, params, err := mime.ParseMediaType(resp.Header.Get("Content-Disposition"))
if err != nil {
os.Remove(id)
return err
}
filename := params["filename"]
if filename == "" {
os.Remove(id)
return fmt.Errorf("No filename provided!")
}
cleanfilename, _ := Untaint(filename, regexp.MustCompile(`[^a-zA-Z0-9\-\._]`))
if err := os.Rename(id, cleanfilename); err != nil {
os.Remove(id)
return fmt.Errorf("\nUnable to rename file: " + err.Error())
}
fmt.Printf("%s successfully downloaded to file %s.", id, cleanfilename)
return nil
}
/*
Untaint user input, that is: remove all non supported chars.
wanted is a regexp matching chars we shall leave. Everything else
will be removed. Eg:
untainted := Untaint(input, `[^a-zA-Z0-9\-]`)
Returns a new string and an error if the input string has been
modified. It's the callers choice to decide what to do about
it. You may ignore the error and use the untainted string or bail
out.
*/
func Untaint(input string, wanted *regexp.Regexp) (string, error) {
untainted := wanted.ReplaceAllString(input, "")
if len(untainted) != len(input) {
return untainted, errors.New("Invalid input string!")
}
return untainted, nil
}

View File

@@ -73,6 +73,7 @@ func WriteExtended(uploads *Uploads) {
fmt.Printf(format, "Context", entry.Context)
fmt.Printf(format, "Uploaded", entry.Uploaded)
fmt.Printf(format, "Filename", entry.File)
fmt.Printf(format, "Url", entry.Url)
fmt.Println()
}
}

View File

@@ -49,6 +49,7 @@ type Upload struct {
Members []string `json:"members"` // contains multiple files, so File is an archive
Uploaded Timestamp `json:"uploaded"`
Context string `json:"context"`
Url string `json:"url"`
}
// this one is also used for marshalling to the client
@@ -170,5 +171,10 @@ func GetApicontext(c *fiber.Ctx) (string, error) {
return "", fmt.Errorf("Unable to initialize session store from context: " + err.Error())
}
return sess.Get("apicontext").(string), nil
apicontext := sess.Get("apicontext")
if apicontext != nil {
return apicontext.(string), nil
}
return "", nil
}

View File

@@ -25,6 +25,7 @@ import (
"os"
"path/filepath"
"strings"
"time"
)
@@ -260,6 +261,10 @@ func Describe(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
"No upload with that id could be found!")
}
for _, upload := range uploads.Entries {
upload.Url = strings.Join([]string{cfg.Url, "download", id, upload.File}, "/")
}
// if we reached this point we can signal success
uploads.Success = true
uploads.Code = fiber.StatusOK

View File

@@ -54,28 +54,31 @@ func Runserver(conf *cfg.Config, args []string) error {
// authenticated routes
api := router.Group(conf.ApiPrefix + ApiVersion)
{
// authenticated routes
// upload
api.Post("/file/", auth, func(c *fiber.Ctx) error {
return FilePut(c, conf, db)
})
// download w/o expire
api.Get("/file/:id/:file", auth, func(c *fiber.Ctx) error {
return FileGet(c, conf, db)
})
api.Get("/file/:id/", auth, func(c *fiber.Ctx) error {
return FileGet(c, conf, db)
})
// remove
api.Delete("/file/:id/", auth, func(c *fiber.Ctx) error {
err := DeleteUpload(c, conf, db)
return SendResponse(c, "", err)
})
// listing
api.Get("/list/", auth, func(c *fiber.Ctx) error {
return List(c, conf, db)
})
// info
api.Get("/upload/:id/", auth, func(c *fiber.Ctx) error {
return Describe(c, conf, db)
})

View File

@@ -13,7 +13,7 @@ apicontext = [
}
]
url = "https://sokrates.daemon.de"
#url = "https://sokrates.daemon.de"
# this is the root context with all permissions
super = "root"