http.go (view raw)
1package main
2
3import (
4 "bytes"
5 "fmt"
6 gmi "git.sr.ht/~adnano/go-gemini"
7 "github.com/gorilla/handlers"
8 "github.com/gorilla/sessions"
9 _ "github.com/mattn/go-sqlite3"
10 "github.com/prometheus/client_golang/prometheus/promhttp"
11 metrics "github.com/slok/go-http-metrics/metrics/prometheus"
12 "github.com/slok/go-http-metrics/middleware"
13 "github.com/slok/go-http-metrics/middleware/std"
14 "golang.org/x/crypto/bcrypt"
15 "html/template"
16 "io"
17 "io/ioutil"
18 "log"
19 "net/http"
20 "os"
21 "path"
22 "path/filepath"
23 "strings"
24 "time"
25)
26
27var t *template.Template
28var SessionStore *sessions.CookieStore
29
30func renderDefaultError(w http.ResponseWriter, statusCode int) {
31 errorMsg := http.StatusText(statusCode)
32 renderError(w, errorMsg, statusCode)
33}
34
35func renderError(w http.ResponseWriter, errorMsg string, statusCode int) {
36 data := struct {
37 PageTitle string
38 StatusCode int
39 ErrorMsg string
40 }{"Error!", statusCode, errorMsg}
41 err := t.ExecuteTemplate(w, "error.html", data)
42 if err != nil { // Shouldn't happen probably
43 http.Error(w, errorMsg, statusCode)
44 }
45}
46
47func rootHandler(w http.ResponseWriter, r *http.Request) {
48 // serve everything inside static directory
49 if r.URL.Path != "/" {
50 fileName := path.Join(c.TemplatesDirectory, "static", filepath.Clean(r.URL.Path))
51 _, err := os.Stat(fileName)
52 if err != nil {
53 renderDefaultError(w, http.StatusNotFound)
54 return
55 }
56 http.ServeFile(w, r, fileName) // TODO better error handling
57 return
58 }
59
60 user := newGetAuthUser(r)
61 indexFiles, err := getIndexFiles(user.IsAdmin)
62 if err != nil {
63 panic(err)
64 }
65 allUsers, err := getActiveUserNames()
66 if err != nil {
67 panic(err)
68 }
69 data := struct {
70 Host string
71 PageTitle string
72 Files []*File
73 Users []string
74 AuthUser AuthUser
75 }{c.Host, c.SiteTitle, indexFiles, allUsers, user}
76 err = t.ExecuteTemplate(w, "index.html", data)
77 if err != nil {
78 panic(err)
79 }
80}
81
82func feedHandler(w http.ResponseWriter, r *http.Request) {
83 user := newGetAuthUser(r)
84 feedEntries, feeds, err := getAllGemfeedEntries()
85 if err != nil {
86 panic(err)
87 }
88 data := struct {
89 Host string
90 PageTitle string
91 FeedEntries []FeedEntry
92 Feeds []Gemfeed
93 AuthUser AuthUser
94 }{c.Host, c.SiteTitle, feedEntries, feeds, user}
95 err = t.ExecuteTemplate(w, "feed.html", data)
96 if err != nil {
97 panic(err)
98 }
99}
100
101func editFileHandler(w http.ResponseWriter, r *http.Request) {
102 user := newGetAuthUser(r)
103 if !user.LoggedIn {
104 renderDefaultError(w, http.StatusForbidden)
105 return
106 }
107 fileName := filepath.Clean(r.URL.Path[len("/edit/"):])
108 filePath := path.Join(c.FilesDirectory, user.Username, fileName)
109 isText := isTextFile(filePath)
110 alert := ""
111 if r.Method == "POST" {
112 // get post body
113 r.ParseForm()
114 fileText := r.Form.Get("file_text")
115 // Web form by default gives us CR LF newlines.
116 // Unix files use just LF
117 fileText = strings.ReplaceAll(fileText, "\r\n", "\n")
118 fileBytes := []byte(fileText)
119 err := checkIfValidFile(filePath, fileBytes)
120 if err != nil {
121 log.Println(err)
122 renderError(w, err.Error(), http.StatusBadRequest)
123 return
124 }
125 // create directories if dne
126 os.MkdirAll(path.Dir(filePath), os.ModePerm)
127 if userHasSpace(user.Username, len(fileBytes)) {
128 if isText { // Cant edit binary files here
129 err = ioutil.WriteFile(filePath, fileBytes, 0644)
130 }
131 } else {
132 renderError(w, fmt.Sprintf("Bad Request: Out of file space. Max space: %d.", c.MaxUserBytes), http.StatusBadRequest)
133 return
134 }
135 if err != nil {
136 panic(err)
137 }
138 alert = "saved"
139 newName := filepath.Clean(r.Form.Get("rename"))
140 err = checkIfValidFile(newName, fileBytes)
141 if err != nil {
142 log.Println(err)
143 renderError(w, err.Error(), http.StatusBadRequest)
144 return
145 }
146 if newName != fileName {
147 newPath := path.Join(c.FilesDirectory, user.Username, newName)
148 os.MkdirAll(path.Dir(newPath), os.ModePerm)
149 os.Rename(filePath, newPath)
150 fileName = newName
151 filePath = newPath
152 alert += " and renamed"
153 }
154 }
155
156 err := checkIfValidFile(filePath, nil)
157 if err != nil {
158 log.Println(err)
159 renderError(w, err.Error(), http.StatusBadRequest)
160 return
161 }
162 // Create directories if dne
163 f, err := os.OpenFile(filePath, os.O_RDONLY, 0644)
164 var fileBytes []byte
165 if os.IsNotExist(err) || !isText {
166 fileBytes = []byte{}
167 err = nil
168 } else {
169 defer f.Close()
170 fileBytes, err = ioutil.ReadAll(f)
171 }
172 if err != nil {
173 panic(err)
174 }
175 data := struct {
176 FileName string
177 FileText string
178 PageTitle string
179 AuthUser AuthUser
180 Host string
181 IsText bool
182 Alert string
183 }{fileName, string(fileBytes), c.SiteTitle, user, c.Host, isText, alert}
184 err = t.ExecuteTemplate(w, "edit_file.html", data)
185 if err != nil {
186 panic(err)
187 }
188}
189
190func uploadFilesHandler(w http.ResponseWriter, r *http.Request) {
191 if r.Method == "POST" {
192 user := newGetAuthUser(r)
193 if !user.LoggedIn {
194 renderDefaultError(w, http.StatusForbidden)
195 return
196 }
197 r.ParseMultipartForm(10 << 6) // why does this not work
198 file, fileHeader, err := r.FormFile("file")
199 fileName := filepath.Clean(fileHeader.Filename)
200 defer file.Close()
201 if err != nil {
202 log.Println(err)
203 renderError(w, err.Error(), http.StatusBadRequest)
204 return
205 }
206 dest, _ := ioutil.ReadAll(file)
207 err = checkIfValidFile(fileName, dest)
208 if err != nil {
209 log.Println(err)
210 renderError(w, err.Error(), http.StatusBadRequest)
211 return
212 }
213 destPath := path.Join(c.FilesDirectory, user.Username, fileName)
214
215 f, err := os.OpenFile(destPath, os.O_WRONLY|os.O_CREATE, 0644)
216 if err != nil {
217 panic(err)
218 }
219 defer f.Close()
220 if userHasSpace(user.Username, c.MaxFileBytes) { // Not quite right
221 io.Copy(f, bytes.NewReader(dest))
222 } else {
223 renderError(w, fmt.Sprintf("Bad Request: Out of file space. Max space: %d.", c.MaxUserBytes), http.StatusBadRequest)
224 return
225 }
226 }
227 http.Redirect(w, r, "/my_site", http.StatusSeeOther)
228}
229
230type AuthUser struct {
231 LoggedIn bool
232 Username string
233 IsAdmin bool
234 ImpersonatingUser string // used if impersonating
235}
236
237func newGetAuthUser(r *http.Request) AuthUser {
238 session, _ := SessionStore.Get(r, "cookie-session")
239 user, ok := session.Values["auth_user"].(string)
240 impers, _ := session.Values["impersonating_user"].(string)
241 isAdmin, _ := session.Values["admin"].(bool)
242 return AuthUser{
243 LoggedIn: ok,
244 Username: user,
245 IsAdmin: isAdmin,
246 ImpersonatingUser: impers,
247 }
248}
249
250func mySiteHandler(w http.ResponseWriter, r *http.Request) {
251 user := newGetAuthUser(r)
252 if !user.LoggedIn {
253 renderDefaultError(w, http.StatusForbidden)
254 return
255 }
256 // check auth
257 userFolder := getUserDirectory(user.Username)
258 files, _ := getMyFilesRecursive(userFolder, user.Username)
259 currentDate := time.Now().Format("2006-01-02")
260 data := struct {
261 Host string
262 PageTitle string
263 Files []File
264 AuthUser AuthUser
265 CurrentDate string
266 }{c.Host, c.SiteTitle, files, user, currentDate}
267 _ = t.ExecuteTemplate(w, "my_site.html", data)
268}
269
270func myAccountHandler(w http.ResponseWriter, r *http.Request) {
271 user := newGetAuthUser(r)
272 authUser := user.Username
273 if !user.LoggedIn {
274 renderDefaultError(w, http.StatusForbidden)
275 return
276 }
277 me, _ := getUserByName(user.Username)
278 type pageData struct {
279 PageTitle string
280 AuthUser AuthUser
281 Email string
282 Errors []string
283 }
284 data := pageData{"My Account", user, me.Email, nil}
285
286 if r.Method == "GET" {
287 err := t.ExecuteTemplate(w, "me.html", data)
288 if err != nil {
289 panic(err)
290 }
291 } else if r.Method == "POST" {
292 r.ParseForm()
293 newUsername := r.Form.Get("username")
294 errors := []string{}
295 newEmail := r.Form.Get("email")
296 newUsername = strings.ToLower(newUsername)
297 var err error
298 if newEmail != me.Email {
299 _, err = DB.Exec("update user set email = ? where username = ?", newEmail, me.Email)
300 if err != nil {
301 // TODO better error not sql
302 errors = append(errors, err.Error())
303 } else {
304 log.Printf("Changed email for %s from %s to %s", authUser, me.Email, newEmail)
305 }
306 }
307 if newUsername != authUser {
308 // Rename User
309 err = renameUser(authUser, newUsername)
310 if err != nil {
311 log.Println(err)
312 errors = append(errors, "Could not rename user")
313 } else {
314 session, _ := SessionStore.Get(r, "cookie-session")
315 session.Values["auth_user"] = newUsername
316 session.Save(r, w)
317 }
318 }
319 // reset auth
320 user = newGetAuthUser(r)
321 data.Errors = errors
322 data.AuthUser = user
323 data.Email = newEmail
324 _ = t.ExecuteTemplate(w, "me.html", data)
325 }
326}
327
328func archiveHandler(w http.ResponseWriter, r *http.Request) {
329 authUser := newGetAuthUser(r)
330 if !authUser.LoggedIn {
331 renderDefaultError(w, http.StatusForbidden)
332 return
333 }
334 if r.Method == "GET" {
335 userFolder := getUserDirectory(authUser.Username)
336 err := zipit(userFolder, w)
337 if err != nil {
338 panic(err)
339 }
340
341 }
342}
343func loginHandler(w http.ResponseWriter, r *http.Request) {
344 if r.Method == "GET" {
345 // show page
346 data := struct {
347 Error string
348 PageTitle string
349 }{"", "Login"}
350 err := t.ExecuteTemplate(w, "login.html", data)
351 if err != nil {
352 panic(err)
353 }
354 } else if r.Method == "POST" {
355 r.ParseForm()
356 name := r.Form.Get("username")
357 password := r.Form.Get("password")
358 row := DB.QueryRow("SELECT username, password_hash, active, admin FROM user where username = $1 OR email = $1", name)
359 var db_password []byte
360 var username string
361 var active bool
362 var isAdmin bool
363 err := row.Scan(&username, &db_password, &active, &isAdmin)
364 if err != nil {
365 panic(err)
366 }
367 if db_password != nil && !active {
368 data := struct {
369 Error string
370 PageTitle string
371 }{"Your account is not active yet. Pending admin approval", c.SiteTitle}
372 t.ExecuteTemplate(w, "login.html", data)
373 return
374 }
375 if bcrypt.CompareHashAndPassword(db_password, []byte(password)) == nil {
376 log.Println("logged in")
377 session, _ := SessionStore.Get(r, "cookie-session")
378 session.Values["auth_user"] = username
379 session.Values["admin"] = isAdmin
380 session.Save(r, w)
381 http.Redirect(w, r, "/my_site", http.StatusSeeOther)
382 } else {
383 data := struct {
384 Error string
385 PageTitle string
386 }{"Invalid login or password", c.SiteTitle}
387 err := t.ExecuteTemplate(w, "login.html", data)
388 if err != nil {
389 panic(err)
390 }
391 }
392 }
393}
394
395func logoutHandler(w http.ResponseWriter, r *http.Request) {
396 session, _ := SessionStore.Get(r, "cookie-session")
397 impers, ok := session.Values["impersonating_user"].(string)
398 if ok {
399 session.Values["auth_user"] = impers
400 session.Values["impersonating_user"] = nil // TODO expire this automatically
401 // session.Values["admin"] = nil // TODO fix admin
402 } else {
403 session.Options.MaxAge = -1
404 }
405 session.Save(r, w)
406 http.Redirect(w, r, "/", http.StatusSeeOther)
407}
408
409const ok = "-0123456789abcdefghijklmnopqrstuvwxyz"
410
411func isOkUsername(s string) error {
412 if len(s) < 1 {
413 return fmt.Errorf("Username is too short")
414 }
415 if len(s) > 32 {
416 return fmt.Errorf("Username is too long. 32 char max.")
417 }
418 for _, char := range s {
419 if !strings.Contains(ok, strings.ToLower(string(char))) {
420 return fmt.Errorf("Username contains invalid characters. Valid characters include lowercase letters, numbers, and hyphens.")
421 }
422 }
423 return nil
424}
425func registerHandler(w http.ResponseWriter, r *http.Request) {
426 if r.Method == "GET" {
427 data := struct {
428 Host string
429 Errors []string
430 PageTitle string
431 }{c.Host, nil, "Register"}
432 err := t.ExecuteTemplate(w, "register.html", data)
433 if err != nil {
434 panic(err)
435 }
436 } else if r.Method == "POST" {
437 r.ParseForm()
438 email := r.Form.Get("email")
439 password := r.Form.Get("password")
440 errors := []string{}
441 if r.Form.Get("password") != r.Form.Get("password2") {
442 errors = append(errors, "Passwords don't match")
443 }
444 if len(password) < 6 {
445 errors = append(errors, "Password is too short")
446 }
447 username := strings.ToLower(r.Form.Get("username"))
448 err := isOkUsername(username)
449 if err != nil {
450 errors = append(errors, err.Error())
451 }
452 hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 8) // TODO handle error
453 if err != nil {
454 panic(err)
455 }
456 reference := r.Form.Get("reference")
457 if len(errors) == 0 {
458 _, err = DB.Exec("insert into user (username, email, password_hash, reference) values ($1, $2, $3, $4)", username, email, string(hashedPassword), reference)
459 if err != nil {
460 errors = append(errors, "Username or email is already used")
461 }
462 }
463 if len(errors) > 0 {
464 data := struct {
465 Host string
466 Errors []string
467 PageTitle string
468 }{c.Host, errors, "Register"}
469 t.ExecuteTemplate(w, "register.html", data)
470 } else {
471 data := struct {
472 Host string
473 Message string
474 PageTitle string
475 }{c.Host, "Registration complete! The server admin will approve your request before you can log in.", "Registration Complete"}
476 t.ExecuteTemplate(w, "message.html", data)
477 }
478 }
479}
480
481func deleteFileHandler(w http.ResponseWriter, r *http.Request) {
482 user := newGetAuthUser(r)
483 if !user.LoggedIn {
484 renderDefaultError(w, http.StatusForbidden)
485 return
486 }
487 filePath := safeGetFilePath(user.Username, r.URL.Path[len("/delete/"):])
488 if r.Method == "POST" {
489 os.Remove(filePath) // TODO handle error
490 }
491 http.Redirect(w, r, "/my_site", http.StatusSeeOther)
492}
493
494func adminHandler(w http.ResponseWriter, r *http.Request) {
495 user := newGetAuthUser(r)
496 if !user.IsAdmin {
497 renderDefaultError(w, http.StatusForbidden)
498 return
499 }
500 allUsers, err := getUsers()
501 if err != nil {
502 log.Println(err)
503 renderDefaultError(w, http.StatusInternalServerError)
504 return
505 }
506 data := struct {
507 Users []User
508 AuthUser AuthUser
509 PageTitle string
510 Host string
511 }{allUsers, user, "Admin", c.Host}
512 err = t.ExecuteTemplate(w, "admin.html", data)
513 if err != nil {
514 panic(err)
515 }
516}
517
518func getFavicon(user string) string {
519 faviconPath := path.Join(c.FilesDirectory, filepath.Clean(user), "favicon.txt")
520 content, err := ioutil.ReadFile(faviconPath)
521 if err != nil {
522 return ""
523 }
524 strcontent := []rune(string(content))
525 if len(strcontent) > 0 {
526 return string(strcontent[0])
527 }
528 return ""
529}
530
531// Server a user's file
532// Here be dragons
533func userFile(w http.ResponseWriter, r *http.Request) {
534 userName := filepath.Clean(strings.Split(r.Host, ".")[0]) // Clean probably unnecessary
535 p := filepath.Clean(r.URL.Path)
536 var isDir bool
537 fullPath := path.Join(c.FilesDirectory, userName, p) // TODO rename filepath
538 stat, err := os.Stat(fullPath)
539 if stat != nil {
540 isDir = stat.IsDir()
541 }
542 if strings.HasSuffix(p, "index.gmi") {
543 http.Redirect(w, r, path.Dir(p), http.StatusMovedPermanently)
544 }
545
546 if strings.HasPrefix(p, "/"+HiddenFolder) {
547 renderDefaultError(w, http.StatusForbidden)
548 return
549 }
550 if r.URL.Path == "/style.css" {
551 http.ServeFile(w, r, path.Join(c.TemplatesDirectory, "static/style.css"))
552 return
553 }
554 if r.URL.Path == "/gemlog/atom.xml" && os.IsNotExist(err) {
555 w.Header().Set("Content-Type", "application/atom+xml")
556 // TODO set always somehow
557 feed := generateFeedFromUser(userName)
558 atomString := feed.toAtomFeed()
559 io.Copy(w, strings.NewReader(atomString))
560 return
561 }
562
563 var geminiContent string
564 _, err = os.Stat(path.Join(fullPath, "index.gmi"))
565 if p == "/" || isDir {
566 if os.IsNotExist(err) {
567 if p == "/gemlog" {
568 geminiContent = generateGemfeedPage(userName)
569 } else {
570 geminiContent = generateFolderPage(fullPath)
571 }
572 } else {
573 fullPath = path.Join(fullPath, "index.gmi")
574 }
575 }
576 if geminiContent == "" && os.IsNotExist(err) {
577 renderDefaultError(w, http.StatusNotFound)
578 return
579 }
580 // Dumb content negotiation
581 _, raw := r.URL.Query()["raw"]
582 acceptsGemini := strings.Contains(r.Header.Get("Accept"), "text/gemini")
583 if !raw && !acceptsGemini && (isGemini(fullPath) || geminiContent != "") {
584 var htmlString string
585 if geminiContent == "" {
586 file, _ := os.Open(fullPath)
587 htmlString = textToHTML(gmi.ParseText(file))
588 defer file.Close()
589 } else {
590 htmlString = textToHTML(gmi.ParseText(strings.NewReader(geminiContent)))
591 }
592 favicon := getFavicon(userName)
593 hostname := strings.Split(r.Host, ":")[0]
594 URI := hostname + r.URL.String()
595 data := struct {
596 SiteBody template.HTML
597 Favicon string
598 PageTitle string
599 URI string
600 }{template.HTML(htmlString), favicon, userName + p, URI}
601 t.ExecuteTemplate(w, "user_page.html", data)
602 } else {
603 http.ServeFile(w, r, fullPath)
604 }
605}
606
607func deleteAccountHandler(w http.ResponseWriter, r *http.Request) {
608 user := newGetAuthUser(r)
609 if r.Method == "POST" {
610 r.ParseForm()
611 validate := r.Form.Get("validate-delete")
612 if validate == user.Username {
613 err := deleteUser(user.Username)
614 if err != nil {
615 log.Println(err)
616 renderDefaultError(w, http.StatusInternalServerError)
617 return
618 }
619 logoutHandler(w, r)
620 } else {
621 http.Redirect(w, r, "/me", http.StatusSeeOther)
622 }
623 }
624}
625
626func resetPasswordHandler(w http.ResponseWriter, r *http.Request) {
627 user := newGetAuthUser(r)
628 data := struct {
629 PageTitle string
630 AuthUser AuthUser
631 Error string
632 }{"Reset Password", user, ""}
633 if r.Method == "GET" {
634 err := t.ExecuteTemplate(w, "reset_pass.html", data)
635 if err != nil {
636 panic(err)
637 }
638 } else if r.Method == "POST" {
639 r.ParseForm()
640 enteredCurrPass := r.Form.Get("password")
641 password1 := r.Form.Get("new_password1")
642 password2 := r.Form.Get("new_password2")
643 if password1 != password2 {
644 data.Error = "New passwords do not match"
645 } else if len(password1) < 6 {
646 data.Error = "Password is too short"
647 } else {
648 err := checkAuth(user.Username, enteredCurrPass)
649 if err == nil {
650 hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password1), 8)
651 if err != nil {
652 panic(err)
653 }
654 _, err = DB.Exec("update user set password_hash = ? where username = ?", hashedPassword, user.Username)
655 if err != nil {
656 panic(err)
657 }
658 log.Printf("User %s reset password", user.Username)
659 http.Redirect(w, r, "/me", http.StatusSeeOther)
660 return
661 } else {
662 data.Error = "That's not your current password"
663 }
664 }
665 err := t.ExecuteTemplate(w, "reset_pass.html", data)
666 if err != nil {
667 panic(err)
668 }
669 }
670}
671
672func adminUserHandler(w http.ResponseWriter, r *http.Request) {
673 user := newGetAuthUser(r)
674 if r.Method == "POST" {
675 if !user.IsAdmin {
676 renderDefaultError(w, http.StatusForbidden)
677 return
678 }
679 components := strings.Split(r.URL.Path, "/")
680 if len(components) < 5 {
681 renderError(w, "Invalid action", http.StatusBadRequest)
682 return
683 }
684 userName := components[3]
685 action := components[4]
686 var err error
687 if action == "activate" {
688 err = activateUser(userName)
689 } else if action == "impersonate" {
690 if user.ImpersonatingUser != "" {
691 // Don't allow nested impersonation
692 renderError(w, "Cannot nest impersonation, log out from impersonated user first.", 400)
693 return
694 }
695 session, _ := SessionStore.Get(r, "cookie-session")
696 session.Values["auth_user"] = userName
697 session.Values["impersonating_user"] = user.Username
698 session.Save(r, w)
699 log.Printf("User %s impersonated %s", user.Username, userName)
700 http.Redirect(w, r, "/", http.StatusSeeOther)
701 return
702 }
703 if err != nil {
704 log.Println(err)
705 renderDefaultError(w, http.StatusInternalServerError)
706 return
707 }
708 http.Redirect(w, r, "/admin", http.StatusSeeOther)
709 }
710}
711
712func runHTTPMetricsServer() {
713 metricsAddr := c.PrometheusMetricsPort
714 log.Printf("metrics listening at %s", metricsAddr)
715 if err := http.ListenAndServe(metricsAddr, promhttp.Handler()); err != nil {
716 log.Panicf("error while serving metrics: %s", err)
717 }
718}
719
720func runHTTPServer() {
721 log.Printf("Running http server with hostname %s on port %d. TLS enabled: %t", c.Host, c.HttpPort, c.HttpsEnabled)
722 var err error
723 t, err = template.ParseGlob(path.Join(c.TemplatesDirectory, "*.html"))
724 if err != nil {
725 log.Fatal(err)
726 }
727 serveMux := http.NewServeMux()
728
729 s := strings.SplitN(c.Host, ":", 2)
730 hostname := s[0]
731 port := c.HttpPort
732
733 serveMux.HandleFunc(hostname+"/", rootHandler)
734 serveMux.HandleFunc(hostname+"/feed", feedHandler)
735 serveMux.HandleFunc(hostname+"/my_site", mySiteHandler)
736 serveMux.HandleFunc(hostname+"/me", myAccountHandler)
737 serveMux.HandleFunc(hostname+"/my_site/flounder-archive.zip", archiveHandler)
738 serveMux.HandleFunc(hostname+"/admin", adminHandler)
739 serveMux.HandleFunc(hostname+"/edit/", editFileHandler)
740 serveMux.HandleFunc(hostname+"/upload", uploadFilesHandler)
741 serveMux.Handle(hostname+"/login", limit(http.HandlerFunc(loginHandler)))
742 serveMux.Handle(hostname+"/register", limit(http.HandlerFunc(registerHandler)))
743 serveMux.HandleFunc(hostname+"/logout", logoutHandler)
744 serveMux.HandleFunc(hostname+"/delete/", deleteFileHandler)
745 serveMux.HandleFunc(hostname+"/delete-account", deleteAccountHandler)
746 serveMux.HandleFunc(hostname+"/reset-password", resetPasswordHandler)
747
748 // admin commands
749 serveMux.HandleFunc(hostname+"/admin/user/", adminUserHandler)
750 // TODO authentication
751 serveMux.HandleFunc(hostname+"/webdav/", webdavHandler)
752
753 wrapped := handlers.LoggingHandler(log.Writer(), handlers.RecoveryHandler()(serveMux))
754 if c.PrometheusMetrics {
755 prom := middleware.New(middleware.Config{
756 Recorder: metrics.NewRecorder(metrics.Config{}),
757 })
758 wrapped = std.Handler("", prom, wrapped)
759 }
760
761 // handle user files based on subdomain
762 serveMux.HandleFunc("/", userFile)
763 // login+register functions
764 srv := &http.Server{
765 ReadTimeout: 5 * time.Second,
766 WriteTimeout: 10 * time.Second,
767 IdleTimeout: 120 * time.Second,
768 Addr: fmt.Sprintf(":%d", port),
769 // TLSConfig: tlsConfig,
770 Handler: wrapped,
771 }
772 if c.HttpsEnabled {
773 log.Fatal(srv.ListenAndServeTLS(c.TLSCertFile, c.TLSKeyFile))
774 } else {
775 log.Fatal(srv.ListenAndServe())
776 }
777
778}