-
Notifications
You must be signed in to change notification settings - Fork 13
/
db.go
261 lines (219 loc) · 6.09 KB
/
db.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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
package main
import (
"bytes"
"database/sql"
"encoding/gob"
"errors"
"strconv"
"time"
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3"
)
var ErrNotAuthorized = errors.New("Not authorized")
// ZerodropEntry is a page entry.
type ZerodropEntry struct {
Name string // The request URI used to access this entry
URL string // The URL that this entry references
Filename string // The location of the file in the uploads directory
ContentType string // The MIME type to serve as Content-Type header
Redirect bool // Indicates whether to redirect instead of proxy
Creation time.Time // The time this entry was created
AccessRedirectOnDeny string // Entry to redirect to if entry is blacklisted or expired
AccessBlacklist Blacklist // Blacklist
AccessBlacklistCount int // Number of requests that have been caught by the blacklist
AccessExpire bool // Indicates whether to expire after finite access
AccessExpireCount int // The number of requests on this entry before expiry
AccessCount int // The number of times this has been accessed
AccessTrain bool // Whether training is active
}
// ZerodropDB represents a database connection.
// TODO: Use a persistent backend.
type ZerodropDB struct {
*sql.DB
// Accessors
GetStmt *sql.Stmt
ListStmt *sql.Stmt
ListTokenStmt *sql.Stmt
// Restricted Mutators
UpdateCheckTokenStmt *sql.Stmt
DeleteStmt *sql.Stmt
ClearStmt *sql.Stmt
// Admin Mutators
AdminUpdateStmt *sql.Stmt
AdminDeleteStmt *sql.Stmt
AdminClearStmt *sql.Stmt
}
// Connect opens a connection to the backend.
func (d *ZerodropDB) Connect(driver, source string) error {
db, err := sql.Open(driver, source)
if err != nil {
return err
}
gob.Register(&ZerodropEntry{})
if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS entries (
name TEXT PRIMARY KEY NOT NULL,
token TEXT NOT NULL,
creation INTEGER NOT NULL,
gob BLOB NOT NULL
)`); err != nil {
return err
}
// Accessors
d.GetStmt, err = db.Prepare(
`SELECT gob FROM entries WHERE name = ?`)
if err != nil {
return err
}
d.ListStmt, err = db.Prepare(
`SELECT gob FROM entries ORDER BY creation DESC`)
if err != nil {
return err
}
d.ListTokenStmt, err = db.Prepare(
`SELECT gob FROM entries WHERE token = ? ORDER BY creation DESC`)
if err != nil {
return err
}
// Restricted Mutators
d.UpdateCheckTokenStmt, err = db.Prepare(
`SELECT token FROM entries WHERE name = ?`)
if err != nil {
return err
}
d.DeleteStmt, err = db.Prepare(
`DELETE FROM entries WHERE name = ? AND token = ?`)
if err != nil {
return err
}
d.ClearStmt, err = db.Prepare(
`DELETE FROM entries WHERE token = ?`)
if err != nil {
return err
}
// Admin Mutators
d.AdminUpdateStmt, err = db.Prepare(
`REPLACE INTO entries (name, token, creation, gob) VALUES (?, ?, ?, ?)`)
if err != nil {
return err
}
d.AdminDeleteStmt, err = db.Prepare(
`DELETE FROM entries WHERE name = ?`)
if err != nil {
return err
}
d.AdminClearStmt, err = db.Prepare(
`DELETE FROM entries`)
if err != nil {
return err
}
d.DB = db
return nil
}
// Get returns the entry with the specified name.
func (d *ZerodropDB) Get(name string) (*ZerodropEntry, error) {
var data []byte
if err := d.GetStmt.QueryRow(name).Scan(&data); err != nil {
return nil, err
}
var entry *ZerodropEntry
dec := gob.NewDecoder(bytes.NewReader(data))
if err := dec.Decode(&entry); err != nil {
return nil, err
}
return entry, nil
}
// List returns a slice of all entries sorted by creation time,
// with the most recent first.
func (d *ZerodropDB) List(token string) ([]*ZerodropEntry, error) {
list := []*ZerodropEntry{}
var err error
var rows *sql.Rows
if token == "" {
rows, err = d.ListStmt.Query()
} else {
rows, err = d.ListTokenStmt.Query(token)
}
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var data []byte
if err := rows.Scan(&data); err != nil {
return nil, err
}
var entry *ZerodropEntry
dec := gob.NewDecoder(bytes.NewReader(data))
if err := dec.Decode(&entry); err != nil {
return nil, err
}
list = append(list, entry)
}
if err := rows.Err(); err != nil {
return nil, err
}
return list, nil
}
// Update adds an entry to the database.
func (d *ZerodropDB) Update(entry *ZerodropEntry, claims *AdminClaims) error {
var token string
err := d.UpdateCheckTokenStmt.QueryRow(entry.Name).Scan(&token)
if err != nil {
// The entry does not exist.
token = claims.Token
} else if !claims.Admin && token != claims.Token {
// The entry exists and the tokens do not match.
return ErrNotAuthorized
}
var buffer bytes.Buffer
enc := gob.NewEncoder(&buffer)
if err := enc.Encode(entry); err != nil {
return err
}
if _, err := d.AdminUpdateStmt.Exec(entry.Name, token, entry.Creation.Unix(), buffer.Bytes()); err != nil {
return err
}
return nil
}
// Remove removes an entry from the database with the specified token.
func (d *ZerodropDB) Remove(name string, claims *AdminClaims) error {
if claims.Admin {
if _, err := d.AdminDeleteStmt.Exec(name); err != nil {
return err
}
return nil
}
if _, err := d.DeleteStmt.Exec(name, claims.Token); err != nil {
return err
}
return nil
}
// Clear resets the database by removing all entries with the specified token.
func (d *ZerodropDB) Clear(claims *AdminClaims) error {
if claims.Admin {
if _, err := d.AdminClearStmt.Exec(); err != nil {
return err
}
return nil
}
if _, err := d.ClearStmt.Exec(claims.Token); err != nil {
return err
}
return nil
}
// IsExpired returns true if the entry is expired
func (e *ZerodropEntry) IsExpired() bool {
return e.AccessExpire && (e.AccessCount >= e.AccessExpireCount)
}
// SetTraining sets the AccessTrain flag
func (e *ZerodropEntry) SetTraining(train bool) {
e.AccessTrain = train
}
// Access increases the access count for an entry.
func (e *ZerodropEntry) Access() {
e.AccessCount++
}
func (e *ZerodropEntry) String() string {
return strconv.Quote(e.Name)
}