source: code/trunk/partage.go@ 27

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

Add option to chroot into a directory on startup

File size: 5.6 KB
Line 
1package main
2
3import (
4 "fmt"
5 "flag"
6 "io"
7 "io/ioutil"
8 "net/http"
9 "os"
10 "time"
11 "path"
12 "syscall"
13 "path/filepath"
14 "html/template"
15 "encoding/json"
16
17 "github.com/dustin/go-humanize"
18 "github.com/vharitonsky/iniflags"
19)
20
21type templatedata struct {
22 Links []string
23 Size string
24 Maxsize string
25}
26
27type metadata struct {
28 Filename string
29 Size int64
30 Expiry int64
31}
32
33var conf struct {
34 bind string
35 baseuri string
36 filepath string
37 metapath string
38 rootdir string
39 chroot string
40 templatedir string
41 filectx string
42 metactx string
43 maxsize int64
44 expiry int64
45}
46
47func writefile(f *os.File, s io.ReadCloser, contentlength int64) error {
48 buffer := make([]byte, 4096)
49 eof := false
50 sz := int64(0)
51
52 defer f.Sync()
53
54 for !eof {
55 n, err := s.Read(buffer)
56 if err != nil && err != io.EOF {
57 return err
58 } else if err == io.EOF {
59 eof = true
60 }
61
62 /* ensure we don't write more than expected */
63 r := int64(n)
64 if sz+r > contentlength {
65 r = contentlength - sz
66 eof = true
67 }
68
69 _, err = f.Write(buffer[:r])
70 if err != nil {
71 return err
72 }
73 sz += r
74 }
75
76 return nil
77}
78
79func writemeta(filename string, expiry int64) error {
80
81 f, _ := os.Open(filename)
82 stat, _ := f.Stat()
83 size := stat.Size()
84 f.Close()
85
86 meta := metadata{
87 Filename: filepath.Base(filename),
88 Size: size,
89 Expiry: time.Now().Unix() + expiry,
90 }
91
92 f, err := os.Create(conf.metapath + "/" + meta.Filename + ".json")
93 if err != nil {
94 return err
95 }
96 defer f.Close()
97
98 j, err := json.Marshal(meta)
99 if err != nil {
100 return err
101 }
102
103 _, err = f.Write(j)
104
105 return err
106}
107
108func servetemplate(w http.ResponseWriter, f string, d templatedata) {
109 t, err := template.ParseFiles(conf.templatedir + "/" + f)
110 if err != nil {
111 http.Error(w, "Internal error", http.StatusInternalServerError)
112 return
113 }
114
115 err = t.Execute(w, d)
116 if err != nil {
117 fmt.Println(err)
118 }
119}
120
121func uploaderPut(w http.ResponseWriter, r *http.Request) {
122 /* limit upload size */
123 if r.ContentLength > conf.maxsize {
124 http.Error(w, "File is too big", http.StatusRequestEntityTooLarge)
125 }
126
127 tmp, _ := ioutil.TempFile(conf.filepath, "*"+path.Ext(r.URL.Path))
128 f, err := os.Create(tmp.Name())
129 if err != nil {
130 fmt.Println(err)
131 return
132 }
133 defer f.Close()
134
135 if err = writefile(f, r.Body, r.ContentLength); err != nil {
136 http.Error(w, "Internal error", http.StatusInternalServerError)
137 defer os.Remove(tmp.Name())
138 return
139 }
140 writemeta(tmp.Name(), conf.expiry)
141
142 resp := conf.baseuri + conf.filectx + filepath.Base(tmp.Name())
143 w.Write([]byte(resp))
144}
145
146func uploaderPost(w http.ResponseWriter, r *http.Request) {
147 /* read 32Mb at a time */
148 r.ParseMultipartForm(32 << 20)
149
150 links := []string{}
151 for _, h := range r.MultipartForm.File["uck"] {
152 if h.Size > conf.maxsize {
153 http.Error(w, "File is too big", http.StatusRequestEntityTooLarge)
154 return
155 }
156
157 post, err := h.Open()
158 if err != nil {
159 http.Error(w, "Internal error", http.StatusInternalServerError)
160 return
161 }
162 defer post.Close()
163
164 tmp, _ := ioutil.TempFile(conf.filepath, "*"+path.Ext(h.Filename))
165 f, err := os.Create(tmp.Name())
166 if err != nil {
167 http.Error(w, "Internal error", http.StatusInternalServerError)
168 return
169 }
170 defer f.Close()
171
172 if err = writefile(f, post, h.Size); err != nil {
173 http.Error(w, "Internal error", http.StatusInternalServerError)
174 defer os.Remove(tmp.Name())
175 return
176 }
177
178 writemeta(tmp.Name(), conf.expiry)
179
180
181 link := conf.baseuri + conf.filectx + filepath.Base(tmp.Name())
182 links = append(links, link)
183 }
184
185 if (r.PostFormValue("output") == "html") {
186 data := templatedata{ Links: links }
187 servetemplate(w, "/upload.html", data)
188 return
189 } else {
190 for _, link := range links {
191 w.Write([]byte(link + "\r\n"))
192 }
193 }
194}
195
196func uploaderGet(w http.ResponseWriter, r *http.Request) {
197 // r.URL.Path is sanitized regarding "." and ".."
198 filename := r.URL.Path
199 if r.URL.Path == "/" || r.URL.Path == "/index.html" {
200 data := templatedata{ Maxsize: humanize.IBytes(uint64(conf.maxsize))}
201 servetemplate(w, "/index.html", data)
202 return
203 }
204
205 http.ServeFile(w, r, conf.rootdir + filename)
206}
207
208func uploader(w http.ResponseWriter, r *http.Request) {
209 switch r.Method {
210 case "POST":
211 uploaderPost(w, r)
212 case "PUT":
213 uploaderPut(w, r)
214 case "GET":
215 uploaderGet(w, r)
216 }
217}
218
219func main() {
220 flag.StringVar(&conf.bind, "bind", "0.0.0.0:8080", "Address to bind to (default: 0.0.0.0:8080)")
221 flag.StringVar(&conf.baseuri, "baseuri", "http://127.0.0.1:8080", "Base URI to use for links (default: http://127.0.0.1:8080)")
222 flag.StringVar(&conf.filepath, "filepath", "./files", "Path to save files to (default: ./files)")
223 flag.StringVar(&conf.metapath, "metapath", "./meta", "Path to save metadata to (default: ./meta)")
224 flag.StringVar(&conf.filectx, "filectx", "/f/", "Context to serve files from (default: /f/)")
225 flag.StringVar(&conf.metactx, "metactx", "/m/", "Context to serve metadata from (default: /m/)")
226 flag.StringVar(&conf.rootdir, "rootdir", "./static", "Root directory (default: ./static)")
227 flag.StringVar(&conf.chroot, "chroot", "", "Directory to chroot into upon starting (default: no chroot)")
228 flag.StringVar(&conf.templatedir, "templatedir", "./templates", "Templates directory (default: ./templates)")
229 flag.Int64Var(&conf.maxsize, "maxsize", 30064771072, "Maximum file size (default: 28Gib)")
230 flag.Int64Var(&conf.expiry, "expiry", 86400, "Link expiration time (default: 24h)")
231
232 iniflags.Parse()
233
234 if (conf.chroot != "") {
235 syscall.Chroot(conf.chroot)
236 }
237
238 http.HandleFunc("/", uploader)
239 http.Handle(conf.filectx, http.StripPrefix(conf.filectx, http.FileServer(http.Dir(conf.filepath))))
240 http.ListenAndServe(conf.bind, nil)
241}
Note: See TracBrowser for help on using the repository browser.