From 5b4ff5ef68b29192191b0bd1af1e1ad986f0b369 Mon Sep 17 00:00:00 2001 From: Timur Ismagilov Date: Sun, 9 May 2021 15:42:12 +0500 Subject: [PATCH] Move all web-related stuff to a new module --- flag.go | 5 +- http_admin.go | 43 ------- main.go | 142 ++--------------------- name.go | 14 --- util/util.go | 18 +++ web/http_admin.go | 44 +++++++ http_auth.go => web/http_auth.go | 16 +-- http_history.go => web/http_history.go | 14 +-- http_mutators.go => web/http_mutators.go | 28 ++--- http_readers.go => web/http_readers.go | 24 ++-- http_stuff.go => web/http_stuff.go | 18 +-- web/web.go | 127 ++++++++++++++++++++ 12 files changed, 250 insertions(+), 243 deletions(-) delete mode 100644 http_admin.go create mode 100644 web/http_admin.go rename http_auth.go => web/http_auth.go (86%) rename http_history.go => web/http_history.go (86%) rename http_mutators.go => web/http_mutators.go (93%) rename http_readers.go => web/http_readers.go (90%) rename http_stuff.go => web/http_stuff.go (87%) create mode 100644 web/web.go diff --git a/flag.go b/flag.go index 35bb9da..d801d3d 100644 --- a/flag.go +++ b/flag.go @@ -41,9 +41,8 @@ func parseCliArgs() { log.Fatal("Error: pass a wiki directory") } - var err error - WikiDir, err = filepath.Abs(args[0]) - cfg.WikiDir = WikiDir + wikiDir, err := filepath.Abs(args[0]) + cfg.WikiDir = wikiDir if err != nil { log.Fatal(err) } diff --git a/http_admin.go b/http_admin.go deleted file mode 100644 index d4d1612..0000000 --- a/http_admin.go +++ /dev/null @@ -1,43 +0,0 @@ -package main - -import ( - "github.com/bouncepaw/mycorrhiza/cfg" - "log" - "net/http" - - "github.com/bouncepaw/mycorrhiza/user" - "github.com/bouncepaw/mycorrhiza/views" -) - -// This is not init(), because user.AuthUsed is not set at init-stage. -func initAdmin() { - if user.AuthUsed { - http.HandleFunc("/admin", handlerAdmin) - http.HandleFunc("/admin/shutdown", handlerAdminShutdown) - http.HandleFunc("/admin/reindex-users", handlerAdminReindexUsers) - } -} - -func handlerAdmin(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) - if user.CanProceed(rq, "admin") { - w.Header().Set("Content-Type", "text/html;charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write([]byte(base("Admin panel", views.AdminPanelHTML(), user.FromRequest(rq)))) - } -} - -func handlerAdminShutdown(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) - if user.CanProceed(rq, "admin/shutdown") && rq.Method == "POST" { - log.Fatal("An admin commanded the wiki to shutdown") - } -} - -func handlerAdminReindexUsers(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) - if user.CanProceed(rq, "admin") && rq.Method == "POST" { - user.ReadUsersFromFilesystem() - http.Redirect(w, rq, "/hypha/"+cfg.UserHypha, http.StatusSeeOther) - } -} diff --git a/main.go b/main.go index 3d5d722..da0d6e6 100644 --- a/main.go +++ b/main.go @@ -5,121 +5,18 @@ package main import ( - "fmt" "github.com/bouncepaw/mycorrhiza/cfg" - "io" - "io/ioutil" - "log" - "net/http" - "net/url" - "os" - "strings" - - "github.com/bouncepaw/mycorrhiza/assets" "github.com/bouncepaw/mycorrhiza/files" "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/shroom" "github.com/bouncepaw/mycorrhiza/user" - "github.com/bouncepaw/mycorrhiza/views" + "github.com/bouncepaw/mycorrhiza/web" + "log" + "net/http" + "os" ) -// WikiDir is a rooted path to the wiki storage directory. -var WikiDir string - -// HttpErr is used by many handlers to signal errors in a compact way. -func HttpErr(w http.ResponseWriter, status int, name, title, errMsg string) { - log.Println(errMsg, "for", name) - w.Header().Set("Content-Type", "text/html;charset=utf-8") - w.WriteHeader(status) - fmt.Fprint( - w, - base( - title, - fmt.Sprintf( - `

