-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmodel.go
179 lines (159 loc) · 5.02 KB
/
model.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
package qbin
import (
"database/sql"
"encoding/hex"
"errors"
"strings"
"time"
"golang.org/x/crypto/scrypt"
"crypto/sha256"
)
const MaxFilesize = 1024 * 1024 // 1MB
// Document specifies the content and metadata of a piece of code that is hosted on qbin.
type Document struct {
// ID is set on Store()
ID string
Content string
Syntax string
// Upload is set on Store()
Upload time.Time
Expiration time.Time
Views int
Custom string
}
// Store a document object in the database.
func Store(document *Document) error {
// Generate a name that doesn't exist yet
name, err := GenerateSafeName()
if err != nil {
return err
}
document.ID = name
// Round the timestamps on the object. Won't affect the database, but we want consistency.
document.Upload = time.Now().Round(time.Second)
document.Expiration = document.Expiration.Round(time.Second)
// Normalize new lines
document.Content = strings.Trim(strings.Replace(strings.Replace(document.Content, "\r\n", "\n", -1), "\r", "\n", -1), "\n") + "\n"
// Don't accept binary files
if strings.Contains(document.Content, "\x00") {
return errors.New("file contains 0x00 bytes")
}
contentHighlighted := ""
originalRequired := false
if document.Custom == "" {
if document.Syntax == "none" {
document.Syntax = ""
}
contentHighlighted, originalRequired, err = Highlight(document.Content, document.Syntax)
if err != nil {
Log.Warningf("Skipped syntax highlighting for the following reason: %s", err)
}
} else {
contentHighlighted = EscapeHTML(document.Content)
}
// Filter content for spam
err = FilterSpam(document, &contentHighlighted)
if err != nil {
Log.Warningf("Spam filter hit for document: %s", err)
return errors.New("spam: " + err.Error())
}
var expiration interface{}
if (document.Expiration != time.Time{}) {
expiration = document.Expiration.UTC().Format("2006-01-02 15:04:05")
}
// Server-Side Encryption
key, err := scrypt.Key([]byte(document.ID), []byte(document.Upload.UTC().Format("2006-01-02 15:04:05")), 16384, 8, 1, 24)
if err != nil {
Log.Errorf("Invalid script parameters: %s", err)
}
data, err := encrypt([]byte(contentHighlighted), key)
if err != nil {
Log.Errorf("AES error: %s", err)
return err
}
rawData := sql.NullString{}
if originalRequired {
s, err := encrypt([]byte(document.Content), key)
if err != nil {
Log.Errorf("AES error: %s", err)
return err
}
rawData = sql.NullString{
String: string(s),
Valid: true,
}
}
databaseID := sha256.Sum256([]byte(document.ID))
// Write the document to the database
_, err = db.Exec(
"INSERT INTO documents (id, content, custom, syntax, upload, expiration, views, raw) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
hex.EncodeToString(databaseID[:]),
string(data),
document.Custom,
document.Syntax,
document.Upload.UTC().Format("2006-01-02 15:04:05"),
expiration,
document.Views,
rawData)
if err != nil {
return err
}
return nil
}
// Request a document from the database by its ID.
func Request(id string, raw bool) (Document, error) {
doc := Document{ID: id}
var views int
var upload, expiration, rawString sql.NullString
databaseID := sha256.Sum256([]byte(id))
err := db.QueryRow("SELECT content, custom, syntax, upload, expiration, views, raw FROM documents WHERE id = ?", hex.EncodeToString(databaseID[:])).
Scan(&doc.Content, &doc.Custom, &doc.Syntax, &upload, &expiration, &views, &rawString)
if err != nil {
if err.Error() != "sql: no rows in result set" {
Log.Warningf("Error retrieving document: %s", err)
}
return Document{}, err
}
go db.Exec("UPDATE documents SET views = views + 1 WHERE id = ?", hex.EncodeToString(databaseID[:]))
doc.Views = views
doc.Upload, _ = time.Parse("2006-01-02 15:04:05", upload.String)
// Server-Side Decryption
if raw && rawString.Valid {
doc.Content = rawString.String
}
key, err := scrypt.Key([]byte(id), []byte(doc.Upload.UTC().Format("2006-01-02 15:04:05")), 16384, 8, 1, 24)
if err != nil {
Log.Errorf("Invalid script parameters: %s", err)
return Document{}, err
}
data, err := decrypt([]byte(doc.Content), key)
if err != nil && !(err.Error() == "cipher: message authentication failed" && !strings.Contains(doc.Content, "\000")) {
Log.Errorf("AES error: %s", err)
return Document{}, err
} else if err == nil {
doc.Content = string(data)
}
if expiration.Valid {
doc.Expiration, err = time.Parse("2006-01-02 15:04:05", expiration.String)
if doc.Expiration.Before(time.Unix(0, 1)) {
if doc.Views > 0 {
// Volatile document
_, err = db.Exec("DELETE FROM documents WHERE id = ?", hex.EncodeToString(databaseID[:]))
if err != nil {
Log.Errorf("Couldn't delete volatile document: %s", err)
}
}
} else {
if err != nil {
return Document{}, err
}
if doc.Expiration.Before(time.Now()) {
return Document{}, errors.New("the document has expired")
}
}
}
if raw {
doc.Content = StripHTML(doc.Content)
}
return doc, nil
}