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