%s. Go back to the hypha.

`, - errMsg, - name, - ), - user.EmptyUser(), - ), - ) -} - -// This part is present in all html documents. -var base = views.BaseHTML - -func handlerStyle(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) - if _, err := os.Stat(cfg.WikiDir + "/static/common.css"); err == nil { - http.ServeFile(w, rq, cfg.WikiDir+"/static/common.css") - } else { - w.Header().Set("Content-Type", "text/css;charset=utf-8") - w.Write([]byte(assets.DefaultCSS())) - } - if bytes, err := ioutil.ReadFile(cfg.WikiDir + "/static/custom.css"); err == nil { - w.Write(bytes) - } -} - -func handlerToolbar(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) - w.Header().Set("Content-Type", "text/javascript;charset=utf-8") - w.Write([]byte(assets.ToolbarJS())) -} - -// handlerIcon serves the requested icon. All icons are distributed as part of the Mycorrhiza binary. -// -// See assets/assets/icon/ for icons themselves, see assets/assets.qtpl for their sources. -func handlerIcon(w http.ResponseWriter, rq *http.Request) { - iconName := strings.TrimPrefix(rq.URL.Path, "/assets/icon/") - if iconName == "https" { - iconName = "http" - } - w.Header().Set("Content-Type", "image/svg+xml") - icon := func() string { - switch iconName { - case "gemini": - return assets.IconGemini() - case "mailto": - return assets.IconMailto() - case "gopher": - return assets.IconGopher() - case "feed": - return assets.IconFeed() - default: - return assets.IconHTTP() - } - }() - _, err := io.WriteString(w, icon) - if err != nil { - log.Println(err) - } - -} - -func handlerUserList(w http.ResponseWriter, rq *http.Request) { - w.Header().Set("Content-Type", "text/html;charset=utf-8") - w.WriteHeader(http.StatusOK) - w.Write([]byte(base("User list", views.UserListHTML(), user.FromRequest(rq)))) -} - -func handlerRobotsTxt(w http.ResponseWriter, rq *http.Request) { - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusOK) - w.Write([]byte( - `User-agent: * -Allow: /page/ -Allow: /recent-changes -Disallow: / -Crawl-delay: 5`)) -} - -func prepareRq(rq *http.Request) { - log.Println(rq.RequestURI) - rq.URL.Path = strings.TrimSuffix(rq.URL.Path, "/") -} - func main() { parseCliArgs() @@ -131,41 +28,20 @@ func main() { } log.Println("Running MycorrhizaWiki") - if err := os.Chdir(WikiDir); err != nil { + if err := os.Chdir(cfg.WikiDir); err != nil { log.Fatal(err) } - log.Println("Wiki storage directory is", WikiDir) - hyphae.Index(WikiDir) + log.Println("Wiki storage directory is", cfg.WikiDir) + hyphae.Index(cfg.WikiDir) log.Println("Indexed", hyphae.Count(), "hyphae") - // Initialize user database user.InitUserDatabase() - history.Start(WikiDir) + history.Start(cfg.WikiDir) shroom.SetHeaderLinks() go handleGemini() - // See http_admin.go for /admin, /admin/* - initAdmin() - // See http_readers.go for /page/, /hypha/, /text/, /binary/, /attachment/ - // See http_mutators.go for /upload-binary/, /upload-text/, /edit/, /delete-ask/, /delete-confirm/, /rename-ask/, /rename-confirm/, /unattach-ask/, /unattach-confirm/ - // See http_auth.go for /login, /login-data, /logout, /logout-confirm - http.HandleFunc("/user-list/", handlerUserList) - // See http_history.go for /history/, /recent-changes - // See http_stuff.go for list, reindex, update-header-links, random, about - http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(WikiDir+"/static")))) - http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, rq *http.Request) { - http.ServeFile(w, rq, WikiDir+"/static/favicon.ico") - }) - http.HandleFunc("/static/common.css", handlerStyle) - http.HandleFunc("/static/toolbar.js", handlerToolbar) - http.HandleFunc("/assets/icon/", handlerIcon) - http.HandleFunc("/robots.txt", handlerRobotsTxt) - http.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) { - addr, _ := url.Parse("/hypha/" + cfg.HomeHypha) // Let's pray it never fails - rq.URL = addr - handlerHypha(w, rq) - }) + web.Init() log.Fatal(http.ListenAndServe("0.0.0.0:"+cfg.HTTPPort, nil)) } diff --git a/name.go b/name.go index fd2a742..f05a44f 100644 --- a/name.go +++ b/name.go @@ -1,9 +1,7 @@ package main import ( - "github.com/bouncepaw/mycorrhiza/cfg" "log" - "net/http" "strings" "git.sr.ht/~adnano/go-gemini" @@ -11,18 +9,6 @@ import ( "github.com/bouncepaw/mycorrhiza/util" ) -// HyphaNameFromRq extracts hypha name from http request. You have to also pass the action which is embedded in the url or several actions. For url /hypha/hypha, the action would be "hypha". -func HyphaNameFromRq(rq *http.Request, actions ...string) string { - p := rq.URL.Path - for _, action := range actions { - if strings.HasPrefix(p, "/"+action+"/") { - return util.CanonicalName(strings.TrimPrefix(p, "/"+action+"/")) - } - } - log.Println("HyphaNameFromRq: this request is invalid, fallback to home hypha") - return cfg.HomeHypha -} - // geminiHyphaNameFromRq extracts hypha name from gemini request. You have to also pass the action which is embedded in the url or several actions. For url /hypha/hypha, the action would be "hypha". func geminiHyphaNameFromRq(rq *gemini.Request, actions ...string) string { p := rq.URL.Path diff --git a/util/util.go b/util/util.go index 92fcd68..1f3aced 100644 --- a/util/util.go +++ b/util/util.go @@ -4,12 +4,18 @@ import ( "crypto/rand" "encoding/hex" "github.com/bouncepaw/mycorrhiza/cfg" + "log" "net/http" "regexp" "strings" "unicode" ) +func PrepareRq(rq *http.Request) { + log.Println(rq.RequestURI) + rq.URL.Path = strings.TrimSuffix(rq.URL.Path, "/") +} + // LettersNumbersOnly keeps letters and numbers only in the given string. func LettersNumbersOnly(s string) string { var ( @@ -93,3 +99,15 @@ func IsCanonicalName(name string) bool { func IsPossibleUsername(username string) bool { return UsernamePattern.MatchString(strings.TrimSpace(username)) } + +// HyphaNameFromRq extracts hypha name from http request. You have to also pass the action which is embedded in the url or several actions. For url /hypha/hypha, the action would be "hypha". +func HyphaNameFromRq(rq *http.Request, actions ...string) string { + p := rq.URL.Path + for _, action := range actions { + if strings.HasPrefix(p, "/"+action+"/") { + return CanonicalName(strings.TrimPrefix(p, "/"+action+"/")) + } + } + log.Println("HyphaNameFromRq: this request is invalid, fallback to home hypha") + return cfg.HomeHypha +} diff --git a/web/http_admin.go b/web/http_admin.go new file mode 100644 index 0000000..0cd01a2 --- /dev/null +++ b/web/http_admin.go @@ -0,0 +1,44 @@ +package web + +import ( + "log" + "net/http" + + "github.com/bouncepaw/mycorrhiza/cfg" + "github.com/bouncepaw/mycorrhiza/user" + "github.com/bouncepaw/mycorrhiza/util" + "github.com/bouncepaw/mycorrhiza/views" +) + +// InitAdmin sets up /admin routes if auth is used. Call it after you have decided if you want to use auth. +func InitAdmin() { + if user.AuthUsed { + http.HandleFunc("/admin", HandlerAdmin) + http.HandleFunc("/admin/shutdown", HandlerAdminShutdown) + http.HandleFunc("/admin/reindex-users", HandlerAdminReindexUsers) + } +} + +func HandlerAdmin(w http.ResponseWriter, rq *http.Request) { + util.PrepareRq(rq) + if user.CanProceed(rq, "admin") { + w.Header().Set("Content-Type", "text/html;charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write([]byte(views.BaseHTML("Admin panel", views.AdminPanelHTML(), user.FromRequest(rq)))) + } +} + +func HandlerAdminShutdown(w http.ResponseWriter, rq *http.Request) { + util.PrepareRq(rq) + if user.CanProceed(rq, "admin/shutdown") && rq.Method == "POST" { + log.Fatal("An admin commanded the wiki to shutdown") + } +} + +func HandlerAdminReindexUsers(w http.ResponseWriter, rq *http.Request) { + util.PrepareRq(rq) + if user.CanProceed(rq, "admin") && rq.Method == "POST" { + user.ReadUsersFromFilesystem() + http.Redirect(w, rq, "/hypha/"+cfg.UserHypha, http.StatusSeeOther) + } +} diff --git a/http_auth.go b/web/http_auth.go similarity index 86% rename from http_auth.go rename to web/http_auth.go index fb0337f..84cdff2 100644 --- a/http_auth.go +++ b/web/http_auth.go @@ -1,4 +1,4 @@ -package main +package web import ( "github.com/bouncepaw/mycorrhiza/cfg" @@ -20,14 +20,14 @@ func init() { } func handlerRegister(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) + util.PrepareRq(rq) if !cfg.UseRegistration { w.WriteHeader(http.StatusForbidden) } if rq.Method == http.MethodGet { io.WriteString( w, - base( + views.BaseHTML( "Register", views.RegisterHTML(rq), user.FromRequest(rq), @@ -61,7 +61,7 @@ func handlerLogout(w http.ResponseWriter, rq *http.Request) { log.Println("Unknown user tries to log out") w.WriteHeader(http.StatusForbidden) } - w.Write([]byte(base("Logout?", views.LogoutHTML(can), u))) + w.Write([]byte(views.BaseHTML("Logout?", views.LogoutHTML(can), u))) } func handlerLogoutConfirm(w http.ResponseWriter, rq *http.Request) { @@ -70,26 +70,26 @@ func handlerLogoutConfirm(w http.ResponseWriter, rq *http.Request) { } func handlerLoginData(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) + util.PrepareRq(rq) var ( username = util.CanonicalName(rq.PostFormValue("username")) password = rq.PostFormValue("password") err = user.LoginDataHTTP(w, rq, username, password) ) if err != "" { - w.Write([]byte(base(err, views.LoginErrorHTML(err), user.EmptyUser()))) + w.Write([]byte(views.BaseHTML(err, views.LoginErrorHTML(err), user.EmptyUser()))) } else { http.Redirect(w, rq, "/", http.StatusSeeOther) } } func handlerLogin(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) + util.PrepareRq(rq) w.Header().Set("Content-Type", "text/html;charset=utf-8") if user.AuthUsed { w.WriteHeader(http.StatusOK) } else { w.WriteHeader(http.StatusForbidden) } - w.Write([]byte(base("Login", views.LoginHTML(), user.EmptyUser()))) + w.Write([]byte(views.BaseHTML("Login", views.LoginHTML(), user.EmptyUser()))) } diff --git a/http_history.go b/web/http_history.go similarity index 86% rename from http_history.go rename to web/http_history.go index e65e84b..9cba85b 100644 --- a/http_history.go +++ b/web/http_history.go @@ -1,4 +1,4 @@ -package main +package web import ( "fmt" @@ -23,8 +23,8 @@ func init() { // handlerHistory lists all revisions of a hypha func handlerHistory(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) - hyphaName := HyphaNameFromRq(rq, "history") + util.PrepareRq(rq) + hyphaName := util.HyphaNameFromRq(rq, "history") var list string // History can be found for files that do not exist anymore. @@ -35,25 +35,25 @@ func handlerHistory(w http.ResponseWriter, rq *http.Request) { log.Println("Found", len(revs), "revisions for", hyphaName) util.HTTP200Page(w, - base(hyphaName, views.HistoryHTML(rq, hyphaName, list), user.FromRequest(rq))) + views.BaseHTML(hyphaName, views.HistoryHTML(rq, hyphaName, list), user.FromRequest(rq))) } // Recent changes func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) + util.PrepareRq(rq) var ( noPrefix = strings.TrimPrefix(rq.URL.String(), "/recent-changes/") n, err = strconv.Atoi(noPrefix) ) if err == nil && n < 101 { - util.HTTP200Page(w, base(strconv.Itoa(n)+" recent changes", views.RecentChangesHTML(n), user.FromRequest(rq))) + util.HTTP200Page(w, views.BaseHTML(strconv.Itoa(n)+" recent changes", views.RecentChangesHTML(n), user.FromRequest(rq))) } else { http.Redirect(w, rq, "/recent-changes/20", http.StatusSeeOther) } } func genericHandlerOfFeeds(w http.ResponseWriter, rq *http.Request, f func() (string, error), name string) { - prepareRq(rq) + util.PrepareRq(rq) if content, err := f(); err != nil { w.Header().Set("Content-Type", "text/plain;charset=utf-8") w.WriteHeader(http.StatusInternalServerError) diff --git a/http_mutators.go b/web/http_mutators.go similarity index 93% rename from http_mutators.go rename to web/http_mutators.go index a3d98aa..fd6a5fc 100644 --- a/http_mutators.go +++ b/web/http_mutators.go @@ -1,4 +1,4 @@ -package main +package web import ( "fmt" @@ -35,9 +35,9 @@ func factoryHandlerAsker( succPageTemplate func(*http.Request, string, bool) string, ) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) + util.PrepareRq(rq) var ( - hyphaName = HyphaNameFromRq(rq, actionPath) + hyphaName = util.HyphaNameFromRq(rq, actionPath) h = hyphae.ByName(hyphaName) u = user.FromRequest(rq) ) @@ -52,7 +52,7 @@ func factoryHandlerAsker( } util.HTTP200Page( w, - base( + views.BaseHTML( fmt.Sprintf(succTitleTemplate, hyphaName), succPageTemplate(rq, hyphaName, h.Exists), u)) @@ -85,9 +85,9 @@ func factoryHandlerConfirmer( confirmer func(*hyphae.Hypha, *user.User, *http.Request) (*history.HistoryOp, string), ) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) + util.PrepareRq(rq) var ( - hyphaName = HyphaNameFromRq(rq, actionPath) + hyphaName = util.HyphaNameFromRq(rq, actionPath) h = hyphae.ByName(hyphaName) u = user.FromRequest(rq) ) @@ -129,9 +129,9 @@ var handlerRenameConfirm = factoryHandlerConfirmer( // handlerEdit shows the edit form. It doesn't edit anything actually. func handlerEdit(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) + util.PrepareRq(rq) var ( - hyphaName = HyphaNameFromRq(rq, "edit") + hyphaName = util.HyphaNameFromRq(rq, "edit") h = hyphae.ByName(hyphaName) warning string textAreaFill string @@ -158,7 +158,7 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) { } util.HTTP200Page( w, - base( + views.BaseHTML( "Edit "+hyphaName, views.EditHTML(rq, hyphaName, textAreaFill, warning), u)) @@ -166,9 +166,9 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) { // handlerUploadText uploads a new text part for the hypha. func handlerUploadText(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) + util.PrepareRq(rq) var ( - hyphaName = HyphaNameFromRq(rq, "upload-text") + hyphaName = util.HyphaNameFromRq(rq, "upload-text") h = hyphae.ByName(hyphaName) textData = rq.PostFormValue("text") action = rq.PostFormValue("action") @@ -190,7 +190,7 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) { if action == "Preview" { util.HTTP200Page( w, - base( + views.BaseHTML( "Preview "+hyphaName, views.PreviewHTML( rq, @@ -206,10 +206,10 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) { // handlerUploadBinary uploads a new binary part for the hypha. func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) + util.PrepareRq(rq) rq.ParseMultipartForm(10 << 20) // Set upload limit var ( - hyphaName = HyphaNameFromRq(rq, "upload-binary") + hyphaName = util.HyphaNameFromRq(rq, "upload-binary") h = hyphae.ByName(hyphaName) u = user.FromRequest(rq) file, handler, err = rq.FormFile("binary") diff --git a/http_readers.go b/web/http_readers.go similarity index 90% rename from http_readers.go rename to web/http_readers.go index 0d8e492..bd86ff4 100644 --- a/http_readers.go +++ b/web/http_readers.go @@ -1,4 +1,4 @@ -package main +package web import ( "fmt" @@ -29,9 +29,9 @@ func init() { } func handlerAttachment(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) + util.PrepareRq(rq) var ( - hyphaName = HyphaNameFromRq(rq, "attachment") + hyphaName = util.HyphaNameFromRq(rq, "attachment") h = hyphae.ByName(hyphaName) u = user.FromRequest(rq) ) @@ -43,7 +43,7 @@ func handlerAttachment(w http.ResponseWriter, rq *http.Request) { } func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) + util.PrepareRq(rq) var ( shorterUrl = strings.TrimPrefix(rq.URL.Path, "/primitive-diff/") firstSlashIndex = strings.IndexRune(shorterUrl, '/') @@ -61,7 +61,7 @@ func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) { // handlerRevision displays a specific revision of text part a page func handlerRevision(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) + util.PrepareRq(rq) var ( shorterUrl = strings.TrimPrefix(rq.URL.Path, "/rev/") firstSlashIndex = strings.IndexRune(shorterUrl, '/') @@ -83,13 +83,13 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) { ) w.Header().Set("Content-Type", "text/html;charset=utf-8") w.WriteHeader(http.StatusOK) - w.Write([]byte(base(util.BeautifulName(hyphaName), page, u))) + w.Write([]byte(views.BaseHTML(util.BeautifulName(hyphaName), page, u))) } // handlerText serves raw source text of the hypha. func handlerText(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) - hyphaName := HyphaNameFromRq(rq, "text") + util.PrepareRq(rq) + hyphaName := util.HyphaNameFromRq(rq, "text") if h := hyphae.ByName(hyphaName); h.Exists { log.Println("Serving", h.TextPath) w.Header().Set("Content-Type", "text/plain; charset=utf-8") @@ -99,8 +99,8 @@ func handlerText(w http.ResponseWriter, rq *http.Request) { // handlerBinary serves binary part of the hypha. func handlerBinary(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) - hyphaName := HyphaNameFromRq(rq, "binary") + util.PrepareRq(rq) + hyphaName := util.HyphaNameFromRq(rq, "binary") if h := hyphae.ByName(hyphaName); h.Exists { log.Println("Serving", h.BinaryPath) w.Header().Set("Content-Type", mimetype.FromExtension(filepath.Ext(h.BinaryPath))) @@ -110,9 +110,9 @@ func handlerBinary(w http.ResponseWriter, rq *http.Request) { // handlerHypha is the main hypha action that displays the hypha and the binary upload form along with some navigation. func handlerHypha(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) + util.PrepareRq(rq) var ( - hyphaName = HyphaNameFromRq(rq, "page", "hypha") + hyphaName = util.HyphaNameFromRq(rq, "page", "hypha") h = hyphae.ByName(hyphaName) contents string openGraph string diff --git a/http_stuff.go b/web/http_stuff.go similarity index 87% rename from http_stuff.go rename to web/http_stuff.go index 8984fc1..605a227 100644 --- a/http_stuff.go +++ b/web/http_stuff.go @@ -1,5 +1,5 @@ // http_stuff.go is used for meta stuff about the wiki or all hyphae at once. -package main +package web import ( "github.com/bouncepaw/mycorrhiza/cfg" @@ -25,22 +25,22 @@ func init() { // handlerList shows a list of all hyphae in the wiki in random order. func handlerList(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) - util.HTTP200Page(w, base("List of pages", views.HyphaListHTML(), user.FromRequest(rq))) + util.PrepareRq(rq) + util.HTTP200Page(w, views.BaseHTML("List of pages", views.HyphaListHTML(), user.FromRequest(rq))) } // handlerReindex reindexes all hyphae by checking the wiki storage directory anew. func handlerReindex(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) + util.PrepareRq(rq) if ok := user.CanProceed(rq, "reindex"); !ok { HttpErr(w, http.StatusForbidden, cfg.HomeHypha, "Not enough rights", "You must be an admin to reindex hyphae.") log.Println("Rejected", rq.URL) return } hyphae.ResetCount() - log.Println("Wiki storage directory is", WikiDir) + log.Println("Wiki storage directory is", cfg.WikiDir) log.Println("Start indexing hyphae...") - hyphae.Index(WikiDir) + hyphae.Index(cfg.WikiDir) log.Println("Indexed", hyphae.Count(), "hyphae") http.Redirect(w, rq, "/", http.StatusSeeOther) } @@ -49,7 +49,7 @@ func handlerReindex(w http.ResponseWriter, rq *http.Request) { // // See https://mycorrhiza.lesarbr.es/hypha/configuration/header func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) + util.PrepareRq(rq) if ok := user.CanProceed(rq, "update-header-links"); !ok { HttpErr(w, http.StatusForbidden, cfg.HomeHypha, "Not enough rights", "You must be a moderator to update header links.") log.Println("Rejected", rq.URL) @@ -61,7 +61,7 @@ func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) { // handlerRandom redirects to a random hypha. func handlerRandom(w http.ResponseWriter, rq *http.Request) { - prepareRq(rq) + util.PrepareRq(rq) var ( randomHyphaName string amountOfHyphae = hyphae.Count() @@ -85,7 +85,7 @@ func handlerRandom(w http.ResponseWriter, rq *http.Request) { func handlerAbout(w http.ResponseWriter, rq *http.Request) { w.Header().Set("Content-Type", "text/html;charset=utf-8") w.WriteHeader(http.StatusOK) - _, err := io.WriteString(w, base("About "+cfg.WikiName, views.AboutHTML(), user.FromRequest(rq))) + _, err := io.WriteString(w, views.BaseHTML("About "+cfg.WikiName, views.AboutHTML(), user.FromRequest(rq))) if err != nil { log.Println(err) } diff --git a/web/web.go b/web/web.go new file mode 100644 index 0000000..6d49f81 --- /dev/null +++ b/web/web.go @@ -0,0 +1,127 @@ +package web + +import ( + "fmt" + "github.com/bouncepaw/mycorrhiza/assets" + "github.com/bouncepaw/mycorrhiza/cfg" + "github.com/bouncepaw/mycorrhiza/util" + "io" + "io/ioutil" + "log" + "net/http" + "net/url" + "os" + "strings" + + "github.com/bouncepaw/mycorrhiza/user" + "github.com/bouncepaw/mycorrhiza/views" +) + +// HttpErr is used by many handlers to signal errors in a compact way. +func HttpErr(w http.ResponseWriter, status int, name, title, errMsg string) { + log.Println(errMsg, "for", name) + w.Header().Set("Content-Type", "text/html;charset=utf-8") + w.WriteHeader(status) + fmt.Fprint( + w, + views.BaseHTML( + title, + fmt.Sprintf( + `

