source: code/trunk/partage.go@ 30

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

Move configuration to a file

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 "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)
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 var file string
248 flag.StringVar(&file, "f", "", "Configuration file")
249 flag.BoolVar(&verbose, "v", false, "Verbose logging")
250 flag.Parse()
251
252 /* default values */
253 conf.bind = "0.0.0.0:8080"
254 conf.baseuri = "http://127.0.0.1:8080"
255 conf.rootdir = "/htdocs"
256 conf.tmplpath = "/htdocs/templates"
257 conf.filepath = "/htdocs/files"
258 conf.metapath = "/htdocs/meta"
259 conf.filectx = "/f/"
260 conf.metactx = "/m/"
261 conf.maxsize = 34359738368
262 conf.expiry = 86400
263
264 if file != "" {
265 if verbose {
266 log.Printf("Reading configuration %s", file)
267 }
268
269 cfg, err := ini.Load(file)
270 if err != nil {
271 fmt.Println(err)
272 return
273 }
274
275 conf.bind = cfg.Section("").Key("bind").String()
276 conf.user = cfg.Section("").Key("user").String()
277 conf.group = cfg.Section("").Key("group").String()
278 conf.baseuri = cfg.Section("").Key("baseuri").String()
279 conf.filepath = cfg.Section("").Key("filepath").String()
280 conf.metapath = cfg.Section("").Key("metapath").String()
281 conf.filectx = cfg.Section("").Key("filectx").String()
282 conf.metactx = cfg.Section("").Key("metactx").String()
283 conf.rootdir = cfg.Section("").Key("rootdir").String()
284 conf.chroot = cfg.Section("").Key("chroot").String()
285 conf.tmplpath = cfg.Section("").Key("tmplpath").String()
286 conf.maxsize, _ = cfg.Section("").Key("maxsize").Int64()
287 conf.expiry, _ = cfg.Section("").Key("expiry").Int64()
288 }
289
290 if verbose {
291 log.Printf("Applied configuration:\n%s", conf)
292 }
293
294 if (conf.chroot != "") {
295 if verbose {
296 log.Printf("Changing root to %s", conf.chroot)
297 }
298 syscall.Chroot(conf.chroot)
299 }
300
301 if conf.user != "" {
302 u, err := user.Lookup(conf.user)
303 if err != nil {
304 fmt.Println(err)
305 return
306 }
307
308 uid, _ := strconv.Atoi(u.Uid)
309 gid, _ := strconv.Atoi(u.Gid)
310
311 if conf.group != "" {
312 g, err := user.LookupGroup(conf.group)
313 if err != nil {
314 fmt.Println(err)
315 return
316 }
317 gid, _ = strconv.Atoi(g.Gid)
318 }
319
320 if verbose {
321 log.Printf("Dropping privileges to %s", conf.user)
322 }
323
324 syscall.Setuid(uid)
325 syscall.Setgid(gid)
326 }
327
328 http.HandleFunc("/", uploader)
329 http.Handle(conf.filectx, http.StripPrefix(conf.filectx, http.FileServer(http.Dir(conf.filepath))))
330
331 if verbose {
332 log.Printf("Listening on %s", conf.bind)
333 }
334
335 http.ListenAndServe(conf.bind, nil)
336}
Note: See TracBrowser for help on using the repository browser.