source: code/trunk/partage.go@ 38

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

Provide ability to listen on unix sockets

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