%s. Go back to the hypha.

`, + errMsg, + name, + ), + user.EmptyUser(), + ), + ) +} + +func handlerStyle(w http.ResponseWriter, rq *http.Request) { + util.PrepareRq(rq) + if _, err := os.Stat(cfg.WikiDir + "/static/common.css"); err == nil { + http.ServeFile(w, rq, cfg.WikiDir+"/static/common.css") + } else { + w.Header().Set("Content-Type", "text/css;charset=utf-8") + w.Write([]byte(assets.DefaultCSS())) + } + if bytes, err := ioutil.ReadFile(cfg.WikiDir + "/static/custom.css"); err == nil { + w.Write(bytes) + } +} + +func handlerToolbar(w http.ResponseWriter, rq *http.Request) { + util.PrepareRq(rq) + w.Header().Set("Content-Type", "text/javascript;charset=utf-8") + w.Write([]byte(assets.ToolbarJS())) +} + +// handlerIcon serves the requested icon. All icons are distributed as part of the Mycorrhiza binary. +// +// See assets/assets/icon/ for icons themselves, see assets/assets.qtpl for their sources. +func handlerIcon(w http.ResponseWriter, rq *http.Request) { + iconName := strings.TrimPrefix(rq.URL.Path, "/assets/icon/") + if iconName == "https" { + iconName = "http" + } + w.Header().Set("Content-Type", "image/svg+xml") + icon := func() string { + switch iconName { + case "gemini": + return assets.IconGemini() + case "mailto": + return assets.IconMailto() + case "gopher": + return assets.IconGopher() + case "feed": + return assets.IconFeed() + default: + return assets.IconHTTP() + } + }() + _, err := io.WriteString(w, icon) + if err != nil { + log.Println(err) + } + +} + +func handlerUserList(w http.ResponseWriter, rq *http.Request) { + w.Header().Set("Content-Type", "text/html;charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write([]byte(views.BaseHTML("User list", views.UserListHTML(), user.FromRequest(rq)))) +} + +func handlerRobotsTxt(w http.ResponseWriter, rq *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + w.Write([]byte( + `User-agent: * +Allow: /page/ +Allow: /recent-changes +Disallow: / +Crawl-delay: 5`)) +} + +func Init() { + // See http_admin.go for /admin, /admin/* + InitAdmin() + // See http_readers.go for /page/, /hypha/, /text/, /binary/, /attachment/ + // See http_mutators.go for /upload-binary/, /upload-text/, /edit/, /delete-ask/, /delete-confirm/, /rename-ask/, /rename-confirm/, /unattach-ask/, /unattach-confirm/ + // See http_auth.go for /login, /login-data, /logout, /logout-confirm + http.HandleFunc("/user-list/", handlerUserList) + // See http_history.go for /history/, /recent-changes + // See http_stuff.go for list, reindex, update-header-links, random, about + http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(cfg.WikiDir+"/static")))) + http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, rq *http.Request) { + http.ServeFile(w, rq, cfg.WikiDir+"/static/favicon.ico") + }) + http.HandleFunc("/static/common.css", handlerStyle) + http.HandleFunc("/static/toolbar.js", handlerToolbar) + http.HandleFunc("/assets/icon/", handlerIcon) + http.HandleFunc("/robots.txt", handlerRobotsTxt) + http.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) { + addr, _ := url.Parse("/hypha/" + cfg.HomeHypha) // Let's pray it never fails + rq.URL = addr + handlerHypha(w, rq) + }) +}