From 923d9353ade5b647b611590627f517b9621faf10 Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Wed, 29 Mar 2023 13:55:06 +0200 Subject: [PATCH 1/9] added workflows --- .github/ISSUE_TEMPLATE/bug_report.md | 31 ++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 23 +++++++++++++ .github/workflows/ci.yaml | 40 +++++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/workflows/ci.yaml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..e914bfc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[bug-report]" +labels: bug +assignees: TLINDEN + +--- + +**Describtion** + + + +**Steps To Reproduce** + + + +**Expected behavior** + + + +**Version information** + + + +**Additional informations** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..8722b18 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,23 @@ +--- +name: Feature request +about: Suggest a feature +title: "[feature-request]" +labels: feature-request +assignees: TLINDEN + +--- + +**Describtion** + + + + +**Version information** + + diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..2486ea7 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,40 @@ +name: build-and-test-tablizer +on: [push, pull_request] +jobs: + build: + strategy: + matrix: + version: [1.18, 1.19] + os: [ubuntu-latest, windows-latest, macos-latest] + name: Build + runs-on: ${{ matrix.os }} + steps: + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.version }} + id: go + + - name: checkout + uses: actions/checkout@v3 + + - name: build + run: make + + - name: test ephemerup + run: make test + + - name: test upctl + run: make -C upctl test + + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.version }} + - uses: actions/checkout@v3 + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + From 25bdd339ecd03a5c0ce84dfbf46255d090039af6 Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Wed, 29 Mar 2023 13:56:26 +0200 Subject: [PATCH 2/9] fix test name --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2486ea7..99533c0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,4 +1,4 @@ -name: build-and-test-tablizer +name: build-and-test-ephemerup on: [push, pull_request] jobs: build: From 0e572f0aa4967ca4a00e232f951d88a19de255f2 Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Wed, 29 Mar 2023 14:08:09 +0200 Subject: [PATCH 3/9] fix ts tests, leave actual time because of TZ errors --- .github/workflows/ci.yaml | 5 +++-- upctl/lib/client_test.go | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 99533c0..e9af937 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -4,8 +4,9 @@ jobs: build: strategy: matrix: - version: [1.18, 1.19] - os: [ubuntu-latest, windows-latest, macos-latest] + version: [1.18] + #os: [ubuntu-latest, windows-latest, macos-latest] + os: [ubuntu-latest] name: Build runs-on: ${{ matrix.os }} steps: diff --git a/upctl/lib/client_test.go b/upctl/lib/client_test.go index b2fc1fb..15de2ed 100644 --- a/upctl/lib/client_test.go +++ b/upctl/lib/client_test.go @@ -205,7 +205,7 @@ func TestList(t *testing.T) { sendjson: listing, files: []string{}, method: "GET", - expect: `cc2c965a\s*asap\s*foo\s*2023-03-21 12:06:54`, // expect tabular output + expect: `cc2c965a\s*asap\s*foo\s*2023-03-21`, // expect tabular output }, { name: "list-catch-empty-json", @@ -266,7 +266,7 @@ func TestDescribe(t *testing.T) { sendjson: listing, files: []string{"cc2c965a"}, method: "GET", - expect: `Created: 2023-03-21 12:06:54.890501888`, + expect: `Created: 2023-03-21`, }, { name: "describe-catch-empty-json", From 50d25fe7b7c57d33a0e8559be366d3a0e6fffd7d Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Wed, 29 Mar 2023 14:59:04 +0200 Subject: [PATCH 4/9] fix error handling --- api/auth.go | 3 +++ api/cleaner.go | 8 +++----- api/db_test.go | 11 +++++++++-- api/fileio.go | 22 +++++++++++++++++++--- api/form_handlers.go | 6 +++++- api/upload_handlers.go | 27 +++++++++++++++++++++------ cfg/config.go | 4 ++-- cmd/root.go | 32 ++++++++++++++++++-------------- ephemerup.hcl | 2 +- upctl/cmd/formcommands.go | 3 +-- upctl/cmd/root.go | 13 ++++++++----- upctl/lib/client.go | 10 ++++++---- upctl/lib/output.go | 2 -- upctl/t/t1 | 1 - 14 files changed, 96 insertions(+), 48 deletions(-) delete mode 100644 upctl/t/t1 diff --git a/api/auth.go b/api/auth.go index 6602be2..afc79c1 100644 --- a/api/auth.go +++ b/api/auth.go @@ -71,6 +71,9 @@ func AuthValidateOnetimeKey(c *fiber.Ctx, key string, db *Db) (bool, error) { } sess, err := Sessionstore.Get(c) + if err != nil { + return false, errors.New("Could not retrieve session from Sessionstore: " + err.Error()) + } // store the result into the session, the 'formid' key tells the // upload handler that the apicontext it sees is in fact a form id diff --git a/api/cleaner.go b/api/cleaner.go index c769a1c..dc97710 100644 --- a/api/cleaner.go +++ b/api/cleaner.go @@ -58,10 +58,6 @@ func DeleteExpiredUploads(conf *cfg.Config, db *Db) error { return err }) - if err != nil { - Log("DB error: %s", err.Error()) - } - return err } @@ -74,7 +70,9 @@ func BackgroundCleaner(conf *cfg.Config, db *Db) chan bool { for { select { case <-ticker.C: - DeleteExpiredUploads(conf, db) + if err := DeleteExpiredUploads(conf, db); err != nil { + Log("Failed to delete eypired uploads: %s", err.Error()) + } case <-done: ticker.Stop() return diff --git a/api/db_test.go b/api/db_test.go index 571a08e..c2dc66f 100644 --- a/api/db_test.go +++ b/api/db_test.go @@ -112,6 +112,10 @@ func TestDboperation(t *testing.T) { if tt.upload.Id != "" { // set ts ts, err := time.Parse(timeformat, tt.ts) + if err != nil { + t.Errorf("Could not parse time: " + err.Error()) + } + tt.upload.Created = common.Timestamp{Time: ts} // create new upload db object @@ -162,7 +166,7 @@ func TestDboperation(t *testing.T) { } // fetch again, shall return empty - response, err = db.Get(tt.context, tt.id, common.TypeUpload) + _, err = db.Get(tt.context, tt.id, common.TypeUpload) if err == nil { t.Errorf("Could fetch upload object again although we deleted it") } @@ -171,6 +175,9 @@ func TestDboperation(t *testing.T) { if tt.form.Id != "" { // set ts ts, err := time.Parse(timeformat, tt.ts) + if err != nil { + t.Errorf("Could not parse time: " + err.Error()) + } tt.form.Created = common.Timestamp{Time: ts} // create new form db object @@ -221,7 +228,7 @@ func TestDboperation(t *testing.T) { } // fetch again, shall return empty - response, err = db.Get(tt.context, tt.id, common.TypeForm) + _, err = db.Get(tt.context, tt.id, common.TypeForm) if err == nil { t.Errorf("Could fetch form object again although we deleted it") } diff --git a/api/fileio.go b/api/fileio.go index 8301e33..685cc71 100644 --- a/api/fileio.go +++ b/api/fileio.go @@ -114,17 +114,23 @@ func ZipDir(directory, zipfilename string) error { var wg sync.WaitGroup wg.Add(1) + failure := make(chan string) + // don't chdir the server itself go func() { defer wg.Done() - os.Chdir(directory) + if err := os.Chdir(directory); err != nil { + failure <- "Failed to changedir: " + err.Error() + return + } newDir, err := os.Getwd() if err != nil { + failure <- "Failed to get cwd: " + err.Error() } + if newDir != directory { - err = errors.New("Failed to changedir!") - return + failure <- "Failed to changedir!" } err = filepath.Walk(".", func(path string, info os.FileInfo, err error) error { @@ -171,9 +177,19 @@ func ZipDir(directory, zipfilename string) error { _, err = io.Copy(headerWriter, f) return err }) + + if err != nil { + failure <- "Failed to zip directory: " + err.Error() + } }() wg.Wait() + goterr := <-failure + + if goterr != "" { + return errors.New(goterr) + } + return err } diff --git a/api/form_handlers.go b/api/form_handlers.go index d55f244..c4f42ba 100644 --- a/api/form_handlers.go +++ b/api/form_handlers.go @@ -82,7 +82,11 @@ func FormCreate(c *fiber.Ctx, cfg *cfg.Config, db *Db) error { Log("Form created with API-Context %s", entry.Context) // we do this in the background to not thwart the server - go db.Insert(id, entry) + go func() { + if err := db.Insert(id, entry); err != nil { + Log("Failed to insert: " + err.Error()) + } + }() // everything went well so far res := &common.Response{Forms: []*common.Form{entry}} diff --git a/api/upload_handlers.go b/api/upload_handlers.go index 46f13d8..e6a5fef 100644 --- a/api/upload_handlers.go +++ b/api/upload_handlers.go @@ -53,7 +53,10 @@ func UploadPost(c *fiber.Ctx, cfg *cfg.Config, db *Db) error { var returnUrl string var formdata Meta - os.MkdirAll(filepath.Join(cfg.StorageDir, id), os.ModePerm) + if err := os.MkdirAll(filepath.Join(cfg.StorageDir, id), os.ModePerm); err != nil { + return JsonStatus(c, fiber.StatusInternalServerError, + "Unable to initialize directories: "+err.Error()) + } // fetch auxiliary form data form, err := c.MultipartForm() @@ -114,7 +117,11 @@ func UploadPost(c *fiber.Ctx, cfg *cfg.Config, db *Db) error { Log("Uploaded with API-Context %s", entry.Context) // we do this in the background to not thwart the server - go db.Insert(id, entry) + go func() { + if err := db.Insert(id, entry); err != nil { + Log("Failed to insert: " + err.Error()) + } + }() // everything went well so far res := &common.Response{Uploads: []*common.Upload{entry}} @@ -131,7 +138,9 @@ func UploadPost(c *fiber.Ctx, cfg *cfg.Config, db *Db) error { if err == nil { if len(r.Forms) == 1 { if r.Forms[0].Expire == "asap" { - db.Delete(apicontext, formid) + if err := db.Delete(apicontext, formid); err != nil { + Log("Failed to delete formid %s: %s", formid, err.Error()) + } } // email notification to form creator @@ -184,7 +193,11 @@ func UploadFetch(c *fiber.Ctx, cfg *cfg.Config, db *Db, shallExpire ...bool) err if _, err := os.Stat(filename); err != nil { // db entry is there, but file isn't (anymore?) - go db.Delete(apicontext, id) + go func() { + if err := db.Delete(apicontext, id); err != nil { + Log("Unable to delete entry id %s: %s", id, err.Error()) + } + }() return fiber.NewError(404, "No download with that id could be found!") } @@ -192,12 +205,14 @@ func UploadFetch(c *fiber.Ctx, cfg *cfg.Config, db *Db, shallExpire ...bool) err err = c.Download(filename, file) if len(shallExpire) > 0 { - if shallExpire[0] == true { + if shallExpire[0] { go func() { // check if we need to delete the file now and do it in the background if upload.Expire == "asap" { cleanup(filepath.Join(cfg.StorageDir, id)) - db.Delete(apicontext, id) + if err := db.Delete(apicontext, id); err != nil { + Log("Unable to delete entry id %s: %s", id, err.Error()) + } } }() } diff --git a/cfg/config.go b/cfg/config.go index dfbf732..3b1b761 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -62,10 +62,10 @@ type Config struct { Network string // only settable via config - Apicontexts []Apicontext `koanf:"apicontext"` + Apicontexts []Apicontext `koanf:"apicontexts"` // smtp settings - Mail Mailsettings `koanf:mail` + Mail Mailsettings `koanf:"mail"` // Internals only RegNormalizedFilename *regexp.Regexp diff --git a/cmd/root.go b/cmd/root.go index 8834071..0f5cd3a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -75,7 +75,9 @@ func Execute() error { f.StringVarP(&conf.AppName, "appname", "n", "ephemerupd "+conf.GetVersion(), "App name to say hi as") f.IntVarP(&conf.BodyLimit, "bodylimit", "b", 10250000000, "Max allowed upload size in bytes") - f.Parse(os.Args[1:]) + if err := f.Parse(os.Args[1:]); err != nil { + return err + } // exclude -6 and -4 if conf.V4only && conf.V6only { @@ -86,19 +88,19 @@ func Execute() error { var k = koanf.New(".") // Load the config files provided in the commandline or the default locations - configfiles := []string{} + var configfiles []string configfile, _ := f.GetString("config") if configfile != "" { configfiles = []string{configfile} } else { configfiles = []string{ - "/etc/ephemerupd.hcl", "/usr/local/etc/ephemerupd.hcl", // unix variants - filepath.Join(os.Getenv("HOME"), ".config", "ephemerupd", "ephemerupd.hcl"), - filepath.Join(os.Getenv("HOME"), ".ephemerupd"), - "ephemerupd.hcl", + "/etc/ephemerup.hcl", "/usr/local/etc/ephemerup.hcl", // unix variants + filepath.Join(os.Getenv("HOME"), ".config", "ephemerup", "ephemerup.hcl"), + filepath.Join(os.Getenv("HOME"), ".ephemerup"), + "ephemerup.hcl", } } - + repr.Print(configfiles) for _, cfgfile := range configfiles { if _, err := os.Stat(cfgfile); err == nil { if err := k.Load(file.Provider(cfgfile), hcl.Parser(true)); err != nil { @@ -109,10 +111,12 @@ func Execute() error { } // env overrides config file - k.Load(env.Provider("EPHEMERUPD_", ".", func(s string) string { + if err := k.Load(env.Provider("EPHEMERUPD_", ".", func(s string) string { return strings.Replace(strings.ToLower( strings.TrimPrefix(s, "EPHEMERUPD_")), "_", ".", -1) - }), nil) + }), nil); err != nil { + return errors.New("error loading environment: " + err.Error()) + } // command line overrides env if err := k.Load(posflag.Provider(f, ".", k), nil); err != nil { @@ -120,7 +124,9 @@ func Execute() error { } // fetch values - k.Unmarshal("", &conf) + if err := k.Unmarshal("", &conf); err != nil { + return errors.New("error unmarshalling: " + err.Error()) + } // there may exist some api context variables GetApicontextsFromEnv(&conf) @@ -180,7 +186,7 @@ func Execute() error { eg: EPHEMERUPD_CONTEXT_SUPPORT="support:tymag-fycyh-gymof-dysuf-doseb-puxyx" - ^^^^^^^- doesn't matter. + ^^^^^^^- doesn't matter. Modifies cfg.Config directly */ @@ -197,9 +203,7 @@ func GetApicontextsFromEnv(conf *cfg.Config) { } } - for _, ap := range conf.Apicontexts { - contexts = append(contexts, ap) - } + contexts = append(contexts, conf.Apicontexts...) conf.Apicontexts = contexts } diff --git a/ephemerup.hcl b/ephemerup.hcl index 274f3f0..69be05c 100644 --- a/ephemerup.hcl +++ b/ephemerup.hcl @@ -2,7 +2,7 @@ listen = ":8080" bodylimit = 10000 -apicontext = [ +apicontexts = [ { context = "root" key = "0fddbff5d8010f81cd28a7d77f3e38981b13d6164c2fd6e1c3f60a4287630c37", diff --git a/upctl/cmd/formcommands.go b/upctl/cmd/formcommands.go index b95d192..a140cfd 100644 --- a/upctl/cmd/formcommands.go +++ b/upctl/cmd/formcommands.go @@ -33,8 +33,7 @@ func FormCommand(conf *cfg.Config) *cobra.Command { // errors at this stage do not cause the usage to be shown //cmd.SilenceUsage = true if len(args) == 0 { - cmd.Help() - os.Exit(0) + return cmd.Help() } return nil }, diff --git a/upctl/cmd/root.go b/upctl/cmd/root.go index b2e5b46..7e52e52 100644 --- a/upctl/cmd/root.go +++ b/upctl/cmd/root.go @@ -130,13 +130,12 @@ func initConfig(cmd *cobra.Command, cfg *cfg.Config) error { v.SetEnvPrefix("upctl") // map flags to viper - bindFlags(cmd, v) - - return nil + return bindFlags(cmd, v) } // bind flags to viper settings (env+cfgfile) -func bindFlags(cmd *cobra.Command, v *viper.Viper) { +func bindFlags(cmd *cobra.Command, v *viper.Viper) error { + var fail error cmd.Flags().VisitAll(func(f *pflag.Flag) { // map flag name to config variable configName := f.Name @@ -144,7 +143,11 @@ func bindFlags(cmd *cobra.Command, v *viper.Viper) { // use config variable if flag is not set and config is set if !f.Changed && v.IsSet(configName) { val := v.Get(configName) - cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)) + if err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)); err != nil { + fail = err + } } }) + + return fail } diff --git a/upctl/lib/client.go b/upctl/lib/client.go index 2850b53..93483f8 100644 --- a/upctl/lib/client.go +++ b/upctl/lib/client.go @@ -181,7 +181,9 @@ func UploadFiles(w io.Writer, c *cfg.Config, args []string) error { var left float64 rq.R.SetUploadCallbackWithInterval(func(info req.UploadInfo) { left = float64(info.UploadedSize) / float64(info.FileSize) * 100.0 - bar.Add(int(left)) + if err := bar.Add(int(left)); err != nil { + fmt.Print("\r") + } }, 10*time.Millisecond) } @@ -278,7 +280,9 @@ func Download(w io.Writer, c *cfg.Config, args []string) error { callback := func(info req.DownloadInfo) { if info.Response.Response != nil { - bar.Add(1) + if err := bar.Add(1); err != nil { + fmt.Print("\r") + } } } @@ -344,6 +348,4 @@ func CreateForm(w io.Writer, c *cfg.Config) error { } return RespondExtended(w, resp) - - return nil } diff --git a/upctl/lib/output.go b/upctl/lib/output.go index 63c8861..8f01620 100644 --- a/upctl/lib/output.go +++ b/upctl/lib/output.go @@ -38,8 +38,6 @@ func prepareExpire(expire string, start common.Timestamp) string { return time.Unix(start.Unix()+int64(common.Duration2int(expire)), 0). Format("2006-01-02 15:04:05") } - - return "" } // generic table writer diff --git a/upctl/t/t1 b/upctl/t/t1 deleted file mode 100644 index 7c0bf85..0000000 --- a/upctl/t/t1 +++ /dev/null @@ -1 +0,0 @@ -Mon Mar 20 12:16:26 PM CET 2023 From c6f2b3f57c706ba240237e47ab07a1fcbccdd83b Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Wed, 29 Mar 2023 15:01:28 +0200 Subject: [PATCH 5/9] add test file --- t/t1 | 1 + 1 file changed, 1 insertion(+) create mode 100644 t/t1 diff --git a/t/t1 b/t/t1 new file mode 100644 index 0000000..d87562a --- /dev/null +++ b/t/t1 @@ -0,0 +1 @@ +Wed Mar 29 03:01:21 PM CEST 2023 From 4aac69d4250773d9374287988e1b24a0ed43503a Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Wed, 29 Mar 2023 19:04:01 +0200 Subject: [PATCH 6/9] changes: - added form list - added sort by time - fixed hanging channel in zip writer - removed download url from form template - added upload description - added linter to Makefiles - added missing test file --- Makefile | 8 +++++- README.md | 2 +- api/fileio.go | 2 ++ api/form_handlers.go | 9 +++++++ cfg/config.go | 5 ++-- cmd/formtemplate.go | 4 +++ common/types.go | 15 ++++++------ templates/formtemplate.html | 8 ++++-- upctl/Makefile | 7 +++++- upctl/cmd/formcommands.go | 24 ++++++++++++++++++ upctl/cmd/maincommands.go | 4 ++- upctl/lib/client.go | 31 +++++++++++++++++------ upctl/lib/client_test.go | 3 ++- upctl/lib/output.go | 49 ++++++++++++++++++++++++++++++++----- {t => upctl/t}/t1 | 0 upctl/upctl.hcl | 1 + 16 files changed, 142 insertions(+), 30 deletions(-) rename {t => upctl/t}/t1 (100%) diff --git a/Makefile b/Makefile index a1f589d..cdb3b0a 100644 --- a/Makefile +++ b/Makefile @@ -27,9 +27,15 @@ COMMIT = $(shell git rev-parse --short=8 HEAD) BUILD = $(shell date +%Y.%m.%d.%H%M%S) VERSION := $(if $(filter $(BRANCH), development),$(version)-$(BRANCH)-$(COMMIT)-$(BUILD),$(version)) HAVE_POD := $(shell pod2text -h 2>/dev/null) +HAVE_LINT:= $(shell golangci-lint -h 2>/dev/null) DAEMON := ephemerupd -all: cmd/formtemplate.go buildlocal buildlocalctl +all: cmd/formtemplate.go lint buildlocal buildlocalctl + +lint: +ifdef HAVE_LINT + golangci-lint run +endif buildlocalctl: make -C upctl diff --git a/README.md b/README.md index 66f2fe8..cc31c89 100644 --- a/README.md +++ b/README.md @@ -271,7 +271,7 @@ The `endpoint` is the **ephemerup** server running somewhere and the - upctl: get rid of HandleResponse(), used only once anyway - add form so that public users can upload - use Writer for output.go so we can unit test the stuff in there - +- add (default by time!) sorting to list outputs, and add sort flag ## BUGS diff --git a/api/fileio.go b/api/fileio.go index 685cc71..8018488 100644 --- a/api/fileio.go +++ b/api/fileio.go @@ -180,6 +180,8 @@ func ZipDir(directory, zipfilename string) error { if err != nil { failure <- "Failed to zip directory: " + err.Error() + } else { + close(failure) } }() diff --git a/api/form_handlers.go b/api/form_handlers.go index c4f42ba..b45f914 100644 --- a/api/form_handlers.go +++ b/api/form_handlers.go @@ -73,6 +73,15 @@ func FormCreate(c *fiber.Ctx, cfg *cfg.Config, db *Db) error { entry.Notify = nt } + if len(formdata.Description) != 0 { + des, err := common.Untaint(formdata.Description, cfg.RegText) + if err != nil { + return JsonStatus(c, fiber.StatusForbidden, + "Invalid description: "+err.Error()) + } + entry.Description = des + } + // get url [and zip if there are multiple files] returnUrl := strings.Join([]string{cfg.Url, "form", id}, "/") entry.Url = returnUrl diff --git a/cfg/config.go b/cfg/config.go index 3b1b761..af82a8b 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -72,6 +72,7 @@ type Config struct { RegDuration *regexp.Regexp RegKey *regexp.Regexp RegEmail *regexp.Regexp + RegText *regexp.Regexp CleanInterval time.Duration DefaultExpire int } @@ -118,8 +119,8 @@ func (c *Config) ApplyDefaults() { c.RegNormalizedFilename = regexp.MustCompile(`[^\w\d\-_\.]`) c.RegDuration = regexp.MustCompile(`[^dhms0-9]`) c.RegKey = regexp.MustCompile(`[^a-zA-Z0-9\-]`) - c.RegEmail = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`) - c.RegEmail = regexp.MustCompile(`[^a-z0-9._%+\-@0-9]`) + c.RegEmail = regexp.MustCompile(`[^a-zA-Z0-9._%+\-@0-9]`) + c.RegText = regexp.MustCompile(`[^a-zA-Z0-9._%+\-@0-9 #/\.]`) c.CleanInterval = 10 * time.Second c.DefaultExpire = 30 * 86400 // 1 month diff --git a/cmd/formtemplate.go b/cmd/formtemplate.go index 38c8bff..bc3b79b 100644 --- a/cmd/formtemplate.go +++ b/cmd/formtemplate.go @@ -30,6 +30,10 @@ const formtemplate = ` Use this form to upload one or more files. The creator of the form will automatically get notified.

