source: code/trunk/partage.go@ 29

Last change on this file since 29 was 29, checked in by dev, 4 years ago

Add logs and a verbose mode

File size: 7.0 KB
Line 
1package main
2
3import (
4 "fmt"
5 "flag"
6 "io"
7 "io/ioutil"
8 "log"
9 "net/http"
10 "os"
11 "os/user"
12 "time"
13 "path"
14 "syscall"
15 "strconv"
16 "path/filepath"
17 "html/template"
18 "encoding/json"
19
20 "github.com/dustin/go-humanize"
21 "github.com/vharitonsky/iniflags"
22)
23
24type templatedata struct {
25 Links []string
26 Size string
27 Maxsize string
28}
29
30type metadata struct {
31 Filename string
32 Size int64
33 Expiry int64
34}
35
36var conf struct {
37 bind string
38 user string
39 group string
40 baseuri string
41 filepath string
42 metapath string
43 rootdir string
44 chroot string
45 templatedir string
46 filectx string
47 metactx string
48 maxsize int64
49 expiry int64
50}
51
52var verbose bool
53
54func writefile(f *os.File, s io.ReadCloser, contentlength int64) error {
55 buffer := make([]byte, 4096)
56 eof := false
57 sz := int64(0)
58
59 defer f.Sync()
60
61 for !eof {
62 n, err := s.Read(buffer)
63 if err != nil && err != io.EOF {
64 return err
65 } else if err == io.EOF {
66 eof = true
67 }
68
69 /* ensure we don't write more than expected */
70 r := int64(n)
71 if sz+r > contentlength {
72 r = contentlength - sz
73 eof = true
74 }
75
76 _, err = f.Write(buffer[:r])
77 if err != nil {
78 return err
79 }
80 sz += r
81 }
82
83 return nil
84}
85
86func writemeta(filename string, expiry int64) error {
87
88 f, _ := os.Open(filename)
89 stat, _ := f.Stat()
90 size := stat.Size()
91 f.Close()
92
93 meta := metadata{
94 Filename: filepath.Base(filename),
95 Size: size,
96 Expiry: time.Now().Unix() + expiry,
97 }
98
99 if verbose {
100 log.Printf("Saving metadata for %s in %s", meta.Filename, conf.metapath + "/" + meta.Filename + ".json")
101 }
102
103 f, err := os.Create(conf.metapath + "/" + meta.Filename + ".json")
104 if err != nil {
105 return err
106 }
107 defer f.Close()
108
109 j, err := json.Marshal(meta)
110 if err != nil {
111 return err
112 }
113
114 _, err = f.Write(j)
115
116 return err
117}
118
119func servetemplate(w http.ResponseWriter, f string, d templatedata) {
120 t, err := template.ParseFiles(conf.templatedir + "/" + f)
121 if err != nil {
122 http.Error(w, "Internal error", http.StatusInternalServerError)
123 return
124 }
125
126 if verbose {
127 log.Printf("Serving template %s", t.Name())
128 }
129
130 err = t.Execute(w, d)
131 if err != nil {
132 fmt.Println(err)
133 }
134}
135
136func uploaderPut(w http.ResponseWriter, r *http.Request) {
137 /* limit upload size */
138 if r.ContentLength > conf.maxsize {
139 http.Error(w, "File is too big", http.StatusRequestEntityTooLarge)
140 }
141
142 tmp, _ := ioutil.TempFile(conf.filepath, "*"+path.Ext(r.URL.Path))
143 f, err := os.Create(tmp.Name())
144 if err != nil {
145 fmt.Println(err)
146 return
147 }
148 defer f.Close()
149
150 if verbose {
151 log.Printf("Writing %d bytes to %s", r.ContentLength, tmp)
152 }
153
154 if err = writefile(f, r.Body, r.ContentLength); err != nil {
155 http.Error(w, "Internal error", http.StatusInternalServerError)
156 defer os.Remove(tmp.Name())
157 return
158 }
159 writemeta(tmp.Name(), conf.expiry)
160
161 resp := conf.baseuri + conf.filectx + filepath.Base(tmp.Name())
162 w.Write([]byte(resp))
163}
164
165func uploaderPost(w http.ResponseWriter, r *http.Request) {
166 /* read 32Mb at a time */
167 r.ParseMultipartForm(32 << 20)
168
169 links := []string{}
170 for _, h := range r.MultipartForm.File["uck"] {
171 if h.Size > conf.maxsize {
172 http.Error(w, "File is too big", http.StatusRequestEntityTooLarge)
173 return
174 }
175
176 post, err := h.Open()
177 if err != nil {
178 http.Error(w, "Internal error", http.StatusInternalServerError)
179 return
180 }
181 defer post.Close()
182
183 tmp, _ := ioutil.TempFile(conf.filepath, "*"+path.Ext(h.Filename))
184 f, err := os.Create(tmp.Name())
185 if err != nil {
186 http.Error(w, "Internal error", http.StatusInternalServerError)
187 return
188 }
189 defer f.Close()
190
191 if err = writefile(f, post, h.Size); err != nil {
192 http.Error(w, "Internal error", http.StatusInternalServerError)
193 defer os.Remove(tmp.Name())
194 return
195 }
196
197 writemeta(tmp.Name(), conf.expiry)
198
199
200 link := conf.baseuri + conf.filectx + filepath.Base(tmp.Name())
201 links = append(links, link)
202 }
203
204 if (r.PostFormValue("output") == "html") {
205 data := templatedata{ Links: links }
206 servetemplate(w, "/upload.html", data)
207 return
208 } else {
209 for _, link := range links {
210 w.Write([]byte(link + "\r\n"))
211 }
212 }
213}
214
215func uploaderGet(w http.ResponseWriter, r *http.Request) {
216 // r.URL.Path is sanitized regarding "." and ".."
217 filename := r.URL.Path
218 if r.URL.Path == "/" || r.URL.Path == "/index.html" {
219 data := templatedata{ Maxsize: humanize.IBytes(uint64(conf.maxsize))}
220 servetemplate(w, "/index.html", data)
221 return
222 }
223
224 if verbose {
225 log.Printf("Serving file %s", conf.rootdir + filename)
226 }
227
228 http.ServeFile(w, r, conf.rootdir + filename)
229}
230
231func uploader(w http.ResponseWriter, r *http.Request) {
232 if verbose {
233 log.Printf("%s: <%s> %s %s %s", r.Host, r.RemoteAddr, r.Method, r.RequestURI, r.Proto)
234 }
235
236 switch r.Method {
237 case "POST":
238 uploaderPost(w, r)
239 case "PUT":
240 uploaderPut(w, r)
241 case "GET":
242 uploaderGet(w, r)
243 }
244}
245
246func main() {
247 flag.StringVar(&conf.bind, "bind", "0.0.0.0:8080", "Address to bind to (default: 0.0.0.0:8080)")
248 flag.StringVar(&conf.user, "user", "", "User to drop privileges to on startup (default: current user)")
249 flag.StringVar(&conf.group, "group", "", "Group to drop privileges to on startup (default: user's group)")
250 flag.StringVar(&conf.baseuri, "baseuri", "http://127.0.0.1:8080", "Base URI to use for links (default: http://127.0.0.1:8080)")
251 flag.StringVar(&conf.filepath, "filepath", "./files", "Path to save files to (default: ./files)")
252 flag.StringVar(&conf.metapath, "metapath", "./meta", "Path to save metadata to (default: ./meta)")
253 flag.StringVar(&conf.filectx, "filectx", "/f/", "Context to serve files from (default: /f/)")
254 flag.StringVar(&conf.metactx, "metactx", "/m/", "Context to serve metadata from (default: /m/)")
255 flag.StringVar(&conf.rootdir, "rootdir", "./static", "Root directory (default: ./static)")
256 flag.StringVar(&conf.chroot, "chroot", "", "Directory to chroot into upon starting (default: no chroot)")
257 flag.StringVar(&conf.templatedir, "templatedir", "./templates", "Templates directory (default: ./templates)")
258 flag.Int64Var(&conf.maxsize, "maxsize", 30064771072, "Maximum file size (default: 28Gib)")
259 flag.Int64Var(&conf.expiry, "expiry", 86400, "Link expiration time (default: 24h)")
260
261 iniflags.Parse()
262
263 if verbose {
264 log.Printf("Applied configuration:\n%s", conf)
265 }
266
267 if (conf.chroot != "") {
268 if verbose {
269 log.Printf("Changing root to %s", conf.chroot)
270 }
271 syscall.Chroot(conf.chroot)
272 }
273
274 if conf.user != "" {
275 u, err := user.Lookup(conf.user)
276 if err != nil {
277 fmt.Println(err)
278 return
279 }
280
281 uid, _ := strconv.Atoi(u.Uid)
282 gid, _ := strconv.Atoi(u.Gid)
283
284 if conf.group != "" {
285 g, err := user.LookupGroup(conf.group)
286 if err != nil {
287 fmt.Println(err)
288 return
289 }
290 gid, _ = strconv.Atoi(g.Gid)
291 }
292
293 if verbose {
294 log.Printf("Dropping privileges to %s", conf.user)
295 }
296
297 syscall.Setuid(uid)
298 syscall.Setgid(gid)
299 }
300
301 http.HandleFunc("/", uploader)
302 http.Handle(conf.filectx, http.StripPrefix(conf.filectx, http.FileServer(http.Dir(conf.filepath))))
303
304 if verbose {
305 log.Printf("Listening on %s", conf.bind)
306 }
307
308 http.ListenAndServe(conf.bind, nil)
309}
Note: See TracBrowser for help on using the repository browser.