diff --git a/Makefile b/Makefile
index cdb3b0a..456abed 100644
--- a/Makefile
+++ b/Makefile
@@ -26,9 +26,13 @@ BRANCH = $(shell git branch --show-current)
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))
+ONMAIN := $(if $(filter $(BRANCH), main),"main","")
HAVE_POD := $(shell pod2text -h 2>/dev/null)
HAVE_LINT:= $(shell golangci-lint -h 2>/dev/null)
DAEMON := ephemerupd
+CLIENT := upctl
+DATE = $(shell date +%Y-%m-%d)
+
all: cmd/formtemplate.go lint buildlocal buildlocalctl
@@ -47,8 +51,14 @@ buildimage: clean
docker-compose --verbose build
release:
- ./mkrel.sh $(DAEMON) $(version)
+ifdef BR_MAIN
+ git tag -a $(version) -m "$(version) released on $(DATE)"
+ git push origin --tags
+ ./mkrel.sh $(DAEMON) $(CLIENT) $(version)
gh release create $(version) --generate-notes releases/*
+else
+ @echo "Cannot create release on branch $(BRANCH), checkout main and retry!"
+endif
install: buildlocal
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
diff --git a/api/form_handlers.go b/api/form_handlers.go
index 9711bfa..2c9536c 100644
--- a/api/form_handlers.go
+++ b/api/form_handlers.go
@@ -26,10 +26,27 @@ import (
"bytes"
"html/template"
+ "regexp"
"strings"
"time"
)
+/*
+ Validate a fied by untainting it, modifies field value inplace.
+*/
+func untaintField(c *fiber.Ctx, orig *string, r *regexp.Regexp, caption string) error {
+ if len(*orig) != 0 {
+ nt, err := common.Untaint(*orig, r)
+ if err != nil {
+ return JsonStatus(c, fiber.StatusForbidden,
+ "Invalid "+caption+": "+err.Error())
+ }
+ *orig = nt
+ }
+
+ return nil
+}
+
func FormCreate(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
id := uuid.NewString()
@@ -52,35 +69,25 @@ func FormCreate(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
"bodyparser error : "+err.Error())
}
- // post process expire
+ // post process inputdata
if len(formdata.Expire) == 0 {
entry.Expire = "asap"
} else {
- ex, err := common.Untaint(formdata.Expire, cfg.RegDuration) // duration or asap allowed
- if err != nil {
- return JsonStatus(c, fiber.StatusForbidden,
- "Invalid expire data: "+err.Error())
+ if err := untaintField(c, &formdata.Expire, cfg.RegDuration, "expire data"); err != nil {
+ return err
}
- entry.Expire = ex
+ entry.Expire = formdata.Expire
}
- if len(formdata.Notify) != 0 {
- nt, err := common.Untaint(formdata.Notify, cfg.RegEmail)
- if err != nil {
- return JsonStatus(c, fiber.StatusForbidden,
- "Invalid email address: "+err.Error())
- }
- entry.Notify = nt
+ if err := untaintField(c, &formdata.Notify, cfg.RegDuration, "email address"); err != nil {
+ return err
}
+ entry.Notify = formdata.Notify
- 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
+ if err := untaintField(c, &formdata.Description, cfg.RegDuration, "description"); err != nil {
+ return err
}
+ entry.Description = formdata.Description
// get url [and zip if there are multiple files]
returnUrl := strings.Join([]string{cfg.Url, "form", id}, "/")
@@ -192,7 +199,7 @@ func FormDescribe(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
}
response, err := db.Get(apicontext, id, common.TypeForm)
- if err != nil {
+ if err != nil || len(response.Forms) == 0 {
return JsonStatus(c, fiber.StatusForbidden,
"No form with that id could be found!")
}
@@ -222,17 +229,20 @@ func FormPage(c *fiber.Ctx, cfg *cfg.Config, db *Db, shallexpire bool) error {
apicontext, err := SessionGetApicontext(c)
if err != nil {
- return c.Status(fiber.StatusInternalServerError).SendString("Unable to initialize session store from context:" + err.Error())
+ return c.Status(fiber.StatusInternalServerError).
+ SendString("Unable to initialize session store from context:" + err.Error())
}
response, err := db.Get(apicontext, id, common.TypeForm)
if err != nil || len(response.Forms) == 0 {
- return c.Status(fiber.StatusForbidden).SendString("No form with that id could be found!")
+ return c.Status(fiber.StatusForbidden).
+ SendString("No form with that id could be found!")
}
t := template.New("form")
if t, err = t.Parse(cfg.Formpage); err != nil {
- return c.Status(fiber.StatusInternalServerError).SendString("Unable to load form template: " + err.Error())
+ return c.Status(fiber.StatusInternalServerError).
+ SendString("Unable to load form template: " + err.Error())
}
// prepare upload url
@@ -241,9 +251,79 @@ func FormPage(c *fiber.Ctx, cfg *cfg.Config, db *Db, shallexpire bool) error {
var out bytes.Buffer
if err := t.Execute(&out, response.Forms[0]); err != nil {
- return c.Status(fiber.StatusInternalServerError).SendString("Unable to render form template: " + err.Error())
+ return c.Status(fiber.StatusInternalServerError).
+ SendString("Unable to render form template: " + err.Error())
}
c.Set("Content-type", "text/html; charset=utf-8")
return c.Status(fiber.StatusOK).SendString(out.String())
}
+
+func FormModify(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
+ var formdata common.Form
+
+ // retrieve the API Context name from the session
+ apicontext, err := SessionGetApicontext(c)
+ if err != nil {
+ return JsonStatus(c, fiber.StatusInternalServerError,
+ "Unable to initialize session store from context: "+err.Error())
+ }
+
+ id, err := common.Untaint(c.Params("id"), cfg.RegKey)
+ if err != nil {
+ return JsonStatus(c, fiber.StatusForbidden,
+ "Invalid id provided!")
+ }
+
+ // extract form data
+ if err := c.BodyParser(&formdata); err != nil {
+ return JsonStatus(c, fiber.StatusInternalServerError,
+ "bodyparser error : "+err.Error())
+ }
+
+ // post process input data
+ if err := untaintField(c, &formdata.Expire, cfg.RegDuration, "expire data"); err != nil {
+ return err
+ }
+
+ if err := untaintField(c, &formdata.Notify, cfg.RegDuration, "email address"); err != nil {
+ return err
+ }
+
+ if err := untaintField(c, &formdata.Description, cfg.RegDuration, "description"); err != nil {
+ return err
+ }
+
+ // lookup orig entry
+ response, err := db.Get(apicontext, id, common.TypeForm)
+ if err != nil || len(response.Forms) == 0 {
+ return JsonStatus(c, fiber.StatusForbidden,
+ "No form with that id could be found!")
+ }
+
+ form := response.Forms[0]
+
+ // modify fields
+ if formdata.Expire != "" {
+ form.Expire = formdata.Expire
+ }
+
+ if formdata.Notify != "" {
+ form.Notify = formdata.Notify
+ }
+
+ if formdata.Description != "" {
+ form.Description = formdata.Description
+ }
+
+ // run in foreground because we need the feedback here
+ if err := db.Insert(id, form); err != nil {
+ return JsonStatus(c, fiber.StatusForbidden,
+ "Failed to insert: "+err.Error())
+ }
+
+ res := &common.Response{Forms: []*common.Form{form}}
+ res.Success = true
+ res.Code = fiber.StatusOK
+ return c.Status(fiber.StatusOK).JSON(res)
+}
diff --git a/api/server.go b/api/server.go
index 4ec61d3..5e7fe99 100644
--- a/api/server.go
+++ b/api/server.go
@@ -76,6 +76,11 @@ func Runserver(conf *cfg.Config, args []string) error {
return UploadDescribe(c, conf, db)
})
+ // modify
+ api.Put("/uploads/:id", auth, func(c *fiber.Ctx) error {
+ return UploadModify(c, conf, db)
+ })
+
// download w/o expire
api.Get("/uploads/:id/file", auth, func(c *fiber.Ctx) error {
return UploadFetch(c, conf, db)
@@ -101,6 +106,11 @@ func Runserver(conf *cfg.Config, args []string) error {
api.Get("/forms/:id", auth, func(c *fiber.Ctx) error {
return FormDescribe(c, conf, db)
})
+
+ // modify
+ api.Put("/forms/:id", auth, func(c *fiber.Ctx) error {
+ return FormModify(c, conf, db)
+ })
}
// public routes
diff --git a/api/upload_handlers.go b/api/upload_handlers.go
index e02dcd4..16bfbe4 100644
--- a/api/upload_handlers.go
+++ b/api/upload_handlers.go
@@ -328,3 +328,64 @@ func UploadDescribe(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
return c.Status(fiber.StatusOK).JSON(response)
}
+
+func UploadModify(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
+ var formdata common.Upload
+
+ // retrieve the API Context name from the session
+ apicontext, err := SessionGetApicontext(c)
+ if err != nil {
+ return JsonStatus(c, fiber.StatusInternalServerError,
+ "Unable to initialize session store from context: "+err.Error())
+ }
+
+ id, err := common.Untaint(c.Params("id"), cfg.RegKey)
+ if err != nil {
+ return JsonStatus(c, fiber.StatusForbidden,
+ "Invalid id provided!")
+ }
+
+ // extract form data
+ if err := c.BodyParser(&formdata); err != nil {
+ return JsonStatus(c, fiber.StatusInternalServerError,
+ "bodyparser error : "+err.Error())
+ }
+
+ // post process input data
+ if err := untaintField(c, &formdata.Expire, cfg.RegDuration, "expire data"); err != nil {
+ return err
+ }
+
+ if err := untaintField(c, &formdata.Description, cfg.RegDuration, "description"); err != nil {
+ return err
+ }
+
+ // lookup orig entry
+ response, err := db.Get(apicontext, id, common.TypeUpload)
+ if err != nil || len(response.Uploads) == 0 {
+ return JsonStatus(c, fiber.StatusForbidden,
+ "No upload with that id could be found!")
+ }
+
+ upload := response.Uploads[0]
+
+ // modify fields
+ if formdata.Expire != "" {
+ upload.Expire = formdata.Expire
+ }
+
+ if formdata.Description != "" {
+ upload.Description = formdata.Description
+ }
+
+ // run in foreground because we need the feedback here
+ if err := db.Insert(id, upload); err != nil {
+ return JsonStatus(c, fiber.StatusForbidden,
+ "Failed to insert: "+err.Error())
+ }
+
+ res := &common.Response{Uploads: []*common.Upload{upload}}
+ res.Success = true
+ res.Code = fiber.StatusOK
+ return c.Status(fiber.StatusOK).JSON(res)
+}
diff --git a/cfg/config.go b/cfg/config.go
index eb04af6..1935502 100644
--- a/cfg/config.go
+++ b/cfg/config.go
@@ -23,7 +23,7 @@ import (
"time"
)
-const Version string = "v0.0.1"
+const Version string = "v0.0.2"
var VERSION string // maintained by -x
diff --git a/mkrel.sh b/mkrel.sh
new file mode 100755
index 0000000..158e2ba
--- /dev/null
+++ b/mkrel.sh
@@ -0,0 +1,81 @@
+#!/bin/bash
+
+# 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 .
+
+
+# get list with: go tool dist list
+DIST="darwin/amd64
+freebsd/amd64
+linux/amd64
+windows/amd64"
+
+daemon="$1"
+client="$2"
+version="$3"
+
+if test -z "$version"; then
+ echo "Usage: $0 "
+ exit 1
+fi
+
+rm -rf releases
+mkdir -p releases
+
+
+for D in $DIST; do
+ os=${D/\/*/}
+ arch=${D/*\//}
+ binfile="releases/${daemon}-${os}-${arch}-${version}"
+ clientfile="releases/${client}-${os}-${arch}-${version}"
+ tardir="${daemon}-${os}-${arch}-${version}"
+ tarfile="releases/${daemon}-${os}-${arch}-${version}.tar.gz"
+ set -x
+
+ GOOS=${os} GOARCH=${arch} go build -o ${binfile} -ldflags "-X 'github.com/tlinden/ephemerup/cfg.VERSION=${version}'"
+ cd $client
+ GOOS=${os} GOARCH=${arch} go build -o ../${clientfile} -ldflags "-X 'github.com/tlinden/ephemerup/upctl/cfg.VERSION=${version}'"
+ cd -
+
+ mkdir -p ${tardir}
+ cp ${binfile} ${clientfile} README.md LICENSE ${tardir}/
+
+ echo 'daemon = ephemerupd
+client = upctl
+PREFIX = /usr/local
+UID = root
+GID = 0
+
+install-client:
+ install -d -o $(UID) -g $(GID) $(PREFIX)/bin
+ install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1
+ install -o $(UID) -g $(GID) -m 555 $(client) $(PREFIX)/bin/
+ install -o $(UID) -g $(GID) -m 444 $(client).1 $(PREFIX)/man/man1/
+
+install: install-client
+ install -d -o $(UID) -g $(GID) $(PREFIX)/sbin
+ install -o $(UID) -g $(GID) -m 555 $(daemon) $(PREFIX)/sbin/
+ install -o $(UID) -g $(GID) -m 444 $(daemon).1 $(PREFIX)/man/man1/' > ${tardir}/Makefile
+
+ tar cpzf ${tarfile} ${tardir}
+
+ for file in ${binfile} ${tarfile} ${clientfile}; do
+ sha256sum ${file} | cut -d' ' -f1 > ${file}.sha256
+ done
+
+ rm -rf ${tardir}
+ set +x
+done
+
diff --git a/upctl/cmd/formcommands.go b/upctl/cmd/formcommands.go
index 15929a9..e1973d8 100644
--- a/upctl/cmd/formcommands.go
+++ b/upctl/cmd/formcommands.go
@@ -48,6 +48,7 @@ func FormCommand(conf *cfg.Config) *cobra.Command {
formCmd.AddCommand(FormListCommand(conf))
formCmd.AddCommand(FormDeleteCommand(conf))
formCmd.AddCommand(FormDescribeCommand(conf))
+ formCmd.AddCommand(FormModifyCommand(conf))
return formCmd
}
@@ -66,9 +67,12 @@ func FormCreateCommand(conf *cfg.Config) *cobra.Command {
}
// options
- formCreateCmd.PersistentFlags().StringVarP(&conf.Expire, "expire", "e", "", "Expire setting: asap or duration (accepted shortcuts: dmh)")
- formCreateCmd.PersistentFlags().StringVarP(&conf.Description, "description", "D", "", "Description of the form")
- formCreateCmd.PersistentFlags().StringVarP(&conf.Notify, "notify", "n", "", "Email address to get notified when consumer has uploaded files")
+ formCreateCmd.PersistentFlags().StringVarP(&conf.Expire, "expire", "e", "",
+ "Expire setting: asap or duration (accepted shortcuts: dmh)")
+ formCreateCmd.PersistentFlags().StringVarP(&conf.Description, "description", "D", "",
+ "Description of the form")
+ formCreateCmd.PersistentFlags().StringVarP(&conf.Notify, "notify", "n", "",
+ "Email address to get notified when consumer has uploaded files")
formCreateCmd.Aliases = append(formCreateCmd.Aliases, "add")
formCreateCmd.Aliases = append(formCreateCmd.Aliases, "+")
@@ -76,6 +80,37 @@ func FormCreateCommand(conf *cfg.Config) *cobra.Command {
return formCreateCmd
}
+func FormModifyCommand(conf *cfg.Config) *cobra.Command {
+ var formModifyCmd = &cobra.Command{
+ Use: "modify [options] ",
+ Short: "Modify a form",
+ Long: `Modify an existing form.`,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ if len(args) == 0 {
+ return cmd.Help()
+ }
+
+ // errors at this stage do not cause the usage to be shown
+ cmd.SilenceUsage = true
+
+ return lib.Modify(os.Stdout, conf, args, common.TypeForm)
+ },
+ }
+
+ // options
+ formModifyCmd.PersistentFlags().StringVarP(&conf.Expire, "expire", "e", "",
+ "Expire setting: asap or duration (accepted shortcuts: dmh)")
+ formModifyCmd.PersistentFlags().StringVarP(&conf.Description, "description", "D", "",
+ "Description of the form")
+ formModifyCmd.PersistentFlags().StringVarP(&conf.Notify, "notify", "n", "",
+ "Email address to get notified when consumer has uploaded files")
+
+ formModifyCmd.Aliases = append(formModifyCmd.Aliases, "mod")
+ formModifyCmd.Aliases = append(formModifyCmd.Aliases, "change")
+
+ return formModifyCmd
+}
+
func FormListCommand(conf *cfg.Config) *cobra.Command {
var listCmd = &cobra.Command{
Use: "list [options]",
diff --git a/upctl/cmd/maincommands.go b/upctl/cmd/maincommands.go
index fc20d62..a6254d9 100644
--- a/upctl/cmd/maincommands.go
+++ b/upctl/cmd/maincommands.go
@@ -146,3 +146,32 @@ func DownloadCommand(conf *cfg.Config) *cobra.Command {
return listCmd
}
+
+func ModifyCommand(conf *cfg.Config) *cobra.Command {
+ var uploadModifyCmd = &cobra.Command{
+ Use: "modify [options] ",
+ Short: "Modify an upload",
+ Long: `Modify an existing upload.`,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ if len(args) == 0 {
+ return cmd.Help()
+ }
+
+ // errors at this stage do not cause the usage to be shown
+ cmd.SilenceUsage = true
+
+ return lib.Modify(os.Stdout, conf, args, common.TypeUpload)
+ },
+ }
+
+ // options
+ uploadModifyCmd.PersistentFlags().StringVarP(&conf.Expire, "expire", "e", "",
+ "Expire setting: asap or duration (accepted shortcuts: dmh)")
+ uploadModifyCmd.PersistentFlags().StringVarP(&conf.Description, "description", "D", "",
+ "Description of the upload")
+
+ uploadModifyCmd.Aliases = append(uploadModifyCmd.Aliases, "mod")
+ uploadModifyCmd.Aliases = append(uploadModifyCmd.Aliases, "change")
+
+ return uploadModifyCmd
+}
diff --git a/upctl/cmd/root.go b/upctl/cmd/root.go
index 7e52e52..0aeebda 100644
--- a/upctl/cmd/root.go
+++ b/upctl/cmd/root.go
@@ -92,6 +92,9 @@ func Execute() {
rootCmd.AddCommand(DeleteCommand(&conf))
rootCmd.AddCommand(DescribeCommand(&conf))
rootCmd.AddCommand(DownloadCommand(&conf))
+ rootCmd.AddCommand(ModifyCommand(&conf))
+
+ // forms are being handled with its own subcommand
rootCmd.AddCommand(FormCommand(&conf))
err := rootCmd.Execute()
diff --git a/upctl/lib/client.go b/upctl/lib/client.go
index 4f7c560..cc90f8d 100644
--- a/upctl/lib/client.go
+++ b/upctl/lib/client.go
@@ -357,6 +357,43 @@ func Download(w io.Writer, c *cfg.Config, args []string) error {
return nil
}
+func Modify(w io.Writer, c *cfg.Config, args []string, typ int) error {
+ id := args[0]
+ var rq *Request
+
+ // setup url, req.Request, timeout handling etc
+ switch typ {
+ case common.TypeUpload:
+ rq = Setup(c, "/uploads/"+id)
+ rq.R.
+ SetBody(&common.Upload{
+ Expire: c.Expire,
+ Description: c.Description,
+ })
+ case common.TypeForm:
+ rq = Setup(c, "/forms/"+id)
+ rq.R.
+ SetBody(&common.Form{
+ Expire: c.Expire,
+ Description: c.Description,
+ Notify: c.Notify,
+ })
+ }
+
+ // actual put w/ settings
+ resp, err := rq.R.Put(rq.Url)
+
+ if err != nil {
+ return err
+ }
+
+ if err := HandleResponse(c, resp); err != nil {
+ return err
+ }
+
+ return RespondExtended(w, resp)
+}
+
/**** Forms stuff ****/
func CreateForm(w io.Writer, c *cfg.Config) error {
// setup url, req.Request, timeout handling etc
diff --git a/upctl/lib/output.go b/upctl/lib/output.go
index ef250c0..cdc26ad 100644
--- a/upctl/lib/output.go
+++ b/upctl/lib/output.go
@@ -80,7 +80,7 @@ func WriteExtended(w io.Writer, response *common.Response) {
for _, entry := range response.Uploads {
expire := prepareExpire(entry.Expire, entry.Created)
fmt.Fprintf(w, format, "Upload-Id", entry.Id)
- fmt.Fprintf(w, format, "Description", 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)