-
Notifications
You must be signed in to change notification settings - Fork 0
/
files.go
244 lines (207 loc) · 6.67 KB
/
files.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
/*
files.go
Defines operations related to files, such as uploading,
deleting, etc.
*/
package main
import (
"crypto/md5"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"github.com/colin353/markdown.ninja/models"
"github.com/colin353/markdown.ninja/requesthandler"
)
// NewFileHandler returns an instance of the edit handler, with
// the routes populated.
func NewFileHandler() *requesthandler.GenericRequestHandler {
a := requesthandler.GenericRequestHandler{}
a.RouteMap = map[string]requesthandler.Responder{
"files": files,
"upload": upload,
"rename": renameFile,
"delete": deleteFile,
}
return &a
}
func files(u *models.User, w http.ResponseWriter, r *http.Request) interface{} {
f := models.File{}
f.Domain = u.Domain
iterator, err := models.GetList(&f)
if err != nil {
log.Printf("Tried to load files under `%s`, but it failed.", f.RegistrationKey())
return requesthandler.ResponseError
}
fileList := make([]map[string]interface{}, 0, iterator.Count())
for iterator.Next() {
fileList = append(fileList, iterator.Value().Export())
}
return fileList
}
func upload(u *models.User, w http.ResponseWriter, r *http.Request) interface{} {
// We will allow for 32 MiB of memory allocated
// to file reading, if the file is much bigger than that,
// it will be put into a temp directory.
r.ParseMultipartForm(32 << 20)
file, handler, err := r.FormFile("file")
// Figure out the file size. Since the file may be in memory
// or may be an actual file, we can use Seek to find the size.
// The second argument of Seek() tells it where to reference the first
// argument as an offset from. 2 --> end of the file, 1 --> start.
size, err := file.Seek(0, 2)
if err != nil {
log.Printf("Had trouble seeking to end in uploaded file (!)")
return requesthandler.ResponseError
}
// Important to seek back to the start, so we don't mess up the future
// read operations.
_, err = file.Seek(0, 0)
if err != nil {
log.Printf("Had trouble seeking back to start in uploaded file (!)")
return requesthandler.ResponseError
}
defer file.Close()
if size > (50 << 20) {
log.Printf("You can't upload such a big file (%d bytes) to the server. It's not allowed.", size)
return requesthandler.ResponseFileTooBig
}
// We need to check if the user has enough space remaining to upload
// the file. We allow 100 MiB of space.
if int64(u.SpaceUsage)+size > (100 << 20) {
log.Printf("Unable to upload file because we reached the space limit.")
return requesthandler.ResponseInssuficientSpace
}
if err != nil {
log.Printf("Trying to load uploaded file, but it failed.")
return requesthandler.ResponseError
}
f := models.File{}
// Need to ensure that the filename is acceptable. In order to
// do this, I'll set the filename "safely", which is guaranteed
// to result in a valid filename by stripping illegal characters.
f.SetNameSafely(handler.Filename)
f.Domain = u.Domain
// We need to check if that file already exists. If it does,
// we'll delete it so we can replace it.
err = models.Load(&f)
if err == nil {
// The file DOES exist. So we'll delete the redis record
// and the actual file.
os.Remove(f.GetPath())
models.Delete(&f)
// Release the space from the deleted file so the user can
// upload more stuff.
u.SpaceUsage -= f.Size
models.Save(u)
}
f.Size = int(size)
// Now we need to compute the file hash using MD5.
data, err := ioutil.ReadAll(file)
if err != nil {
log.Printf("Unable to read uploaded file.")
return requesthandler.ResponseError
}
f.Hash = fmt.Sprintf("%x", md5.Sum(data))
if !f.Validate() {
log.Printf("Validation failed on attempted upload: `%s`", f.Key())
return requesthandler.ResponseError
}
// Seek back to the beginning of the file so we can copy it.
_, err = file.Seek(0, 0)
// Finally, we'll move the file to the appropriate location
// on the server.
targetFile, err := os.OpenFile(f.GetPath(), os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
log.Printf("Unable to create a new file and copy the upload.")
return requesthandler.ResponseError
}
defer targetFile.Close()
// Copy the file over.
_, err = io.Copy(targetFile, file)
if err != nil {
log.Printf("Unable to copy the upload.")
return requesthandler.ResponseError
}
// Okay, everything was a success so far. So our final step will
// be to create the new record of the file in redis.
err = models.Insert(&f)
if err != nil {
log.Printf("Unable to create a record of the upload in the database.")
return requesthandler.ResponseError
}
// Now we'll update the user's file space usage.
u.SpaceUsage += f.Size
models.Save(u)
return requesthandler.ResponseOK
}
func renameFile(u *models.User, w http.ResponseWriter, r *http.Request) interface{} {
type renameArgs struct {
OldName string `json:"old_name"`
NewName string `json:"new_name"`
}
args := renameArgs{}
err := requesthandler.ParseArguments(r, &args)
if err != nil {
http.Error(w, "", http.StatusBadRequest)
return requesthandler.ResponseInvalidArgs
}
// Get the old version of the page.
f := models.File{}
f.Domain = u.Domain
f.Name = args.OldName
err = models.Load(&f)
if err != nil {
http.Error(w, "", http.StatusBadRequest)
return requesthandler.ResponseInvalidArgs
}
// Rename that page.
err = f.RenameFile(args.NewName)
// The most common reason this fails is because of validation
// failure because an invalid name was provided.
if err != nil {
http.Error(w, "", http.StatusBadRequest)
return requesthandler.ResponseInvalidArgs
}
return requesthandler.ResponseOK
}
func deleteFile(u *models.User, w http.ResponseWriter, r *http.Request) interface{} {
type deleteArgs struct {
Name string `json:"name"`
}
args := deleteArgs{}
err := requesthandler.ParseArguments(r, &args)
if err != nil {
http.Error(w, "", http.StatusBadRequest)
return requesthandler.ResponseInvalidArgs
}
// Try to load the file record.
f := models.File{}
f.Domain = u.Domain
f.Name = args.Name
err = models.Load(&f)
if err != nil {
log.Printf("Tried to delete file `%s`, but failed", f.Key())
return requesthandler.ResponseInvalidArgs
}
// Now try to delete the file from the hard drive.
err = os.Remove(f.GetPath())
if err != nil {
log.Printf("Failed to delete file `%s` from hard drive.", f.Key())
http.Error(w, "", http.StatusInternalServerError)
return requesthandler.ResponseError
}
// And now delete it from the database.
err = models.Delete(&f)
if err != nil {
log.Printf("Failed to delete file `%s` from database.", f.Key())
http.Error(w, "", http.StatusInternalServerError)
return requesthandler.ResponseError
}
// Release the space so the user can upload another file later.
u.SpaceUsage -= f.Size
models.Save(u)
return requesthandler.ResponseOK
}