source: code/trunk/partage.go@ 50

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

Delete useless metactx param

File size: 7.7 KB
Line 
1package main
2
3import (
4 "encoding/json"
5 "flag"
6 "fmt"
7 "html/template"
8 "io"
9 "io/ioutil"
10 "log"
11 "net"
12 "net/http"
13 "net/http/fcgi"
14 "os"
15 "os/signal"
16 "os/user"
17 "path"
18 "path/filepath"
19 "strconv"
20 "syscall"
21 "time"
22
23 "github.com/dustin/go-humanize"
24 "gopkg.in/ini.v1"
25)
26
27type templatedata struct {
28 Links []string
29 Size string
30 Maxsize string
31}
32
33type metadata struct {
34 Filename string
35 Size int64
36 Expiry int64
37}
38
39var conf struct {
40 user string
41 group string
42 chroot string
43 listen string
44 baseuri string
45 rootdir string
46 tmplpath string
47 filepath string
48 metapath string
49 filectx string
50 maxsize int64
51 expiry int64
52}
53
54var verbose bool
55
56func writefile(f *os.File, s io.ReadCloser, contentlength int64) error {
57 buffer := make([]byte, 4096)
58 eof := false
59 sz := int64(0)
60
61 defer f.Sync()
62
63 for !eof {
64 n, err := s.Read(buffer)
65 if err != nil && err != io.EOF {
66 return err
67 } else if err == io.EOF {
68 eof = true
69 }
70
71 /* ensure we don't write more than expected */
72 r := int64(n)
73 if sz+r > contentlength {
74 r = contentlength - sz
75 eof = true
76 }
77
78 _, err = f.Write(buffer[:r])
79 if err != nil {
80 return err
81 }
82 sz += r
83 }
84
85 return nil
86}
87
88func writemeta(filename string, expiry int64) error {
89
90 f, _ := os.Open(filename)
91 stat, _ := f.Stat()
92 size := stat.Size()
93 f.Close()
94
95 meta := metadata{
96 Filename: filepath.Base(filename),
97 Size: size,
98 Expiry: time.Now().Unix() + expiry,
99 }
100
101 if verbose {
102 log.Printf("Saving metadata for %s in %s", meta.Filename, conf.metapath+"/"+meta.Filename+".json")
103 }
104
105 f, err := os.Create(conf.metapath + "/" + meta.Filename + ".json")
106 if err != nil {
107 return err
108 }
109 defer f.Close()
110
111 j, err := json.Marshal(meta)
112 if err != nil {
113 return err
114 }
115
116 _, err = f.Write(j)
117
118 return err
119}
120
121func servetemplate(w http.ResponseWriter, f string, d templatedata) {
122 t, err := template.ParseFiles(conf.tmplpath + "/" + f)
123 if err != nil {
124 http.Error(w, "Internal error", http.StatusInternalServerError)
125 return
126 }
127
128 if verbose {
129 log.Printf("Serving template %s", t.Name())
130 }
131
132 err = t.Execute(w, d)
133 if err != nil {
134 fmt.Println(err)
135 }
136}
137
138func uploaderPut(w http.ResponseWriter, r *http.Request) {
139 /* limit upload size */
140 if r.ContentLength > conf.maxsize {
141 http.Error(w, "File is too big", http.StatusRequestEntityTooLarge)
142 }
143
144 tmp, _ := ioutil.TempFile(conf.filepath, "*"+path.Ext(r.URL.Path))
145 f, err := os.Create(tmp.Name())
146 if err != nil {
147 fmt.Println(err)
148 return
149 }
150 defer f.Close()
151
152 if verbose {
153 log.Printf("Writing %d bytes to %s", r.ContentLength, tmp.Name())
154 }
155
156 if err = writefile(f, r.Body, r.ContentLength); err != nil {
157 http.Error(w, "Internal error", http.StatusInternalServerError)
158 defer os.Remove(tmp.Name())
159 return
160 }
161 writemeta(tmp.Name(), conf.expiry)
162
163 resp := conf.baseuri + conf.filectx + filepath.Base(tmp.Name())
164 w.Write([]byte(resp + "\r\n"))
165}
166
167func uploaderPost(w http.ResponseWriter, r *http.Request) {
168 /* read 32Mb at a time */
169 r.ParseMultipartForm(32 << 20)
170
171 links := []string{}
172 for _, h := range r.MultipartForm.File["file"] {
173 if h.Size > conf.maxsize {
174 http.Error(w, "File is too big", http.StatusRequestEntityTooLarge)
175 return
176 }
177
178 post, err := h.Open()
179 if err != nil {
180 http.Error(w, "Internal error", http.StatusInternalServerError)
181 return
182 }
183 defer post.Close()
184
185 tmp, _ := ioutil.TempFile(conf.filepath, "*"+path.Ext(h.Filename))
186 f, err := os.Create(tmp.Name())
187 if err != nil {
188 http.Error(w, "Internal error", http.StatusInternalServerError)
189 return
190 }
191 defer f.Close()
192
193 if err = writefile(f, post, h.Size); err != nil {
194 http.Error(w, "Internal error", http.StatusInternalServerError)
195 defer os.Remove(tmp.Name())
196 return
197 }
198
199 writemeta(tmp.Name(), conf.expiry)
200
201 link := conf.baseuri + conf.filectx + filepath.Base(tmp.Name())
202 links = append(links, link)
203 }
204
205 switch r.PostFormValue("output") {
206 case "html":
207 data := templatedata{Links: links}
208 servetemplate(w, "/upload.html", data)
209 case "json":
210 data, _ := json.Marshal(links)
211 w.Write(data)
212 default:
213 for _, link := range links {
214 w.Write([]byte(link + "\r\n"))
215 }
216 }
217}
218
219func uploaderGet(w http.ResponseWriter, r *http.Request) {
220 // r.URL.Path is sanitized regarding "." and ".."
221 filename := r.URL.Path
222 if r.URL.Path == "/" || r.URL.Path == "/index.html" {
223 data := templatedata{Maxsize: humanize.IBytes(uint64(conf.maxsize))}
224 servetemplate(w, "/index.html", data)
225 return
226 }
227
228 if verbose {
229 log.Printf("Serving file %s", conf.rootdir+filename)
230 }
231
232 http.ServeFile(w, r, conf.rootdir+filename)
233}
234
235func uploader(w http.ResponseWriter, r *http.Request) {
236 if verbose {
237 log.Printf("%s: <%s> %s %s %s", r.Host, r.RemoteAddr, r.Method, r.RequestURI, r.Proto)
238 }
239
240 switch r.Method {
241 case "POST":
242 uploaderPost(w, r)
243 case "PUT":
244 uploaderPut(w, r)
245 case "GET":
246 uploaderGet(w, r)
247 }
248}
249
250func parseconfig(file string) error {
251 cfg, err := ini.Load(file)
252 if err != nil {
253 return err
254 }
255
256 conf.listen = cfg.Section("").Key("listen").String()
257 conf.user = cfg.Section("").Key("user").String()
258 conf.group = cfg.Section("").Key("group").String()
259 conf.baseuri = cfg.Section("").Key("baseuri").String()
260 conf.filepath = cfg.Section("").Key("filepath").String()
261 conf.metapath = cfg.Section("").Key("metapath").String()
262 conf.filectx = cfg.Section("").Key("filectx").String()
263 conf.rootdir = cfg.Section("").Key("rootdir").String()
264 conf.chroot = cfg.Section("").Key("chroot").String()
265 conf.tmplpath = cfg.Section("").Key("tmplpath").String()
266 conf.maxsize, _ = cfg.Section("").Key("maxsize").Int64()
267 conf.expiry, _ = cfg.Section("").Key("expiry").Int64()
268
269 return nil
270}
271
272func usergroupids(username string, groupname string) (int, int, error) {
273 u, err := user.Lookup(username)
274 if err != nil {
275 return -1, -1, err
276 }
277
278 uid, _ := strconv.Atoi(u.Uid)
279 gid, _ := strconv.Atoi(u.Gid)
280
281 if conf.group != "" {
282 g, err := user.LookupGroup(groupname)
283 if err != nil {
284 return uid, -1, err
285 }
286 gid, _ = strconv.Atoi(g.Gid)
287 }
288
289 return uid, gid, nil
290}
291
292func main() {
293 var err error
294 var configfile string
295 var listener net.Listener
296
297 /* default values */
298 conf.listen = "0.0.0.0:8080"
299 conf.baseuri = "http://127.0.0.1:8080"
300 conf.rootdir = "static"
301 conf.tmplpath = "templates"
302 conf.filepath = "files"
303 conf.metapath = "meta"
304 conf.filectx = "/f/"
305 conf.maxsize = 34359738368
306 conf.expiry = 86400
307
308 flag.StringVar(&configfile, "f", "", "Configuration file")
309 flag.BoolVar(&verbose, "v", false, "Verbose logging")
310 flag.Parse()
311
312 if configfile != "" {
313 if verbose {
314 log.Printf("Reading configuration %s", configfile)
315 }
316 parseconfig(configfile)
317 }
318
319 if conf.chroot != "" {
320 if verbose {
321 log.Printf("Changing root to %s", conf.chroot)
322 }
323 syscall.Chroot(conf.chroot)
324 }
325
326 if conf.listen[0] == '/' {
327 listener, err = net.Listen("unix", conf.listen)
328 if err != nil {
329 log.Fatal(err)
330 }
331
332 /* Ensure unix socket is removed on exit */
333 defer os.Remove(conf.listen)
334 sigs := make(chan os.Signal, 1)
335 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
336 go func() {
337 _ = <-sigs
338 os.Remove(conf.listen)
339 os.Exit(0)
340 }()
341 } else {
342 listener, err = net.Listen("tcp", conf.listen)
343 if err != nil {
344 log.Fatal(err)
345 }
346 }
347
348 if conf.user != "" {
349 if verbose {
350 log.Printf("Dropping privileges to %s", conf.user)
351 }
352 uid, gid, err := usergroupids(conf.user, conf.group)
353 if err != nil {
354 log.Fatal(err)
355 }
356
357 if listener.Addr().Network() == "unix" {
358 os.Chown(conf.listen, uid, gid)
359 }
360
361 syscall.Setuid(uid)
362 syscall.Setgid(gid)
363 }
364
365 http.HandleFunc("/", uploader)
366 http.Handle(conf.filectx, http.StripPrefix(conf.filectx, http.FileServer(http.Dir(conf.filepath))))
367
368 if verbose {
369 log.Printf("Listening on %s", conf.listen)
370 }
371
372 if listener.Addr().Network() == "unix" {
373 err = fcgi.Serve(listener, nil)
374 log.Fatal(err) /* NOTREACHED */
375 }
376
377 err = http.Serve(listener, nil)
378 log.Fatal(err) /* NOTREACHED */
379}
Note: See TracBrowser for help on using the repository browser.