source: code/trunk/partage.go@ 33

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

Fix incorrect log messages

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