source: code/trunk/partage.go@ 35

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

Move parsing and privilege drop out of main()

File size: 6.9 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/http"
12 "os"
13 "os/user"
14 "path"
15 "path/filepath"
16 "strconv"
17 "syscall"
18 "time"
19
20 "github.com/dustin/go-humanize"
21 "gopkg.in/ini.v1"
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 user string
38 group string
39 chroot string
40 bind string
41 baseuri string
42 rootdir string
43 tmplpath string
44 filepath string
45 metapath 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.tmplpath + "/" + 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.Name())
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 link := conf.baseuri + conf.filectx + filepath.Base(tmp.Name())
200 links = append(links, link)
201 }
202
203 if r.PostFormValue("output") == "html" {
204 data := templatedata{Links: links}
205 servetemplate(w, "/upload.html", data)
206 return
207 } else {
208 for _, link := range links {
209 w.Write([]byte(link + "\r\n"))
210 }
211 }
212}
213
214func uploaderGet(w http.ResponseWriter, r *http.Request) {
215 // r.URL.Path is sanitized regarding "." and ".."
216 filename := r.URL.Path
217 if r.URL.Path == "/" || r.URL.Path == "/index.html" {
218 data := templatedata{Maxsize: humanize.IBytes(uint64(conf.maxsize))}
219 servetemplate(w, "/index.html", data)
220 return
221 }
222
223 if verbose {
224 log.Printf("Serving file %s", conf.rootdir+filename)
225 }
226
227 http.ServeFile(w, r, conf.rootdir+filename)
228}
229
230func uploader(w http.ResponseWriter, r *http.Request) {
231 if verbose {
232 log.Printf("%s: <%s> %s %s %s", r.Host, r.RemoteAddr, r.Method, r.RequestURI, r.Proto)
233 }
234
235 switch r.Method {
236 case "POST":
237 uploaderPost(w, r)
238 case "PUT":
239 uploaderPut(w, r)
240 case "GET":
241 uploaderGet(w, r)
242 }
243}
244
245func parseconfig(file string) error {
246 cfg, err := ini.Load(file)
247 if err != nil {
248 return err
249 }
250
251 conf.bind = cfg.Section("").Key("bind").String()
252 conf.user = cfg.Section("").Key("user").String()
253 conf.group = cfg.Section("").Key("group").String()
254 conf.baseuri = cfg.Section("").Key("baseuri").String()
255 conf.filepath = cfg.Section("").Key("filepath").String()
256 conf.metapath = cfg.Section("").Key("metapath").String()
257 conf.filectx = cfg.Section("").Key("filectx").String()
258 conf.metactx = cfg.Section("").Key("metactx").String()
259 conf.rootdir = cfg.Section("").Key("rootdir").String()
260 conf.chroot = cfg.Section("").Key("chroot").String()
261 conf.tmplpath = cfg.Section("").Key("tmplpath").String()
262 conf.maxsize, _ = cfg.Section("").Key("maxsize").Int64()
263 conf.expiry, _ = cfg.Section("").Key("expiry").Int64()
264
265 return nil
266}
267
268func dropprivilege(username string, groupname string) error {
269 u, err := user.Lookup(username)
270 if err != nil {
271 return err
272 }
273
274 uid, _ := strconv.Atoi(u.Uid)
275 gid, _ := strconv.Atoi(u.Gid)
276
277 if conf.group != "" {
278 g, err := user.LookupGroup(groupname)
279 if err != nil {
280 return err
281 }
282 gid, _ = strconv.Atoi(g.Gid)
283 }
284
285 syscall.Setuid(uid)
286 syscall.Setgid(gid)
287
288 return nil
289}
290
291func main() {
292 var configfile string
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.user != "" {
325 if verbose {
326 log.Printf("Dropping privileges to %s", conf.user)
327 }
328 dropprivilege(conf.user, conf.group)
329 }
330
331 http.HandleFunc("/", uploader)
332 http.Handle(conf.filectx, http.StripPrefix(conf.filectx, http.FileServer(http.Dir(conf.filepath))))
333
334 if verbose {
335 log.Printf("Listening on %s", conf.bind)
336 }
337
338 http.ListenAndServe(conf.bind, nil)
339}
Note: See TracBrowser for help on using the repository browser.