mirror of
https://codeberg.org/scip/ephemerup.git
synced 2025-12-17 12:40:57 +01:00
changes:
- added cleaner goroutine - added delete cmd - added list cmd - refactoring
This commit is contained in:
@@ -3,15 +3,10 @@ Simple standalone file upload server with api and cli
|
|||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- implement goroutine to expire after 1d, 10m etc
|
|
||||||
implemented. add go routine to server, use Db.Iter()
|
|
||||||
- use bolt db to retrieve list of items to expire
|
|
||||||
- also serve a html upload page
|
- also serve a html upload page
|
||||||
- add auth options (access key, users, roles, oauth2)
|
|
||||||
- add metrics
|
- add metrics
|
||||||
- add upctl command to remove a file
|
|
||||||
- use global map of api endpoints like /file/get/ etc
|
|
||||||
- create cobra client commands (upload, list, delete, edit)
|
- create cobra client commands (upload, list, delete, edit)
|
||||||
|
- add authorization checks for delete and list based on apicontext
|
||||||
|
|
||||||
## BUGS
|
## BUGS
|
||||||
|
|
||||||
|
|||||||
47
upctl/cmd/delete.go
Normal file
47
upctl/cmd/delete.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
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 DeleteCommand(conf *cfg.Config) *cobra.Command {
|
||||||
|
var deleteCmd = &cobra.Command{
|
||||||
|
Use: "delete [options] <id>",
|
||||||
|
Short: "delete an upload",
|
||||||
|
Long: `Delete an upload 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(conf, args)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteCmd.Aliases = append(deleteCmd.Aliases, "rm")
|
||||||
|
deleteCmd.Aliases = append(deleteCmd.Aliases, "d")
|
||||||
|
|
||||||
|
return deleteCmd
|
||||||
|
}
|
||||||
@@ -38,5 +38,8 @@ func ListCommand(conf *cfg.Config) *cobra.Command {
|
|||||||
// options
|
// options
|
||||||
listCmd.PersistentFlags().StringVarP(&conf.Apicontext, "apicontext", "", "", "Filter by given API context")
|
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
|
return listCmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ func Execute() {
|
|||||||
|
|
||||||
rootCmd.AddCommand(UploadCommand(&conf))
|
rootCmd.AddCommand(UploadCommand(&conf))
|
||||||
rootCmd.AddCommand(ListCommand(&conf))
|
rootCmd.AddCommand(ListCommand(&conf))
|
||||||
|
rootCmd.AddCommand(DeleteCommand(&conf))
|
||||||
|
|
||||||
err := rootCmd.Execute()
|
err := rootCmd.Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -36,12 +36,15 @@ func UploadCommand(conf *cfg.Config) *cobra.Command {
|
|||||||
// errors at this stage do not cause the usage to be shown
|
// errors at this stage do not cause the usage to be shown
|
||||||
cmd.SilenceUsage = true
|
cmd.SilenceUsage = true
|
||||||
|
|
||||||
return lib.Upload(conf, args)
|
return lib.UploadFiles(conf, args)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// options
|
// options
|
||||||
uploadCmd.PersistentFlags().StringVarP(&conf.Expire, "expire", "e", "", "Expire setting: asap or duration (accepted shortcuts: dmh)")
|
uploadCmd.PersistentFlags().StringVarP(&conf.Expire, "expire", "e", "", "Expire setting: asap or duration (accepted shortcuts: dmh)")
|
||||||
|
|
||||||
|
uploadCmd.Aliases = append(uploadCmd.Aliases, "up")
|
||||||
|
uploadCmd.Aliases = append(uploadCmd.Aliases, "u")
|
||||||
|
|
||||||
return uploadCmd
|
return uploadCmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ require (
|
|||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||||
github.com/quic-go/qpack v0.4.0 // indirect
|
github.com/quic-go/qpack v0.4.0 // indirect
|
||||||
|
|||||||
@@ -150,8 +150,12 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
|
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||||
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
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/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||||
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
||||||
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
|
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
|
||||||
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
|
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
|
||||||
|
|||||||
@@ -43,6 +43,22 @@ type ListParams struct {
|
|||||||
Apicontext string `json:"apicontext"`
|
Apicontext string `json:"apicontext"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 Timestamp `json:"uploaded"`
|
||||||
|
Context string `json:"context"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Uploads struct {
|
||||||
|
Entries []*Upload `json:"uploads"`
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
func Setup(c *cfg.Config, path string) *Request {
|
func Setup(c *cfg.Config, path string) *Request {
|
||||||
client := req.C()
|
client := req.C()
|
||||||
if c.Debug {
|
if c.Debug {
|
||||||
@@ -107,7 +123,7 @@ func GatherFiles(rq *Request, args []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Upload(c *cfg.Config, args []string) error {
|
func UploadFiles(c *cfg.Config, args []string) error {
|
||||||
// setup url, req.Request, timeout handling etc
|
// setup url, req.Request, timeout handling etc
|
||||||
rq := Setup(c, "/file/")
|
rq := Setup(c, "/file/")
|
||||||
|
|
||||||
@@ -165,7 +181,10 @@ func HandleResponse(c *cfg.Config, resp *req.Response) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// all right
|
// all right
|
||||||
fmt.Println(r.Message)
|
if r.Message != "" {
|
||||||
|
fmt.Println(r.Message)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,11 +196,46 @@ func List(c *cfg.Config, args []string) error {
|
|||||||
SetBodyJsonMarshal(params).
|
SetBodyJsonMarshal(params).
|
||||||
Get(rq.Url)
|
Get(rq.Url)
|
||||||
|
|
||||||
fmt.Println("")
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return HandleResponse(c, resp)
|
uploads := Uploads{}
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(resp.String()), &uploads); err != nil {
|
||||||
|
return errors.New("Could not unmarshall JSON response: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !uploads.Success {
|
||||||
|
return errors.New(uploads.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tablewriter
|
||||||
|
data := [][]string{}
|
||||||
|
for _, entry := range uploads.Entries {
|
||||||
|
data = append(data, []string{
|
||||||
|
entry.Id, entry.Expire, entry.Context, entry.Uploaded.Format("2006-01-02 15:04:05"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return WriteTable([]string{"ID", "EXPIRE", "CONTEXT", "UPLOADED"}, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Delete(c *cfg.Config, args []string) error {
|
||||||
|
for _, id := range args {
|
||||||
|
rq := Setup(c, "/file/"+id+"/")
|
||||||
|
|
||||||
|
resp, err := rq.R.Delete(rq.Url)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := HandleResponse(c, resp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Upload %s successfully deleted.\n", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
50
upctl/lib/output.go
Normal file
50
upctl/lib/output.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
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 lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/olekukonko/tablewriter"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WriteTable(headers []string, data [][]string) error {
|
||||||
|
table := tablewriter.NewWriter(os.Stdout)
|
||||||
|
|
||||||
|
table.SetHeader(headers)
|
||||||
|
table.AppendBulk(data)
|
||||||
|
|
||||||
|
// for _, row := range data.entries {
|
||||||
|
// table.Append(trimRow(row))
|
||||||
|
// }
|
||||||
|
|
||||||
|
table.SetAutoWrapText(false)
|
||||||
|
table.SetAutoFormatHeaders(true)
|
||||||
|
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
|
||||||
|
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
||||||
|
table.SetCenterSeparator("")
|
||||||
|
table.SetColumnSeparator("")
|
||||||
|
table.SetRowSeparator("")
|
||||||
|
table.SetHeaderLine(false)
|
||||||
|
table.SetBorder(false)
|
||||||
|
table.SetTablePadding("\t")
|
||||||
|
table.SetNoWhiteSpace(true)
|
||||||
|
|
||||||
|
table.Render()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
65
upctl/lib/timestamp.go
Normal file
65
upctl/lib/timestamp.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
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 lib
|
||||||
|
|
||||||
|
// FIXME: import from upd!!!!
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://gist.github.com/rhcarvalho/9338c3ff8850897c68bc74797c5dc25b
|
||||||
|
|
||||||
|
// Timestamp is like time.Time, but knows how to unmarshal from JSON
|
||||||
|
// Unix timestamp numbers or RFC3339 strings, and marshal back into
|
||||||
|
// the same JSON representation.
|
||||||
|
type Timestamp struct {
|
||||||
|
time.Time
|
||||||
|
rfc3339 bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Timestamp) MarshalJSON() ([]byte, error) {
|
||||||
|
if t.rfc3339 {
|
||||||
|
return t.Time.MarshalJSON()
|
||||||
|
}
|
||||||
|
return t.formatUnix()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Timestamp) UnmarshalJSON(data []byte) error {
|
||||||
|
err := t.Time.UnmarshalJSON(data)
|
||||||
|
if err != nil {
|
||||||
|
return t.parseUnix(data)
|
||||||
|
}
|
||||||
|
t.rfc3339 = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Timestamp) formatUnix() ([]byte, error) {
|
||||||
|
sec := float64(t.Time.UnixNano()) * float64(time.Nanosecond) / float64(time.Second)
|
||||||
|
return strconv.AppendFloat(nil, sec, 'f', -1, 64), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Timestamp) parseUnix(data []byte) error {
|
||||||
|
f, err := strconv.ParseFloat(string(data), 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.Time = time.Unix(0, int64(f*float64(time.Second/time.Nanosecond)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
85
upd/api/cleaner.go
Normal file
85
upd/api/cleaner.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
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 api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
//"github.com/alecthomas/repr"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/tlinden/up/upd/cfg"
|
||||||
|
bolt "go.etcd.io/bbolt"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DeleteExpiredUploads(conf *cfg.Config, db *Db) error {
|
||||||
|
err := db.bolt.Update(func(tx *bolt.Tx) error {
|
||||||
|
bucket := tx.Bucket([]byte(Bucket))
|
||||||
|
|
||||||
|
if bucket == nil {
|
||||||
|
return nil // nothin to delete so far
|
||||||
|
}
|
||||||
|
|
||||||
|
err := bucket.ForEach(func(id, j []byte) error {
|
||||||
|
upload := &Upload{}
|
||||||
|
if err := json.Unmarshal(j, &upload); err != nil {
|
||||||
|
return fmt.Errorf("unable to unmarshal json: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsExpired(conf, upload.Uploaded.Time, upload.Expire) {
|
||||||
|
if err := bucket.Delete([]byte(id)); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup(filepath.Join(conf.StorageDir, upload.Id))
|
||||||
|
|
||||||
|
Log("Cleaned up upload " + upload.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
Log("DB error: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func BackgroundCleaner(conf *cfg.Config, db *Db) chan bool {
|
||||||
|
ticker := time.NewTicker(conf.CleanInterval)
|
||||||
|
fmt.Println(conf.CleanInterval)
|
||||||
|
done := make(chan bool)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
DeleteExpiredUploads(conf, db)
|
||||||
|
case <-done:
|
||||||
|
ticker.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return done
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/tlinden/up/upd/cfg"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
@@ -39,6 +40,29 @@ type Meta struct {
|
|||||||
Expire string `json:"expire" form:"expire"`
|
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 Timestamp `json:"uploaded"`
|
||||||
|
Context string `json:"context"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// this one is also used for marshalling to the client
|
||||||
|
type Uploads struct {
|
||||||
|
Entries []*Upload `json:"uploads"`
|
||||||
|
|
||||||
|
// integrate the Result struct so we can signal success
|
||||||
|
Result
|
||||||
|
}
|
||||||
|
|
||||||
|
// incoming id
|
||||||
|
type Id struct {
|
||||||
|
Id string `json:"name" xml:"name" form:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
// vaious helbers
|
// vaious helbers
|
||||||
func Log(format string, values ...any) {
|
func Log(format string, values ...any) {
|
||||||
fmt.Printf("[DEBUG] "+format+"\n", values...)
|
fmt.Printf("[DEBUG] "+format+"\n", values...)
|
||||||
@@ -49,12 +73,6 @@ func Ts() string {
|
|||||||
return t.Format("2006-01-02-15-04-")
|
return t.Format("2006-01-02-15-04-")
|
||||||
}
|
}
|
||||||
|
|
||||||
func NormalizeFilename(file string) string {
|
|
||||||
r := regexp.MustCompile(`[^\w\d\-_\\.]`)
|
|
||||||
|
|
||||||
return Ts() + r.ReplaceAllString(file, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
We could use time.ParseDuration(), but this doesn't support days.
|
We could use time.ParseDuration(), but this doesn't support days.
|
||||||
|
|
||||||
@@ -96,9 +114,16 @@ func duration2int(duration string) int {
|
|||||||
aka:
|
aka:
|
||||||
if(now - start) >= duration { time is up}
|
if(now - start) >= duration { time is up}
|
||||||
*/
|
*/
|
||||||
func IsExpired(start time.Time, duration string) bool {
|
func IsExpired(conf *cfg.Config, start time.Time, duration string) bool {
|
||||||
|
var expiretime int // seconds
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
expiretime := duration2int(duration)
|
|
||||||
|
if duration == "asap" {
|
||||||
|
expiretime = conf.DefaultExpire
|
||||||
|
} else {
|
||||||
|
expiretime = duration2int(duration)
|
||||||
|
}
|
||||||
|
|
||||||
if now.Unix()-start.Unix() >= int64(expiretime) {
|
if now.Unix()-start.Unix() >= int64(expiretime) {
|
||||||
return true
|
return true
|
||||||
@@ -120,9 +145,8 @@ func IsExpired(start time.Time, duration string) bool {
|
|||||||
it. You may ignore the error and use the untainted string or bail
|
it. You may ignore the error and use the untainted string or bail
|
||||||
out.
|
out.
|
||||||
*/
|
*/
|
||||||
func Untaint(input string, wanted string) (string, error) {
|
func Untaint(input string, wanted *regexp.Regexp) (string, error) {
|
||||||
re := regexp.MustCompile(wanted)
|
untainted := wanted.ReplaceAllString(input, "")
|
||||||
untainted := re.ReplaceAllString(input, "")
|
|
||||||
|
|
||||||
if len(untainted) != len(input) {
|
if len(untainted) != len(input) {
|
||||||
return untainted, errors.New("Invalid input string!")
|
return untainted, errors.New("Invalid input string!")
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/alecthomas/repr"
|
//"github.com/alecthomas/repr"
|
||||||
bolt "go.etcd.io/bbolt"
|
bolt "go.etcd.io/bbolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,20 +31,6 @@ type Db struct {
|
|||||||
bolt *bolt.DB
|
bolt *bolt.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 Timestamp `json:"uploaded"`
|
|
||||||
Context string `json:"context"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Uploads struct {
|
|
||||||
Entries []*Upload `json:"uploads"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDb(file string) (*Db, error) {
|
func NewDb(file string) (*Db, error) {
|
||||||
b, err := bolt.Open(file, 0600, nil)
|
b, err := bolt.Open(file, 0600, nil)
|
||||||
db := Db{bolt: b}
|
db := Db{bolt: b}
|
||||||
@@ -89,6 +75,11 @@ func (db *Db) Lookup(id string) (Upload, error) {
|
|||||||
|
|
||||||
err := db.bolt.View(func(tx *bolt.Tx) error {
|
err := db.bolt.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(Bucket))
|
bucket := tx.Bucket([]byte(Bucket))
|
||||||
|
|
||||||
|
if bucket == nil {
|
||||||
|
return fmt.Errorf("id %s not found", id)
|
||||||
|
}
|
||||||
|
|
||||||
j := bucket.Get([]byte(id))
|
j := bucket.Get([]byte(id))
|
||||||
|
|
||||||
if len(j) == 0 {
|
if len(j) == 0 {
|
||||||
@@ -114,6 +105,10 @@ func (db *Db) Delete(id string) error {
|
|||||||
err := db.bolt.Update(func(tx *bolt.Tx) error {
|
err := db.bolt.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(Bucket))
|
bucket := tx.Bucket([]byte(Bucket))
|
||||||
|
|
||||||
|
if bucket == nil {
|
||||||
|
return fmt.Errorf("id %s not found", id)
|
||||||
|
}
|
||||||
|
|
||||||
j := bucket.Get([]byte(id))
|
j := bucket.Get([]byte(id))
|
||||||
|
|
||||||
if len(j) == 0 {
|
if len(j) == 0 {
|
||||||
@@ -136,6 +131,10 @@ func (db *Db) List(apicontext string) (*Uploads, error) {
|
|||||||
|
|
||||||
err := db.bolt.View(func(tx *bolt.Tx) error {
|
err := db.bolt.View(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(Bucket))
|
bucket := tx.Bucket([]byte(Bucket))
|
||||||
|
if bucket == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
err := bucket.ForEach(func(id, j []byte) error {
|
err := bucket.ForEach(func(id, j []byte) error {
|
||||||
upload := &Upload{}
|
upload := &Upload{}
|
||||||
if err := json.Unmarshal(j, &upload); err != nil {
|
if err := json.Unmarshal(j, &upload); err != nil {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func cleanup(dir string) {
|
|||||||
func SaveFormFiles(c *fiber.Ctx, cfg *cfg.Config, files []*multipart.FileHeader, id string) ([]string, error) {
|
func SaveFormFiles(c *fiber.Ctx, cfg *cfg.Config, files []*multipart.FileHeader, id string) ([]string, error) {
|
||||||
members := []string{}
|
members := []string{}
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
filename := NormalizeFilename(filepath.Base(file.Filename))
|
filename, _ := Untaint(filepath.Base(file.Filename), cfg.RegNormalizedFilename)
|
||||||
path := filepath.Join(cfg.StorageDir, id, filename)
|
path := filepath.Join(cfg.StorageDir, id, filename)
|
||||||
members = append(members, filename)
|
members = append(members, filename)
|
||||||
Log("Received: %s => %s/%s", file.Filename, id, filename)
|
Log("Received: %s => %s/%s", file.Filename, id, filename)
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/tlinden/up/upd/cfg"
|
"github.com/tlinden/up/upd/cfg"
|
||||||
|
|
||||||
"encoding/json"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
@@ -77,7 +76,7 @@ func FilePut(c *fiber.Ctx, cfg *cfg.Config, db *Db) (string, error) {
|
|||||||
if len(formdata.Expire) == 0 {
|
if len(formdata.Expire) == 0 {
|
||||||
entry.Expire = "asap"
|
entry.Expire = "asap"
|
||||||
} else {
|
} else {
|
||||||
ex, err := Untaint(formdata.Expire, `[^dhms0-9]`) // duration or asap allowed
|
ex, err := Untaint(formdata.Expire, cfg.RegDuration) // duration or asap allowed
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -119,7 +118,7 @@ func FileGet(c *fiber.Ctx, cfg *cfg.Config, db *Db, shallExpire ...bool) error {
|
|||||||
|
|
||||||
// we ignore c.Params("file"), cause it may be malign. Also we've
|
// we ignore c.Params("file"), cause it may be malign. Also we've
|
||||||
// got it in the db anyway
|
// got it in the db anyway
|
||||||
id, err := Untaint(c.Params("id"), `[^a-zA-Z0-9\-]`)
|
id, err := Untaint(c.Params("id"), cfg.RegKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(403, "Invalid id provided!")
|
return fiber.NewError(403, "Invalid id provided!")
|
||||||
}
|
}
|
||||||
@@ -157,14 +156,10 @@ func FileGet(c *fiber.Ctx, cfg *cfg.Config, db *Db, shallExpire ...bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
type Id struct {
|
// delete file, id dir and db entry
|
||||||
Id string `json:"name" xml:"name" form:"name"`
|
func DeleteUpload(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
|
||||||
}
|
|
||||||
|
|
||||||
func FileDelete(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
|
id, err := Untaint(c.Params("id"), cfg.RegKey)
|
||||||
// delete file, id dir and db entry
|
|
||||||
|
|
||||||
id, err := Untaint(c.Params("id"), `[^a-zA-Z0-9\-]`)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(403, "Invalid id provided!")
|
return fiber.NewError(403, "Invalid id provided!")
|
||||||
}
|
}
|
||||||
@@ -184,23 +179,24 @@ func FileDelete(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func List(c *fiber.Ctx, cfg *cfg.Config, db *Db) (string, error) {
|
// returns the whole list + error code, no post processing by server
|
||||||
apicontext, err := Untaint(c.Params("apicontext"), `[^a-zA-Z0-9\-]`)
|
func List(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
|
||||||
|
apicontext, err := Untaint(c.Params("apicontext"), cfg.RegKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fiber.NewError(403, "Invalid api context provided!")
|
return JsonStatus(c, fiber.StatusForbidden,
|
||||||
|
"Invalid api context provided!")
|
||||||
}
|
}
|
||||||
|
|
||||||
uploads, err := db.List(apicontext)
|
uploads, err := db.List(apicontext)
|
||||||
repr.Print(uploads)
|
repr.Print(uploads)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fiber.NewError(500, "Unable to list uploads: "+err.Error())
|
return JsonStatus(c, fiber.StatusForbidden,
|
||||||
|
"Unable to list uploads: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonlist, err := json.Marshal(uploads)
|
// if we reached this point we can signal success
|
||||||
if err != nil {
|
uploads.Success = true
|
||||||
return "", fiber.NewError(500, "json marshalling failure: "+err.Error())
|
uploads.Code = fiber.StatusOK
|
||||||
}
|
|
||||||
|
|
||||||
Log(string(jsonlist))
|
return c.Status(fiber.StatusOK).JSON(uploads)
|
||||||
return string(jsonlist), nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ package api
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/compress"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||||
"github.com/gofiber/fiber/v2/middleware/requestid"
|
"github.com/gofiber/fiber/v2/middleware/requestid"
|
||||||
"github.com/gofiber/fiber/v2/middleware/session"
|
"github.com/gofiber/fiber/v2/middleware/session"
|
||||||
@@ -30,84 +32,140 @@ import (
|
|||||||
// sessions are context specific and can be global savely
|
// sessions are context specific and can be global savely
|
||||||
var Sessionstore *session.Store
|
var Sessionstore *session.Store
|
||||||
|
|
||||||
func Runserver(cfg *cfg.Config, args []string) error {
|
const shallExpire = true
|
||||||
|
|
||||||
|
func Runserver(conf *cfg.Config, args []string) error {
|
||||||
|
// required for authenticated routes, used to store the api context
|
||||||
Sessionstore = session.New()
|
Sessionstore = session.New()
|
||||||
|
|
||||||
router := fiber.New(fiber.Config{
|
// bbolt db setup
|
||||||
CaseSensitive: true,
|
db, err := NewDb(conf.DbFile)
|
||||||
StrictRouting: true,
|
|
||||||
Immutable: true,
|
|
||||||
Prefork: cfg.Prefork,
|
|
||||||
ServerHeader: "upd",
|
|
||||||
AppName: cfg.AppName,
|
|
||||||
BodyLimit: cfg.BodyLimit,
|
|
||||||
Network: cfg.Network,
|
|
||||||
})
|
|
||||||
|
|
||||||
router.Use(requestid.New())
|
|
||||||
router.Use(logger.New(logger.Config{
|
|
||||||
Format: "${pid} ${locals:requestid} ${status} - ${method} ${path}\n",
|
|
||||||
}))
|
|
||||||
|
|
||||||
db, err := NewDb(cfg.DbFile)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
AuthSetEndpoints(cfg.ApiPrefix, ApiVersion, []string{"/file"})
|
// setup authenticated endpoints
|
||||||
AuthSetApikeys(cfg.Apicontext)
|
auth := SetupAuthStore(conf)
|
||||||
|
|
||||||
auth := keyauth.New(keyauth.Config{
|
// setup api server
|
||||||
Validator: AuthValidateAPIKey,
|
router := SetupServer(conf)
|
||||||
ErrorHandler: AuthErrHandler,
|
|
||||||
})
|
|
||||||
|
|
||||||
shallExpire := true
|
// authenticated routes
|
||||||
|
api := router.Group(conf.ApiPrefix + ApiVersion)
|
||||||
api := router.Group(cfg.ApiPrefix + ApiVersion)
|
|
||||||
{
|
{
|
||||||
// authenticated routes
|
// authenticated routes
|
||||||
api.Post("/file/", auth, func(c *fiber.Ctx) error {
|
api.Post("/file/", auth, func(c *fiber.Ctx) error {
|
||||||
msg, err := FilePut(c, cfg, db)
|
msg, err := FilePut(c, conf, db)
|
||||||
return SendResponse(c, msg, err)
|
return SendResponse(c, msg, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
api.Get("/file/:id/:file", auth, func(c *fiber.Ctx) error {
|
api.Get("/file/:id/:file", auth, func(c *fiber.Ctx) error {
|
||||||
return FileGet(c, cfg, db)
|
return FileGet(c, conf, db)
|
||||||
})
|
})
|
||||||
|
|
||||||
api.Get("/file/:id/", auth, func(c *fiber.Ctx) error {
|
api.Get("/file/:id/", auth, func(c *fiber.Ctx) error {
|
||||||
return FileGet(c, cfg, db)
|
return FileGet(c, conf, db)
|
||||||
})
|
})
|
||||||
|
|
||||||
api.Delete("/file/:id/", auth, func(c *fiber.Ctx) error {
|
api.Delete("/file/:id/", auth, func(c *fiber.Ctx) error {
|
||||||
return FileDelete(c, cfg, db)
|
err := DeleteUpload(c, conf, db)
|
||||||
|
return SendResponse(c, "", err)
|
||||||
})
|
})
|
||||||
|
|
||||||
api.Get("/list/", auth, func(c *fiber.Ctx) error {
|
api.Get("/list/", auth, func(c *fiber.Ctx) error {
|
||||||
msg, err := List(c, cfg, db)
|
return List(c, conf, db)
|
||||||
return SendResponse(c, msg, err)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// public routes
|
// public routes
|
||||||
router.Get("/", func(c *fiber.Ctx) error {
|
{
|
||||||
return c.Send([]byte("welcome to upload api, use /api enpoint!"))
|
router.Get("/", func(c *fiber.Ctx) error {
|
||||||
|
return c.Send([]byte("welcome to upload api, use /api enpoint!"))
|
||||||
|
})
|
||||||
|
|
||||||
|
router.Get("/download/:id/:file", func(c *fiber.Ctx) error {
|
||||||
|
return FileGet(c, conf, db, shallExpire)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.Get("/download/:id/", func(c *fiber.Ctx) error {
|
||||||
|
return FileGet(c, conf, db, shallExpire)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup cleaner
|
||||||
|
quitcleaner := BackgroundCleaner(conf, db)
|
||||||
|
|
||||||
|
router.Hooks().OnShutdown(func() error {
|
||||||
|
Log("Shutting down cleaner")
|
||||||
|
close(quitcleaner)
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
router.Get("/download/:id/:file", func(c *fiber.Ctx) error {
|
return router.Listen(conf.Listen)
|
||||||
return FileGet(c, cfg, db, shallExpire)
|
|
||||||
})
|
|
||||||
|
|
||||||
router.Get("/download/:id/", func(c *fiber.Ctx) error {
|
|
||||||
return FileGet(c, cfg, db, shallExpire)
|
|
||||||
})
|
|
||||||
|
|
||||||
return router.Listen(cfg.Listen)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetupAuthStore(conf *cfg.Config) func(*fiber.Ctx) error {
|
||||||
|
AuthSetEndpoints(conf.ApiPrefix, ApiVersion, []string{"/file"})
|
||||||
|
AuthSetApikeys(conf.Apicontext)
|
||||||
|
|
||||||
|
return keyauth.New(keyauth.Config{
|
||||||
|
Validator: AuthValidateAPIKey,
|
||||||
|
ErrorHandler: AuthErrHandler,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetupServer(conf *cfg.Config) *fiber.App {
|
||||||
|
router := fiber.New(fiber.Config{
|
||||||
|
CaseSensitive: true,
|
||||||
|
StrictRouting: true,
|
||||||
|
Immutable: true,
|
||||||
|
Prefork: conf.Prefork,
|
||||||
|
ServerHeader: "upd",
|
||||||
|
AppName: conf.AppName,
|
||||||
|
BodyLimit: conf.BodyLimit,
|
||||||
|
Network: conf.Network,
|
||||||
|
})
|
||||||
|
|
||||||
|
router.Use(requestid.New())
|
||||||
|
|
||||||
|
router.Use(logger.New(logger.Config{
|
||||||
|
Format: "${pid} ${locals:requestid} ${status} - ${method} ${path}\n",
|
||||||
|
}))
|
||||||
|
|
||||||
|
router.Use(cors.New(cors.Config{
|
||||||
|
AllowMethods: "GET,PUT,POST,DELETE",
|
||||||
|
ExposeHeaders: "Content-Type,Authorization,Accept",
|
||||||
|
}))
|
||||||
|
|
||||||
|
router.Use(compress.New(compress.Config{
|
||||||
|
Level: compress.LevelBestSpeed,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Wrapper to respond with proper json status, message and code,
|
||||||
|
shall be prepared and called by the handlers directly.
|
||||||
|
*/
|
||||||
|
func JsonStatus(c *fiber.Ctx, code int, msg string) error {
|
||||||
|
success := true
|
||||||
|
|
||||||
|
if code != fiber.StatusOK {
|
||||||
|
success = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(code).JSON(Result{
|
||||||
|
Code: code,
|
||||||
|
Message: msg,
|
||||||
|
Success: success,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Used for non json-aware handlers, called by server
|
||||||
|
*/
|
||||||
func SendResponse(c *fiber.Ctx, msg string, err error) error {
|
func SendResponse(c *fiber.Ctx, msg string, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
code := fiber.StatusInternalServerError
|
code := fiber.StatusInternalServerError
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ package cfg
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Version string = "v0.0.1"
|
const Version string = "v0.0.1"
|
||||||
@@ -50,6 +52,13 @@ type Config struct {
|
|||||||
|
|
||||||
// only settable via config
|
// only settable via config
|
||||||
Apicontext []Apicontext `koanf:"apicontext"`
|
Apicontext []Apicontext `koanf:"apicontext"`
|
||||||
|
|
||||||
|
// Internals only
|
||||||
|
RegNormalizedFilename *regexp.Regexp
|
||||||
|
RegDuration *regexp.Regexp
|
||||||
|
RegKey *regexp.Regexp
|
||||||
|
CleanInterval time.Duration
|
||||||
|
DefaultExpire int
|
||||||
}
|
}
|
||||||
|
|
||||||
func Getversion() string {
|
func Getversion() string {
|
||||||
@@ -88,4 +97,11 @@ func (c *Config) ApplyDefaults() {
|
|||||||
c.Network = "tcp" // dual stack
|
c.Network = "tcp" // dual stack
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.RegNormalizedFilename = regexp.MustCompile(`[^\w\d\-_\.]`)
|
||||||
|
c.RegDuration = regexp.MustCompile(`[^dhms0-9]`)
|
||||||
|
c.RegKey = regexp.MustCompile(`[^a-zA-Z0-9\-]`)
|
||||||
|
|
||||||
|
c.CleanInterval = 10 * time.Second
|
||||||
|
c.DefaultExpire = 30 * 86400 // 1 month
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user