+
+ + +
diff --git a/common/types.go b/common/types.go index 57ea156..8c343e9 100644 --- a/common/types.go +++ b/common/types.go @@ -36,13 +36,14 @@ type Dbentry interface { } 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 - Created Timestamp `json:"uploaded"` - Context string `json:"context"` - Url string `json:"url"` + 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 + Created Timestamp `json:"uploaded"` + Context string `json:"context"` + Description string `json:"description"` + Url string `json:"url"` } // this one is also used for marshalling to the client diff --git a/templates/formtemplate.html b/templates/formtemplate.html index b4ac878..c67318a 100644 --- a/templates/formtemplate.html +++ b/templates/formtemplate.html @@ -27,6 +27,10 @@ Use this form to upload one or more files. The creator of the form will automatically get notified.

+
+ + +
@@ -71,8 +75,8 @@ $('.statusMsg').html(''); if(response.success){ $('#UploadForm')[0].reset(); - $('.statusMsg').html('

Your upload is available at ' - +response.uploads[0].url+' for download

'); + $('.statusMsg').html('

Your upload is available for download.'); $('#UploadForm').hide(); }else{ $('.statusMsg').html('

'+response.message+'

'); diff --git a/upctl/Makefile b/upctl/Makefile index 636da5d..f5316a0 100644 --- a/upctl/Makefile +++ b/upctl/Makefile @@ -28,9 +28,14 @@ COMMIT = $(shell git rev-parse --short=8 HEAD) BUILD = $(shell date +%Y.%m.%d.%H%M%S) VERSION := $(if $(filter $(BRANCH), development),$(version)-$(BRANCH)-$(COMMIT)-$(BUILD),$(version)) HAVE_POD := $(shell pod2text -h 2>/dev/null) +HAVE_LINT:= $(shell golangci-lint -h 2>/dev/null) -all: buildlocal +all: lint buildlocal +lint: +ifdef HAVE_LINT + golangci-lint run +endif buildlocal: go build -ldflags "-X 'github.com/tlinden/ephemerup/upctl/cfg.VERSION=$(VERSION)'" diff --git a/upctl/cmd/formcommands.go b/upctl/cmd/formcommands.go index a140cfd..3c18847 100644 --- a/upctl/cmd/formcommands.go +++ b/upctl/cmd/formcommands.go @@ -19,6 +19,7 @@ package cmd import ( //"errors" "github.com/spf13/cobra" + "github.com/tlinden/ephemerup/common" "github.com/tlinden/ephemerup/upctl/cfg" "github.com/tlinden/ephemerup/upctl/lib" "os" @@ -43,6 +44,7 @@ func FormCommand(conf *cfg.Config) *cobra.Command { formCmd.Aliases = append(formCmd.Aliases, "f") formCmd.AddCommand(FormCreateCommand(conf)) + formCmd.AddCommand(FormListCommand(conf)) return formCmd } @@ -70,3 +72,25 @@ func FormCreateCommand(conf *cfg.Config) *cobra.Command { return formCreateCmd } + +func FormListCommand(conf *cfg.Config) *cobra.Command { + var listCmd = &cobra.Command{ + Use: "list [options]", + Short: "List formss", + Long: `List formss.`, + RunE: func(cmd *cobra.Command, args []string) error { + // errors at this stage do not cause the usage to be shown + cmd.SilenceUsage = true + + return lib.List(os.Stdout, conf, nil, common.TypeForm) + }, + } + + // options + listCmd.PersistentFlags().StringVarP(&conf.Apicontext, "apicontext", "", "", "Filter by given API context") + + listCmd.Aliases = append(listCmd.Aliases, "ls") + listCmd.Aliases = append(listCmd.Aliases, "l") + + return listCmd +} diff --git a/upctl/cmd/maincommands.go b/upctl/cmd/maincommands.go index 180bc01..c0a2ed6 100644 --- a/upctl/cmd/maincommands.go +++ b/upctl/cmd/maincommands.go @@ -19,6 +19,7 @@ package cmd import ( "errors" "github.com/spf13/cobra" + "github.com/tlinden/ephemerup/common" "github.com/tlinden/ephemerup/upctl/cfg" "github.com/tlinden/ephemerup/upctl/lib" "os" @@ -43,6 +44,7 @@ func UploadCommand(conf *cfg.Config) *cobra.Command { // options uploadCmd.PersistentFlags().StringVarP(&conf.Expire, "expire", "e", "", "Expire setting: asap or duration (accepted shortcuts: dmh)") + uploadCmd.PersistentFlags().StringVarP(&conf.Description, "description", "D", "", "Description of the form") uploadCmd.Aliases = append(uploadCmd.Aliases, "up") uploadCmd.Aliases = append(uploadCmd.Aliases, "u") @@ -59,7 +61,7 @@ func ListCommand(conf *cfg.Config) *cobra.Command { // errors at this stage do not cause the usage to be shown cmd.SilenceUsage = true - return lib.List(os.Stdout, conf, args) + return lib.List(os.Stdout, conf, args, common.TypeUpload) }, } diff --git a/upctl/lib/client.go b/upctl/lib/client.go index 93483f8..f9ab95e 100644 --- a/upctl/lib/client.go +++ b/upctl/lib/client.go @@ -190,7 +190,8 @@ func UploadFiles(w io.Writer, c *cfg.Config, args []string) error { // actual post w/ settings resp, err := rq.R. SetFormData(map[string]string{ - "expire": c.Expire, + "expire": c.Expire, + "description": c.Description, }). Post(rq.Url) @@ -205,8 +206,15 @@ func UploadFiles(w io.Writer, c *cfg.Config, args []string) error { return RespondExtended(w, resp) } -func List(w io.Writer, c *cfg.Config, args []string) error { - rq := Setup(c, "/uploads") +func List(w io.Writer, c *cfg.Config, args []string, typ int) error { + var rq *Request + + switch typ { + case common.TypeUpload: + rq = Setup(c, "/uploads") + case common.TypeForm: + rq = Setup(c, "/forms") + } params := &ListParams{Apicontext: c.Apicontext} resp, err := rq.R. @@ -221,7 +229,14 @@ func List(w io.Writer, c *cfg.Config, args []string) error { return err } - return UploadsRespondTable(w, resp) + switch typ { + case common.TypeUpload: + return UploadsRespondTable(w, resp) + case common.TypeForm: + return FormsRespondTable(w, resp) + } + + return nil } func Delete(w io.Writer, c *cfg.Config, args []string) error { @@ -332,10 +347,10 @@ func CreateForm(w io.Writer, c *cfg.Config) error { // actual post w/ settings resp, err := rq.R. - SetFormData(map[string]string{ - "expire": c.Expire, - "description": c.Description, - "notify": c.Notify, + SetBody(&common.Form{ + Expire: c.Expire, + Description: c.Description, + Notify: c.Notify, }). Post(rq.Url) diff --git a/upctl/lib/client_test.go b/upctl/lib/client_test.go index 15de2ed..cd743c1 100644 --- a/upctl/lib/client_test.go +++ b/upctl/lib/client_test.go @@ -22,6 +22,7 @@ import ( "bytes" "fmt" "github.com/jarcoal/httpmock" + "github.com/tlinden/ephemerup/common" "github.com/tlinden/ephemerup/upctl/cfg" "io/ioutil" "net/http" @@ -232,7 +233,7 @@ func TestList(t *testing.T) { for _, unit := range tests { var w bytes.Buffer Intercept(unit) - Check(t, unit, &w, List(&w, conf, []string{})) + Check(t, unit, &w, List(&w, conf, []string{}, common.TypeUpload)) } } diff --git a/upctl/lib/output.go b/upctl/lib/output.go index 8f01620..ef250c0 100644 --- a/upctl/lib/output.go +++ b/upctl/lib/output.go @@ -21,10 +21,12 @@ import ( "encoding/json" "errors" "fmt" + //"github.com/alecthomas/repr" "github.com/imroc/req/v3" "github.com/olekukonko/tablewriter" "github.com/tlinden/ephemerup/common" "io" + "sort" "strings" "time" ) @@ -77,7 +79,8 @@ func WriteExtended(w io.Writer, response *common.Response) { // we shall only have 1 element, however, if we ever support more, here we go for _, entry := range response.Uploads { expire := prepareExpire(entry.Expire, entry.Created) - fmt.Fprintf(w, format, "Id", entry.Id) + fmt.Fprintf(w, format, "Upload-Id", entry.Id) + fmt.Fprintf(w, format, "Description", entry.Id) fmt.Fprintf(w, format, "Expire", expire) fmt.Fprintf(w, format, "Context", entry.Context) fmt.Fprintf(w, format, "Created", entry.Created) @@ -88,18 +91,18 @@ func WriteExtended(w io.Writer, response *common.Response) { for _, entry := range response.Forms { expire := prepareExpire(entry.Expire, entry.Created) - fmt.Fprintf(w, format, "Id", entry.Id) + fmt.Fprintf(w, format, "Form-Id", entry.Id) + fmt.Fprintf(w, format, "Description", entry.Description) fmt.Fprintf(w, format, "Expire", expire) fmt.Fprintf(w, format, "Context", entry.Context) fmt.Fprintf(w, format, "Created", entry.Created) - fmt.Fprintf(w, format, "Description", entry.Description) fmt.Fprintf(w, format, "Notify", entry.Notify) fmt.Fprintf(w, format, "Url", entry.Url) fmt.Fprintln(w) } } -// extract an common.Uploads{} struct from json response +// extract an common.Response{} struct from json response func GetResponse(resp *req.Response) (*common.Response, error) { response := common.Response{} @@ -125,15 +128,49 @@ func UploadsRespondTable(w io.Writer, resp *req.Response) error { fmt.Fprintln(w, response.Message) } + sort.SliceStable(response.Uploads, func(i, j int) bool { + return response.Uploads[i].Created.Time.Unix() < response.Uploads[j].Created.Time.Unix() + }) + // tablewriter data := [][]string{} for _, entry := range response.Uploads { data = append(data, []string{ - entry.Id, entry.Expire, entry.Context, entry.Created.Format("2006-01-02 15:04:05"), + entry.Id, entry.Description, entry.Expire, entry.Context, + entry.Created.Format("2006-01-02 15:04:05"), entry.File, }) } - WriteTable(w, []string{"ID", "EXPIRE", "CONTEXT", "CREATED"}, data) + WriteTable(w, []string{"UPLOAD-ID", "DESCRIPTION", "EXPIRE", "CONTEXT", "CREATED", "FILE"}, data) + + return nil +} + +// turn the Forms{} struct into a table and print it +func FormsRespondTable(w io.Writer, resp *req.Response) error { + response, err := GetResponse(resp) + if err != nil { + return err + } + + if response.Message != "" { + fmt.Fprintln(w, response.Message) + } + + sort.SliceStable(response.Forms, func(i, j int) bool { + return response.Forms[i].Created.Time.Unix() < response.Forms[j].Created.Time.Unix() + }) + + // tablewriter + data := [][]string{} + for _, entry := range response.Forms { + data = append(data, []string{ + entry.Id, entry.Description, entry.Expire, entry.Context, + entry.Created.Format("2006-01-02 15:04:05"), entry.Notify, + }) + } + + WriteTable(w, []string{"FORM-ID", "DESCRIPTION", "EXPIRE", "CONTEXT", "CREATED", "NOTIFY"}, data) return nil } diff --git a/t/t1 b/upctl/t/t1 similarity index 100% rename from t/t1 rename to upctl/t/t1 diff --git a/upctl/upctl.hcl b/upctl/upctl.hcl index fc989e6..4031105 100644 --- a/upctl/upctl.hcl +++ b/upctl/upctl.hcl @@ -1,2 +1,3 @@ endpoint = "http://localhost:8080/v1" apikey = "970b391f22f515d96b3e9b86a2c62c627968828e47b356994d2e583188b4190a" +notify = "root@localhost" From 26f2b25e2263945bcebb2f58cbf3fb24f262a118 Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Wed, 29 Mar 2023 19:07:48 +0200 Subject: [PATCH 7/9] +badges --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index cc31c89..413ab0b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +[![Actions](https://github.com/tlinden/ephemerup/actions/workflows/ci.yaml/badge.svg)](https://github.com/tlinden/ephemerup/actions) +[![License](https://img.shields.io/badge/license-GPL-blue.svg)](https://github.com/tlinden/ephemerup/blob/master/LICENSE) +[![Go Report Card](https://goreportcard.com/badge/github.com/tlinden/ephemerup)](https://goreportcard.com/report/github.com/tlinden/ephemerup) + # ephemerup Simple standalone file upload server with expiration and commandline client. From 8a791d8017ad2a013f930b2fc132d3cd3e5cd7d7 Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Thu, 30 Mar 2023 10:22:57 +0200 Subject: [PATCH 8/9] Changes: - had to add a Type field to interface DbEntry so that db.List() is able to distinguish between Upload and Form properly. - added form describe and delete commands - added --query parameter to form+upload list for filtering --- README.md | 5 +--- api/db.go | 40 ++++++++++++++++++++++++----- api/db_test.go | 9 ++++--- api/form_handlers.go | 10 ++++++-- api/upload_handlers.go | 17 ++++++++---- cfg/config.go | 9 ++++--- cmd/formtemplate.go | 4 +-- common/types.go | 54 +++++++++++++++++++++++++++++++++++++++ upctl/cfg/config.go | 3 +++ upctl/cmd/formcommands.go | 51 ++++++++++++++++++++++++++++++++++++ upctl/cmd/maincommands.go | 11 ++++---- upctl/lib/client.go | 29 ++++++++++++++++----- upctl/lib/client_test.go | 6 ++--- 13 files changed, 208 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 413ab0b..3c3c739 100644 --- a/README.md +++ b/README.md @@ -236,6 +236,7 @@ Available Commands: delete Delete an upload describe Describe an upload. download Download a file. + form Form commands help Help about any command list List uploads upload Upload files @@ -268,13 +269,9 @@ The `endpoint` is the **ephemerup** server running somewhere and the ## TODO -- also serve a html upload page - add metrics (as in https://github.com/ansrivas/fiberprometheus) - do not manually generate output urls, use fiber.GetRoute() - upd: https://docs.gofiber.io/guide/error-handling/ to always use json output -- upctl: get rid of HandleResponse(), used only once anyway -- add form so that public users can upload -- use Writer for output.go so we can unit test the stuff in there - add (default by time!) sorting to list outputs, and add sort flag diff --git a/api/db.go b/api/db.go index c3f43d7..ae68159 100644 --- a/api/db.go +++ b/api/db.go @@ -23,6 +23,7 @@ import ( "github.com/tlinden/ephemerup/common" //"github.com/alecthomas/repr" bolt "go.etcd.io/bbolt" + "regexp" ) const Bucket string = "data" @@ -102,8 +103,9 @@ func (db *Db) Delete(apicontext string, id string) error { return err } -func (db *Db) List(apicontext string, filter string, t int) (*common.Response, error) { +func (db *Db) List(apicontext string, filter string, query string, t int) (*common.Response, error) { response := &common.Response{} + qr := regexp.MustCompile(query) err := db.bolt.View(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(Bucket)) @@ -112,11 +114,17 @@ func (db *Db) List(apicontext string, filter string, t int) (*common.Response, e } err := bucket.ForEach(func(id, j []byte) error { + allowed := false entry, err := common.Unmarshal(j, t) + if err != nil { return fmt.Errorf("unable to unmarshal json: %s", err) } + if !entry.IsType(t) { + return nil + } + var entryContext string if t == common.TypeUpload { entryContext = entry.(*common.Upload).Context @@ -124,22 +132,42 @@ func (db *Db) List(apicontext string, filter string, t int) (*common.Response, e entryContext = entry.(*common.Form).Context } - //fmt.Printf("apicontext: %s, filter: %s\n", apicontext, filter) + // check if the user is allowed to list this entry if apicontext != "" && db.cfg.Super != apicontext { - // only return the uploads for this context + // authenticated user but not member of super + // only return the uploads matching her context if apicontext == entryContext { - // unless a filter needed OR no filter specified + // unless a filter OR no filter specified if (filter != "" && entryContext == filter) || filter == "" { - response.Append(entry) + allowed = true } } } else { // return all, because we operate a public service or current==super if (filter != "" && entryContext == filter) || filter == "" { - response.Append(entry) + allowed = true } } + if allowed { + // user is allowed to view this entry, check if she also wants to see it + if query != "" { + if entry.MatchDescription(qr) || + entry.MatchExpire(qr) || + entry.MatchCreated(qr) || + entry.MatchFile(qr) { + allowed = true + } else { + allowed = false + } + } + } + + if allowed { + // ok, legit and wanted + response.Append(entry) + } + return nil }) diff --git a/api/db_test.go b/api/db_test.go index c2dc66f..fff8efc 100644 --- a/api/db_test.go +++ b/api/db_test.go @@ -72,12 +72,13 @@ var dbtests = []struct { context string ts string filter string + query string upload common.Upload form common.Form }{ { "upload", "test.db", false, "1", "foo", - "2023-03-10T11:45:00.000Z", "", + "2023-03-10T11:45:00.000Z", "", "", common.Upload{ Id: "1", Expire: "asap", File: "none", Context: "foo", Created: common.Timestamp{}}, @@ -85,7 +86,7 @@ var dbtests = []struct { }, { "form", "test.db", false, "2", "foo", - "2023-03-10T11:45:00.000Z", "", + "2023-03-10T11:45:00.000Z", "", "", common.Upload{}, common.Form{ Id: "1", Expire: "asap", Description: "none", Context: "foo", @@ -149,7 +150,7 @@ func TestDboperation(t *testing.T) { td.Cmp(t, response.Uploads[0], &tt.upload, tt.name) // fetch list - response, err = db.List(tt.context, tt.filter, common.TypeUpload) + response, err = db.List(tt.context, tt.filter, tt.query, common.TypeUpload) if err != nil { t.Errorf("Could not fetch uploads list: " + err.Error()) } @@ -211,7 +212,7 @@ func TestDboperation(t *testing.T) { td.Cmp(t, response.Forms[0], &tt.form, tt.name) // fetch list - response, err = db.List(tt.context, tt.filter, common.TypeForm) + response, err = db.List(tt.context, tt.filter, tt.query, common.TypeForm) if err != nil { t.Errorf("Could not fetch forms list: " + err.Error()) } diff --git a/api/form_handlers.go b/api/form_handlers.go index b45f914..9711bfa 100644 --- a/api/form_handlers.go +++ b/api/form_handlers.go @@ -36,7 +36,7 @@ func FormCreate(c *fiber.Ctx, cfg *cfg.Config, db *Db) error { var formdata common.Form // init form obj - entry := &common.Form{Id: id, Created: common.Timestamp{Time: time.Now()}} + entry := &common.Form{Id: id, Created: common.Timestamp{Time: time.Now()}, Type: common.TypeForm} // retrieve the API Context name from the session apicontext, err := SessionGetApicontext(c) @@ -149,6 +149,12 @@ func FormsList(c *fiber.Ctx, cfg *cfg.Config, db *Db) error { "Invalid api context filter provided!") } + query, err := common.Untaint(setcontext.Query, cfg.RegQuery) + if err != nil { + return JsonStatus(c, fiber.StatusForbidden, + "Invalid query provided!") + } + // retrieve the API Context name from the session apicontext, err := SessionGetApicontext(c) if err != nil { @@ -157,7 +163,7 @@ func FormsList(c *fiber.Ctx, cfg *cfg.Config, db *Db) error { } // get list - response, err := db.List(apicontext, filter, common.TypeForm) + response, err := db.List(apicontext, filter, query, common.TypeForm) if err != nil { return JsonStatus(c, fiber.StatusForbidden, "Unable to list forms: "+err.Error()) diff --git a/api/upload_handlers.go b/api/upload_handlers.go index e6a5fef..e02dcd4 100644 --- a/api/upload_handlers.go +++ b/api/upload_handlers.go @@ -33,6 +33,7 @@ import ( type SetContext struct { Apicontext string `json:"apicontext" form:"apicontext"` + Query string `json:"query" form:"query"` } func UploadPost(c *fiber.Ctx, cfg *cfg.Config, db *Db) error { @@ -66,7 +67,7 @@ func UploadPost(c *fiber.Ctx, cfg *cfg.Config, db *Db) error { } // init upload obj - entry := &common.Upload{Id: id, Created: common.Timestamp{Time: time.Now()}} + entry := &common.Upload{Id: id, Created: common.Timestamp{Time: time.Now()}, Type: common.TypeUpload} // retrieve the API Context name from the session apicontext, err := SessionGetApicontext(c) @@ -256,17 +257,23 @@ func UploadDelete(c *fiber.Ctx, cfg *cfg.Config, db *Db) error { // returns the whole list + error code, no post processing by server func UploadsList(c *fiber.Ctx, cfg *cfg.Config, db *Db) error { - // fetch filter from body(json expected) + // fetch apifilter+query from body(json expected) setcontext := new(SetContext) if err := c.BodyParser(setcontext); err != nil { return JsonStatus(c, fiber.StatusForbidden, "Unable to parse body: "+err.Error()) } - filter, err := common.Untaint(setcontext.Apicontext, cfg.RegKey) + apifilter, err := common.Untaint(setcontext.Apicontext, cfg.RegKey) if err != nil { return JsonStatus(c, fiber.StatusForbidden, - "Invalid api context filter provided!") + "Invalid api context apifilter provided!") + } + + query, err := common.Untaint(setcontext.Query, cfg.RegQuery) + if err != nil { + return JsonStatus(c, fiber.StatusForbidden, + "Invalid query provided!") } // retrieve the API Context name from the session @@ -277,7 +284,7 @@ func UploadsList(c *fiber.Ctx, cfg *cfg.Config, db *Db) error { } // get list - uploads, err := db.List(apicontext, filter, common.TypeUpload) + uploads, err := db.List(apicontext, apifilter, query, common.TypeUpload) if err != nil { return JsonStatus(c, fiber.StatusForbidden, "Unable to list uploads: "+err.Error()) diff --git a/cfg/config.go b/cfg/config.go index af82a8b..eb04af6 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -73,8 +73,10 @@ type Config struct { RegKey *regexp.Regexp RegEmail *regexp.Regexp RegText *regexp.Regexp - CleanInterval time.Duration - DefaultExpire int + RegQuery *regexp.Regexp + + CleanInterval time.Duration + DefaultExpire int } func Getversion() string { @@ -120,7 +122,8 @@ func (c *Config) ApplyDefaults() { c.RegDuration = regexp.MustCompile(`[^dhms0-9]`) c.RegKey = regexp.MustCompile(`[^a-zA-Z0-9\-]`) c.RegEmail = regexp.MustCompile(`[^a-zA-Z0-9._%+\-@0-9]`) - c.RegText = regexp.MustCompile(`[^a-zA-Z0-9._%+\-@0-9 #/\.]`) + c.RegText = regexp.MustCompile(`[^a-zA-Z0-9_%+\-@0-9 #/\.]`) + c.RegQuery = regexp.MustCompile(`[^a-zA-Z0-9_%+\-@0-9 #/\.\*\[\]\(\)\\]`) c.CleanInterval = 10 * time.Second c.DefaultExpire = 30 * 86400 // 1 month diff --git a/cmd/formtemplate.go b/cmd/formtemplate.go index bc3b79b..40e583b 100644 --- a/cmd/formtemplate.go +++ b/cmd/formtemplate.go @@ -78,8 +78,8 @@ const formtemplate = ` $('.statusMsg').html(''); if(response.success){ $('#UploadForm')[0].reset(); - $('.statusMsg').html('

Your upload is available at ' - +response.uploads[0].url+' for download

'); + $('.statusMsg').html('

Your upload is available for download.'); $('#UploadForm').hide(); }else{ $('.statusMsg').html('

'+response.message+'

'); diff --git a/common/types.go b/common/types.go index 8c343e9..a380042 100644 --- a/common/types.go +++ b/common/types.go @@ -20,6 +20,7 @@ package common import ( "encoding/json" "fmt" + "regexp" ) // used to return to the api client @@ -33,9 +34,15 @@ type Result struct { type Dbentry interface { Getcontext(j []byte) (string, error) Marshal() ([]byte, error) + MatchExpire(r *regexp.Regexp) bool + MatchDescription(r *regexp.Regexp) bool + MatchFile(r *regexp.Regexp) bool + MatchCreated(r *regexp.Regexp) bool + IsType(t int) bool } type Upload struct { + Type int `json:"type"` Id string `json:"id"` Expire string `json:"expire"` File string `json:"file"` // final filename (visible to the downloader) @@ -61,6 +68,7 @@ type Form struct { // that the upload handler is able to check if the form object has // to be deleted immediately (if its expire field has been set to // asap) + Type int `json:"type"` Id string `json:"id"` Expire string `json:"expire"` Description string `json:"description"` @@ -112,6 +120,52 @@ func (form Form) Marshal() ([]byte, error) { return jsonentry, nil } +func (upload Upload) MatchExpire(r *regexp.Regexp) bool { + return r.MatchString(upload.Expire) +} + +func (upload Upload) MatchDescription(r *regexp.Regexp) bool { + return r.MatchString(upload.Description) +} + +func (upload Upload) MatchCreated(r *regexp.Regexp) bool { + return r.MatchString(upload.Created.Time.String()) +} + +func (upload Upload) MatchFile(r *regexp.Regexp) bool { + return r.MatchString(upload.File) +} + +func (form Form) MatchExpire(r *regexp.Regexp) bool { + return r.MatchString(form.Expire) +} + +func (form Form) MatchDescription(r *regexp.Regexp) bool { + return r.MatchString(form.Description) +} + +func (form Form) MatchCreated(r *regexp.Regexp) bool { + return r.MatchString(form.Created.Time.String()) +} + +func (form Form) MatchFile(r *regexp.Regexp) bool { + return false +} + +func (upload Upload) IsType(t int) bool { + if upload.Type == t { + return true + } + return false +} + +func (form Form) IsType(t int) bool { + if form.Type == t { + return true + } + return false +} + /* Response methods */ diff --git a/upctl/cfg/config.go b/upctl/cfg/config.go index c283ba4..f50c771 100644 --- a/upctl/cfg/config.go +++ b/upctl/cfg/config.go @@ -44,6 +44,9 @@ type Config struct { // required to intercept requests using httpmock in tests Mock bool + // used to filter lists + Query string + // required for forms Description string Notify string diff --git a/upctl/cmd/formcommands.go b/upctl/cmd/formcommands.go index 3c18847..15929a9 100644 --- a/upctl/cmd/formcommands.go +++ b/upctl/cmd/formcommands.go @@ -18,6 +18,7 @@ package cmd import ( //"errors" + "errors" "github.com/spf13/cobra" "github.com/tlinden/ephemerup/common" "github.com/tlinden/ephemerup/upctl/cfg" @@ -45,6 +46,8 @@ func FormCommand(conf *cfg.Config) *cobra.Command { formCmd.AddCommand(FormCreateCommand(conf)) formCmd.AddCommand(FormListCommand(conf)) + formCmd.AddCommand(FormDeleteCommand(conf)) + formCmd.AddCommand(FormDescribeCommand(conf)) return formCmd } @@ -88,9 +91,57 @@ func FormListCommand(conf *cfg.Config) *cobra.Command { // options listCmd.PersistentFlags().StringVarP(&conf.Apicontext, "apicontext", "", "", "Filter by given API context") + listCmd.PersistentFlags().StringVarP(&conf.Query, "query", "q", "", "Filter by given query regexp") listCmd.Aliases = append(listCmd.Aliases, "ls") listCmd.Aliases = append(listCmd.Aliases, "l") return listCmd } + +func FormDeleteCommand(conf *cfg.Config) *cobra.Command { + var deleteCmd = &cobra.Command{ + Use: "delete [options] ", + Short: "Delete an form", + Long: `Delete an form identified by its id`, + 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.Delete(os.Stdout, conf, args, common.TypeForm) + }, + } + + deleteCmd.Aliases = append(deleteCmd.Aliases, "rm") + deleteCmd.Aliases = append(deleteCmd.Aliases, "d") + + return deleteCmd +} + +func FormDescribeCommand(conf *cfg.Config) *cobra.Command { + var listCmd = &cobra.Command{ + Use: "describe [options] form-id", + Long: "Show detailed informations about an form object.", + Short: `Describe an form.`, + 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.Describe(os.Stdout, conf, args, common.TypeForm) + }, + } + + listCmd.Aliases = append(listCmd.Aliases, "des") + listCmd.Aliases = append(listCmd.Aliases, "info") + listCmd.Aliases = append(listCmd.Aliases, "i") + + return listCmd +} diff --git a/upctl/cmd/maincommands.go b/upctl/cmd/maincommands.go index c0a2ed6..fc20d62 100644 --- a/upctl/cmd/maincommands.go +++ b/upctl/cmd/maincommands.go @@ -56,7 +56,7 @@ func ListCommand(conf *cfg.Config) *cobra.Command { var listCmd = &cobra.Command{ Use: "list [options] [file ..]", Short: "List uploads", - Long: `List uploads.`, + Long: `List uploads`, RunE: func(cmd *cobra.Command, args []string) error { // errors at this stage do not cause the usage to be shown cmd.SilenceUsage = true @@ -67,6 +67,7 @@ func ListCommand(conf *cfg.Config) *cobra.Command { // options listCmd.PersistentFlags().StringVarP(&conf.Apicontext, "apicontext", "", "", "Filter by given API context") + listCmd.PersistentFlags().StringVarP(&conf.Query, "query", "q", "", "Filter by given query regexp") listCmd.Aliases = append(listCmd.Aliases, "ls") listCmd.Aliases = append(listCmd.Aliases, "l") @@ -87,7 +88,7 @@ func DeleteCommand(conf *cfg.Config) *cobra.Command { // errors at this stage do not cause the usage to be shown cmd.SilenceUsage = true - return lib.Delete(os.Stdout, conf, args) + return lib.Delete(os.Stdout, conf, args, common.TypeUpload) }, } @@ -101,7 +102,7 @@ func DescribeCommand(conf *cfg.Config) *cobra.Command { var listCmd = &cobra.Command{ Use: "describe [options] upload-id", Long: "Show detailed informations about an upload object.", - Short: `Describe an upload.`, + Short: `Describe an upload`, RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return errors.New("No id specified to delete!") @@ -110,7 +111,7 @@ func DescribeCommand(conf *cfg.Config) *cobra.Command { // errors at this stage do not cause the usage to be shown cmd.SilenceUsage = true - return lib.Describe(os.Stdout, conf, args) + return lib.Describe(os.Stdout, conf, args, common.TypeUpload) }, } @@ -125,7 +126,7 @@ 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.`, + Short: `Download a file`, RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return errors.New("No id specified to delete!") diff --git a/upctl/lib/client.go b/upctl/lib/client.go index f9ab95e..4f7c560 100644 --- a/upctl/lib/client.go +++ b/upctl/lib/client.go @@ -48,6 +48,7 @@ type Request struct { type ListParams struct { Apicontext string `json:"apicontext"` + Query string `json:"query"` } const Maxwidth = 12 @@ -216,7 +217,7 @@ func List(w io.Writer, c *cfg.Config, args []string, typ int) error { rq = Setup(c, "/forms") } - params := &ListParams{Apicontext: c.Apicontext} + params := &ListParams{Apicontext: c.Apicontext, Query: c.Query} resp, err := rq.R. SetBodyJsonMarshal(params). Get(rq.Url) @@ -239,9 +240,18 @@ func List(w io.Writer, c *cfg.Config, args []string, typ int) error { return nil } -func Delete(w io.Writer, c *cfg.Config, args []string) error { +func Delete(w io.Writer, c *cfg.Config, args []string, typ int) error { for _, id := range args { - rq := Setup(c, "/uploads/"+id+"/") + var rq *Request + caption := "Upload" + + switch typ { + case common.TypeUpload: + rq = Setup(c, "/uploads/"+id) + case common.TypeForm: + rq = Setup(c, "/forms/"+id) + caption = "Form" + } resp, err := rq.R.Delete(rq.Url) @@ -253,20 +263,27 @@ func Delete(w io.Writer, c *cfg.Config, args []string) error { return err } - fmt.Fprintf(w, "Upload %s successfully deleted.\n", id) + fmt.Fprintf(w, "%s %s successfully deleted.\n", caption, id) } return nil } -func Describe(w io.Writer, c *cfg.Config, args []string) error { +func Describe(w io.Writer, c *cfg.Config, args []string, typ int) error { if len(args) == 0 { return errors.New("No id provided!") } + var rq *Request id := args[0] // we describe only 1 object - rq := Setup(c, "/uploads/"+id) + switch typ { + case common.TypeUpload: + rq = Setup(c, "/uploads/"+id) + case common.TypeForm: + rq = Setup(c, "/forms/"+id) + } + resp, err := rq.R.Get(rq.Url) if err != nil { diff --git a/upctl/lib/client_test.go b/upctl/lib/client_test.go index cd743c1..3b92a22 100644 --- a/upctl/lib/client_test.go +++ b/upctl/lib/client_test.go @@ -295,7 +295,7 @@ func TestDescribe(t *testing.T) { var w bytes.Buffer unit.route += unit.files[0] Intercept(unit) - Check(t, unit, &w, Describe(&w, conf, unit.files)) + Check(t, unit, &w, Describe(&w, conf, unit.files, common.TypeUpload)) } } @@ -345,9 +345,9 @@ func TestDelete(t *testing.T) { for _, unit := range tests { var w bytes.Buffer - unit.route += unit.files[0] + "/" + unit.route += unit.files[0] Intercept(unit) - Check(t, unit, &w, Delete(&w, conf, unit.files)) + Check(t, unit, &w, Delete(&w, conf, unit.files, common.TypeUpload)) } } From 1db0adf298d6b86d663cbfd0a92f4d9bcf0b0258 Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Thu, 30 Mar 2023 10:31:09 +0200 Subject: [PATCH 9/9] fixed test --- api/db_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/db_test.go b/api/db_test.go index fff8efc..4227412 100644 --- a/api/db_test.go +++ b/api/db_test.go @@ -81,7 +81,7 @@ var dbtests = []struct { "2023-03-10T11:45:00.000Z", "", "", common.Upload{ Id: "1", Expire: "asap", File: "none", Context: "foo", - Created: common.Timestamp{}}, + Created: common.Timestamp{}, Type: common.TypeUpload}, common.Form{}, }, { @@ -90,7 +90,7 @@ var dbtests = []struct { common.Upload{}, common.Form{ Id: "1", Expire: "asap", Description: "none", Context: "foo", - Created: common.Timestamp{}}, + Created: common.Timestamp{}, Type: common.TypeForm}, }, }