-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathstore.go
143 lines (128 loc) · 3.66 KB
/
store.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package main
import (
"crypto/sha256"
"encoding/base64"
"fmt"
"log"
"net/http"
"time"
)
const (
hiddenString = "#HIDDEN#"
)
var (
expFactor = realExpFactor
)
// In-memory representation of a secret.
type StoreEntry struct {
Secret string `json:"secret"`
MaxClicks int `json:"max_clicks"`
Clicks int `json:"clicks"`
DateAdded time.Time `json:"date_added"`
ValidFor int `json:"valid_for"`
AuthToken string `json:"auth_token"`
}
// Secret augmented with computed fields.
type StoreEntryInfo struct {
StoreEntry
Id string `json:"id"`
PathQuery string `json:"path_query"`
Url string `json:"url"`
ApiUrl string `json:"api_url"`
}
type secretStore map[string]StoreEntry
// hashStruct returns a hash from an arbitrary structure, usable in a URL.
func hashStruct(data interface{}) (hash string) {
hashBytes := sha256.Sum256([]byte(fmt.Sprintf("%v", data)))
hash = base64.RawURLEncoding.EncodeToString(hashBytes[:])
return
}
// AddEntry adds a secret to the store.
func (st secretStore) AddEntry(e StoreEntry, id string) string {
e.DateAdded = time.Now()
if id == "" {
id = hashStruct(e)
}
if e.ValidFor == 0 {
e.ValidFor = defaultValidity
}
if e.MaxClicks == 0 {
e.MaxClicks = defaultMaxClicks
}
st[id] = e
return id
}
// NewEntry adds a new secret to the store. Set id to ""
// to have it auto-generated by hashing the entry.
func (st secretStore) NewEntry(secret string, maxclicks int, validfor int, at string, id string) string {
return st.AddEntry(StoreEntry{secret, maxclicks, 0, time.Time{}, validfor, at}, id)
}
// GetEntry retrieves a secret from the store.
func (st secretStore) GetEntry(id string) (se StoreEntry, ok bool) {
se, ok = st[id]
return
}
// GetEntryInfo wraps GetEntry and adds some computed fields.
func (st secretStore) GetEntryInfo(id string) (si StoreEntryInfo, ok bool) {
entry, ok := st.GetEntry(id)
pathQuery := uGet + "?id=" + id
url := getURLBase() + pathQuery
apiurl := getURLBase() + uApiGet + id
return StoreEntryInfo{entry, id, pathQuery, url, apiurl}, ok
}
// GetEntryInfo wraps GetEntry and adds some computed fields. In addition it
// hides the "secret" value.
func (st secretStore) GetEntryInfoHidden(id string) (si StoreEntryInfo, ok bool) {
si, ok = st.GetEntryInfo(id)
si.Secret = hiddenString
return
}
// Click increases the click counter for an entry and sends a notification
func (st secretStore) Click(id string, r *http.Request) {
var msg string
entry, ok := st.GetEntry(id)
if ok {
// in any case increase number of clicks in our temporary entry
entry.Clicks += 1
if entry.Clicks < entry.MaxClicks {
// max clicks not yet reached, save our modified entry to the store
st[id] = entry
} else {
// max clicks reached, delete entry from store
delete(st, id)
}
msg = fmt.Sprintf(`
Id: %s
Clicked: %d time(s)
Clicks left: %d
Request: %s (%s) %s %s %s
User-Agent: %s
`,
id, entry.Clicks, entry.MaxClicks-entry.Clicks,
r.RemoteAddr, getRealIP(r), r.Method, r.URL.Path, r.Proto,
r.Header.Get("User-Agent"))
NotifyMail(entry.AuthToken, msg)
}
return
}
// realExpFactor will scale the given int value to a certain amount of time
func realExpFactor(v int) time.Duration {
return time.Hour * 24 * time.Duration(v)
}
// Expiry checks for expired entries at regular intervals
func (st secretStore) Expiry(interval time.Duration) {
tck := time.NewTicker(interval)
log.Printf("checking for expiration every %s\n", interval)
for {
now := time.Now()
for id, e := range st {
expDate := e.DateAdded
expDate = expDate.Add(expFactor(e.ValidFor))
if now.After(expDate) {
log.Printf("%s expired\n", id)
delete(st, id)
}
}
<-tck.C
}
}