put shared code into own mod (common), + apicontext env vars

This commit is contained in:
2023-03-19 12:33:15 +01:00
parent a786fd56f4
commit 96c6f0c2dc
20 changed files with 295 additions and 279 deletions

78
common/common_test.go Normal file
View File

@@ -0,0 +1,78 @@
package common
import (
"fmt"
"testing"
"time"
)
func TestDuration2Seconds(t *testing.T) {
var tests = []struct {
dur string
expect int
}{
{"1d", 60 * 60 * 24},
{"1h", 60 * 60},
{"10m", 60 * 10},
{"2h4m10s", (60 * 120) + (4 * 60) + 10},
{"88u", 0},
{"19t77X what?4s", 4},
}
for _, tt := range tests {
testname := fmt.Sprintf("duration-%s", tt.dur)
t.Run(testname, func(t *testing.T) {
seconds := duration2int(tt.dur)
if seconds != tt.expect {
t.Errorf("got %d, want %d", seconds, tt.expect)
}
})
}
}
func TestIsExpired(t *testing.T) {
var tests = []struct {
expire string
start time.Time
expect bool
}{
{"3s", time.Now(), true},
{"1d", time.Now(), false},
}
for _, tt := range tests {
testname := fmt.Sprintf("isexpired-%s-%s", tt.start, tt.expire)
t.Run(testname, func(t *testing.T) {
time.Sleep(5 * time.Second)
got := IsExpired(tt.start, tt.expire)
if got != tt.expect {
t.Errorf("got %t, want %t", got, tt.expect)
}
})
}
}
func TestUntaint(t *testing.T) {
var tests = []struct {
want string
input string
expect string
wanterr bool
}{
{`[^a-zA-Z0-9\-]`, "ab23-bb43-beef", "ab23-bb43-beef", false},
{`[^a-zA-Z0-9\-]`, "`cat passwd`+ab23-bb43-beef", "catpasswdab23-bb43-beef", true},
}
for _, tt := range tests {
testname := fmt.Sprintf("untaint-%s-%s", tt.want, tt.expect)
t.Run(testname, func(t *testing.T) {
untainted, err := Untaint(tt.input, tt.want)
if untainted != tt.expect {
t.Errorf("got %s, want %s", untainted, tt.expect)
}
if err != nil && !tt.wanterr {
t.Errorf("got error: %s", err)
}
})
}
}

3
common/go.mod Normal file
View File

@@ -0,0 +1,3 @@
module github.com/tlinden/cenophane/common
go 1.18

97
common/timestamp.go Normal file
View File

@@ -0,0 +1,97 @@
/*
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 common
import (
"regexp"
"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
}
/*
We could use time.ParseDuration(), but this doesn't support days.
We could also use github.com/xhit/go-str2duration/v2, which does
the job, but it's just another dependency, just for this little
gem. And we don't need a time.Time value.
Convert a duration into seconds (int).
Valid time units are "s", "m", "h" and "d".
*/
func Duration2int(duration string) int {
re := regexp.MustCompile(`(\d+)([dhms])`)
seconds := 0
for _, match := range re.FindAllStringSubmatch(duration, -1) {
if len(match) == 3 {
v, _ := strconv.Atoi(match[1])
switch match[2][0] {
case 'd':
seconds += v * 86400
case 'h':
seconds += v * 3600
case 'm':
seconds += v * 60
case 's':
seconds += v
}
}
}
return seconds
}

43
common/types.go Normal file
View File

@@ -0,0 +1,43 @@
/*
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 common
// used to return to the api client
type Result struct {
Success bool `json:"success"`
Message string `json:"message"`
Code int `json:"code"`
}
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"`
Url string `json:"url"`
}
// 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
}

46
common/utils.go Normal file
View File

@@ -0,0 +1,46 @@
/*
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 common
import (
"errors"
"regexp"
)
/*
Untaint user input, that is: remove all non supported chars.
wanted is a regexp matching chars we shall leave. Everything else
will be removed. Eg:
untainted := Untaint(input, `[^a-zA-Z0-9\-]`)
Returns a new string and an error if the input string has been
modified. It's the callers choice to decide what to do about
it. You may ignore the error and use the untainted string or bail
out.
*/
func Untaint(input string, wanted *regexp.Regexp) (string, error) {
untainted := wanted.ReplaceAllString(input, "")
if len(untainted) != len(input) {
return untainted, errors.New("Invalid input string!")
}
return untainted, nil
}