source: code/trunk/partage.go@ 41

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

Remove fastcgi socket when terminating

File size: 7.5 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/user"
16 "path"
17 "path/filepath"
18 "strconv"
19 "syscall"
20 "time"
21
22 "github.com/dustin/go-humanize"
23 "gopkg.in/ini.v1"
24)
25
26type templatedata struct {
27 Links []string
28 Size string
29 Maxsize string
30}
31
32type metadata struct {
33 Filename string
34 Size int64
35 Expiry int64
36}
37
38var conf struct {
39 user string
40 group string
41 chroot string
42 bind string
43 baseuri string
44 rootdir string
45 tmplpath string
46 filepath string
47 metapath string
48 filectx string
49 metactx 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))
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["uck"] {
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 if r.PostFormValue("output") == "html" {
206 data := templatedata{Links: links}
207 servetemplate(w, "/upload.html", data)
208 return
209 } else {
210 for _, link := range links {
211 w.Write([]byte(link + "\r\n"))
212 }
213 }
214}
215
216func uploaderGet(w http.ResponseWriter, r *http.Request) {
217 // r.URL.Path is sanitized regarding "." and ".."
218 filename := r.URL.Path
219 if r.URL.Path == "/" || r.URL.Path == "/index.html" {
220 data := templatedata{Maxsize: humanize.IBytes(uint64(conf.maxsize))}
221 servetemplate(w, "/index.html", data)
222 return
223 }
224
225 if verbose {
226 log.Printf("Serving file %s", conf.rootdir+filename)
227 }
228
229 http.ServeFile(w, r, conf.rootdir+filename)
230}
231
232func uploader(w http.ResponseWriter, r *http.Request) {
233 if verbose {
234 log.Printf("%s: <%s> %s %s %s", r.Host, r.RemoteAddr, r.Method, r.RequestURI, r.Proto)
235 }
236
237 switch r.Method {
238 case "POST":
239 uploaderPost(w, r)
240 case "PUT":
241 uploaderPut(w, r)
242 case "GET":
243 uploaderGet(w, r)
244 }
245}
246
247func parseconfig(file string) error {
248 cfg, err := ini.Load(file)
249 if err != nil {
250 return err
251 }
252
253 conf.bind = cfg.Section("").Key("bind").String()
254 conf.user = cfg.Section("").Key("user").String()
255 conf.group = cfg.Section("").Key("group").String()
256 conf.baseuri = cfg.Section("").Key("baseuri").String()
257 conf.filepath = cfg.Section("").Key("filepath").String()
258 conf.metapath = cfg.Section("").Key("metapath").String()
259 conf.filectx = cfg.Section("").Key("filectx").String()
260 conf.metactx = cfg.Section("").Key("metactx").String()
261 conf.rootdir = cfg.Section("").Key("rootdir").String()
262 conf.chroot = cfg.Section("").Key("chroot").String()
263 conf.tmplpath = cfg.Section("").Key("tmplpath").String()
264 conf.maxsize, _ = cfg.Section("").Key("maxsize").Int64()
265 conf.expiry, _ = cfg.Section("").Key("expiry").Int64()
266
267 return nil
268}
269
270func usergroupids(username string, groupname string) (int, int, error) {
271 u, err := user.Lookup(username)
272 if err != nil {
273 return -1, -1, err
274 }
275
276 uid, _ := strconv.Atoi(u.Uid)
277 gid, _ := strconv.Atoi(u.Gid)
278
279 if conf.group != "" {
280 g, err := user.LookupGroup(groupname)
281 if err != nil {
282 return uid, -1, err
283 }
284 gid, _ = strconv.Atoi(g.Gid)
285 }
286
287 return uid, gid, nil
288}
289
290func main() {
291 var err error
292 var configfile string
293 var listener net.Listener
294
295 /* default values */
296 conf.bind = "0.0.0.0:8080"
297 conf.baseuri = "http://127.0.0.1:8080"
298 conf.rootdir = "/htdocs"
299 conf.tmplpath = "/htdocs/templates"
300 conf.filepath = "/htdocs/files"
301 conf.metapath = "/htdocs/meta"
302 conf.filectx = "/f/"
303 conf.metactx = "/m/"
304 conf.maxsize = 34359738368
305 conf.expiry = 86400
306
307 flag.StringVar(&configfile, "f", "", "Configuration file")
308 flag.BoolVar(&verbose, "v", false, "Verbose logging")
309 flag.Parse()
310
311 if configfile != "" {
312 if verbose {
313 log.Printf("Reading configuration %s", configfile)
314 }
315 parseconfig(configfile)
316 }
317
318 if conf.chroot != "" {
319 if verbose {
320 log.Printf("Changing root to %s", conf.chroot)
321 }
322 syscall.Chroot(conf.chroot)
323 }
324
325 if conf.bind[0] == '/' {
326 listener, err = net.Listen("unix", conf.bind)
327 if err != nil {
328 log.Fatal(err)
329 }
330 defer os.Remove(conf.bind)
331 } else {
332 listener, err = net.Listen("tcp", conf.bind)
333 if err != nil {
334 log.Fatal(err)
335 }
336 }
337
338 if conf.user != "" {
339 if verbose {
340 log.Printf("Dropping privileges to %s", conf.user)
341 }
342 uid, gid, err := usergroupids(conf.user, conf.group)
343 if err != nil {
344 log.Fatal(err)
345 }
346
347 if listener.Addr().Network() == "unix" {
348 os.Chown(conf.bind, uid, gid)
349 }
350
351 syscall.Setuid(uid)
352 syscall.Setgid(gid)
353 }
354
355 http.HandleFunc("/", uploader)
356 http.Handle(conf.filectx, http.StripPrefix(conf.filectx, http.FileServer(http.Dir(conf.filepath))))
357
358 if verbose {
359 log.Printf("Listening on %s", conf.bind)
360 }
361
362 if listener.Addr().Network() == "unix" {
363 err = fcgi.Serve(listener, nil)
364 log.Fatal(err) /* NOTREACHED */
365 }
366
367 err = http.Serve(listener, nil)
368 log.Fatal(err) /* NOTREACHED */
369}
Note: See TracBrowser for help on using the repository browser.