1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2025-08-08 14:54:40 +00:00

Move all web-related stuff to a new module

This commit is contained in:
Timur Ismagilov 2021-05-09 15:42:12 +05:00
parent b53a6d5dff
commit 5b4ff5ef68
12 changed files with 250 additions and 243 deletions

View File

@ -41,9 +41,8 @@ func parseCliArgs() {
log.Fatal("Error: pass a wiki directory") log.Fatal("Error: pass a wiki directory")
} }
var err error wikiDir, err := filepath.Abs(args[0])
WikiDir, err = filepath.Abs(args[0]) cfg.WikiDir = wikiDir
cfg.WikiDir = WikiDir
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -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)
}
}

142
main.go
View File

@ -5,121 +5,18 @@
package main package main
import ( import (
"fmt"
"github.com/bouncepaw/mycorrhiza/cfg" "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/files"
"github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/shroom" "github.com/bouncepaw/mycorrhiza/shroom"
"github.com/bouncepaw/mycorrhiza/user" "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(
`<main class="main-width"><p>%s. <a href="/page/%s">Go back to the hypha.<a></p></main>`,
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() { func main() {
parseCliArgs() parseCliArgs()
@ -131,41 +28,20 @@ func main() {
} }
log.Println("Running MycorrhizaWiki") log.Println("Running MycorrhizaWiki")
if err := os.Chdir(WikiDir); err != nil { if err := os.Chdir(cfg.WikiDir); err != nil {
log.Fatal(err) log.Fatal(err)
} }
log.Println("Wiki storage directory is", WikiDir) log.Println("Wiki storage directory is", cfg.WikiDir)
hyphae.Index(WikiDir) hyphae.Index(cfg.WikiDir)
log.Println("Indexed", hyphae.Count(), "hyphae") log.Println("Indexed", hyphae.Count(), "hyphae")
// Initialize user database
user.InitUserDatabase() user.InitUserDatabase()
history.Start(WikiDir) history.Start(cfg.WikiDir)
shroom.SetHeaderLinks() shroom.SetHeaderLinks()
go handleGemini() go handleGemini()
// See http_admin.go for /admin, /admin/* web.Init()
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)
})
log.Fatal(http.ListenAndServe("0.0.0.0:"+cfg.HTTPPort, nil)) log.Fatal(http.ListenAndServe("0.0.0.0:"+cfg.HTTPPort, nil))
} }

14
name.go
View File

@ -1,9 +1,7 @@
package main package main
import ( import (
"github.com/bouncepaw/mycorrhiza/cfg"
"log" "log"
"net/http"
"strings" "strings"
"git.sr.ht/~adnano/go-gemini" "git.sr.ht/~adnano/go-gemini"
@ -11,18 +9,6 @@ import (
"github.com/bouncepaw/mycorrhiza/util" "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". // 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 { func geminiHyphaNameFromRq(rq *gemini.Request, actions ...string) string {
p := rq.URL.Path p := rq.URL.Path

View File

@ -4,12 +4,18 @@ import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"github.com/bouncepaw/mycorrhiza/cfg" "github.com/bouncepaw/mycorrhiza/cfg"
"log"
"net/http" "net/http"
"regexp" "regexp"
"strings" "strings"
"unicode" "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. // LettersNumbersOnly keeps letters and numbers only in the given string.
func LettersNumbersOnly(s string) string { func LettersNumbersOnly(s string) string {
var ( var (
@ -93,3 +99,15 @@ func IsCanonicalName(name string) bool {
func IsPossibleUsername(username string) bool { func IsPossibleUsername(username string) bool {
return UsernamePattern.MatchString(strings.TrimSpace(username)) 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
}

44
web/http_admin.go Normal file
View File

@ -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)
}
}

View File

@ -1,4 +1,4 @@
package main package web
import ( import (
"github.com/bouncepaw/mycorrhiza/cfg" "github.com/bouncepaw/mycorrhiza/cfg"
@ -20,14 +20,14 @@ func init() {
} }
func handlerRegister(w http.ResponseWriter, rq *http.Request) { func handlerRegister(w http.ResponseWriter, rq *http.Request) {
prepareRq(rq) util.PrepareRq(rq)
if !cfg.UseRegistration { if !cfg.UseRegistration {
w.WriteHeader(http.StatusForbidden) w.WriteHeader(http.StatusForbidden)
} }
if rq.Method == http.MethodGet { if rq.Method == http.MethodGet {
io.WriteString( io.WriteString(
w, w,
base( views.BaseHTML(
"Register", "Register",
views.RegisterHTML(rq), views.RegisterHTML(rq),
user.FromRequest(rq), user.FromRequest(rq),
@ -61,7 +61,7 @@ func handlerLogout(w http.ResponseWriter, rq *http.Request) {
log.Println("Unknown user tries to log out") log.Println("Unknown user tries to log out")
w.WriteHeader(http.StatusForbidden) 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) { 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) { func handlerLoginData(w http.ResponseWriter, rq *http.Request) {
prepareRq(rq) util.PrepareRq(rq)
var ( var (
username = util.CanonicalName(rq.PostFormValue("username")) username = util.CanonicalName(rq.PostFormValue("username"))
password = rq.PostFormValue("password") password = rq.PostFormValue("password")
err = user.LoginDataHTTP(w, rq, username, password) err = user.LoginDataHTTP(w, rq, username, password)
) )
if err != "" { if err != "" {
w.Write([]byte(base(err, views.LoginErrorHTML(err), user.EmptyUser()))) w.Write([]byte(views.BaseHTML(err, views.LoginErrorHTML(err), user.EmptyUser())))
} else { } else {
http.Redirect(w, rq, "/", http.StatusSeeOther) http.Redirect(w, rq, "/", http.StatusSeeOther)
} }
} }
func handlerLogin(w http.ResponseWriter, rq *http.Request) { func handlerLogin(w http.ResponseWriter, rq *http.Request) {
prepareRq(rq) util.PrepareRq(rq)
w.Header().Set("Content-Type", "text/html;charset=utf-8") w.Header().Set("Content-Type", "text/html;charset=utf-8")
if user.AuthUsed { if user.AuthUsed {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} else { } else {
w.WriteHeader(http.StatusForbidden) w.WriteHeader(http.StatusForbidden)
} }
w.Write([]byte(base("Login", views.LoginHTML(), user.EmptyUser()))) w.Write([]byte(views.BaseHTML("Login", views.LoginHTML(), user.EmptyUser())))
} }

View File

@ -1,4 +1,4 @@
package main package web
import ( import (
"fmt" "fmt"
@ -23,8 +23,8 @@ func init() {
// handlerHistory lists all revisions of a hypha // handlerHistory lists all revisions of a hypha
func handlerHistory(w http.ResponseWriter, rq *http.Request) { func handlerHistory(w http.ResponseWriter, rq *http.Request) {
prepareRq(rq) util.PrepareRq(rq)
hyphaName := HyphaNameFromRq(rq, "history") hyphaName := util.HyphaNameFromRq(rq, "history")
var list string var list string
// History can be found for files that do not exist anymore. // 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) log.Println("Found", len(revs), "revisions for", hyphaName)
util.HTTP200Page(w, 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 // Recent changes
func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) { func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) {
prepareRq(rq) util.PrepareRq(rq)
var ( var (
noPrefix = strings.TrimPrefix(rq.URL.String(), "/recent-changes/") noPrefix = strings.TrimPrefix(rq.URL.String(), "/recent-changes/")
n, err = strconv.Atoi(noPrefix) n, err = strconv.Atoi(noPrefix)
) )
if err == nil && n < 101 { 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 { } else {
http.Redirect(w, rq, "/recent-changes/20", http.StatusSeeOther) http.Redirect(w, rq, "/recent-changes/20", http.StatusSeeOther)
} }
} }
func genericHandlerOfFeeds(w http.ResponseWriter, rq *http.Request, f func() (string, error), name string) { 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 { if content, err := f(); err != nil {
w.Header().Set("Content-Type", "text/plain;charset=utf-8") w.Header().Set("Content-Type", "text/plain;charset=utf-8")
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)

View File

@ -1,4 +1,4 @@
package main package web
import ( import (
"fmt" "fmt"
@ -35,9 +35,9 @@ func factoryHandlerAsker(
succPageTemplate func(*http.Request, string, bool) string, succPageTemplate func(*http.Request, string, bool) string,
) func(http.ResponseWriter, *http.Request) { ) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, rq *http.Request) { return func(w http.ResponseWriter, rq *http.Request) {
prepareRq(rq) util.PrepareRq(rq)
var ( var (
hyphaName = HyphaNameFromRq(rq, actionPath) hyphaName = util.HyphaNameFromRq(rq, actionPath)
h = hyphae.ByName(hyphaName) h = hyphae.ByName(hyphaName)
u = user.FromRequest(rq) u = user.FromRequest(rq)
) )
@ -52,7 +52,7 @@ func factoryHandlerAsker(
} }
util.HTTP200Page( util.HTTP200Page(
w, w,
base( views.BaseHTML(
fmt.Sprintf(succTitleTemplate, hyphaName), fmt.Sprintf(succTitleTemplate, hyphaName),
succPageTemplate(rq, hyphaName, h.Exists), succPageTemplate(rq, hyphaName, h.Exists),
u)) u))
@ -85,9 +85,9 @@ func factoryHandlerConfirmer(
confirmer func(*hyphae.Hypha, *user.User, *http.Request) (*history.HistoryOp, string), confirmer func(*hyphae.Hypha, *user.User, *http.Request) (*history.HistoryOp, string),
) func(http.ResponseWriter, *http.Request) { ) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, rq *http.Request) { return func(w http.ResponseWriter, rq *http.Request) {
prepareRq(rq) util.PrepareRq(rq)
var ( var (
hyphaName = HyphaNameFromRq(rq, actionPath) hyphaName = util.HyphaNameFromRq(rq, actionPath)
h = hyphae.ByName(hyphaName) h = hyphae.ByName(hyphaName)
u = user.FromRequest(rq) u = user.FromRequest(rq)
) )
@ -129,9 +129,9 @@ var handlerRenameConfirm = factoryHandlerConfirmer(
// handlerEdit shows the edit form. It doesn't edit anything actually. // handlerEdit shows the edit form. It doesn't edit anything actually.
func handlerEdit(w http.ResponseWriter, rq *http.Request) { func handlerEdit(w http.ResponseWriter, rq *http.Request) {
prepareRq(rq) util.PrepareRq(rq)
var ( var (
hyphaName = HyphaNameFromRq(rq, "edit") hyphaName = util.HyphaNameFromRq(rq, "edit")
h = hyphae.ByName(hyphaName) h = hyphae.ByName(hyphaName)
warning string warning string
textAreaFill string textAreaFill string
@ -158,7 +158,7 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
} }
util.HTTP200Page( util.HTTP200Page(
w, w,
base( views.BaseHTML(
"Edit "+hyphaName, "Edit "+hyphaName,
views.EditHTML(rq, hyphaName, textAreaFill, warning), views.EditHTML(rq, hyphaName, textAreaFill, warning),
u)) u))
@ -166,9 +166,9 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
// handlerUploadText uploads a new text part for the hypha. // handlerUploadText uploads a new text part for the hypha.
func handlerUploadText(w http.ResponseWriter, rq *http.Request) { func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
prepareRq(rq) util.PrepareRq(rq)
var ( var (
hyphaName = HyphaNameFromRq(rq, "upload-text") hyphaName = util.HyphaNameFromRq(rq, "upload-text")
h = hyphae.ByName(hyphaName) h = hyphae.ByName(hyphaName)
textData = rq.PostFormValue("text") textData = rq.PostFormValue("text")
action = rq.PostFormValue("action") action = rq.PostFormValue("action")
@ -190,7 +190,7 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
if action == "Preview" { if action == "Preview" {
util.HTTP200Page( util.HTTP200Page(
w, w,
base( views.BaseHTML(
"Preview "+hyphaName, "Preview "+hyphaName,
views.PreviewHTML( views.PreviewHTML(
rq, rq,
@ -206,10 +206,10 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
// handlerUploadBinary uploads a new binary part for the hypha. // handlerUploadBinary uploads a new binary part for the hypha.
func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) { func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) {
prepareRq(rq) util.PrepareRq(rq)
rq.ParseMultipartForm(10 << 20) // Set upload limit rq.ParseMultipartForm(10 << 20) // Set upload limit
var ( var (
hyphaName = HyphaNameFromRq(rq, "upload-binary") hyphaName = util.HyphaNameFromRq(rq, "upload-binary")
h = hyphae.ByName(hyphaName) h = hyphae.ByName(hyphaName)
u = user.FromRequest(rq) u = user.FromRequest(rq)
file, handler, err = rq.FormFile("binary") file, handler, err = rq.FormFile("binary")

View File

@ -1,4 +1,4 @@
package main package web
import ( import (
"fmt" "fmt"
@ -29,9 +29,9 @@ func init() {
} }
func handlerAttachment(w http.ResponseWriter, rq *http.Request) { func handlerAttachment(w http.ResponseWriter, rq *http.Request) {
prepareRq(rq) util.PrepareRq(rq)
var ( var (
hyphaName = HyphaNameFromRq(rq, "attachment") hyphaName = util.HyphaNameFromRq(rq, "attachment")
h = hyphae.ByName(hyphaName) h = hyphae.ByName(hyphaName)
u = user.FromRequest(rq) u = user.FromRequest(rq)
) )
@ -43,7 +43,7 @@ func handlerAttachment(w http.ResponseWriter, rq *http.Request) {
} }
func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) { func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) {
prepareRq(rq) util.PrepareRq(rq)
var ( var (
shorterUrl = strings.TrimPrefix(rq.URL.Path, "/primitive-diff/") shorterUrl = strings.TrimPrefix(rq.URL.Path, "/primitive-diff/")
firstSlashIndex = strings.IndexRune(shorterUrl, '/') 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 // handlerRevision displays a specific revision of text part a page
func handlerRevision(w http.ResponseWriter, rq *http.Request) { func handlerRevision(w http.ResponseWriter, rq *http.Request) {
prepareRq(rq) util.PrepareRq(rq)
var ( var (
shorterUrl = strings.TrimPrefix(rq.URL.Path, "/rev/") shorterUrl = strings.TrimPrefix(rq.URL.Path, "/rev/")
firstSlashIndex = strings.IndexRune(shorterUrl, '/') 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.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(http.StatusOK) 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. // handlerText serves raw source text of the hypha.
func handlerText(w http.ResponseWriter, rq *http.Request) { func handlerText(w http.ResponseWriter, rq *http.Request) {
prepareRq(rq) util.PrepareRq(rq)
hyphaName := HyphaNameFromRq(rq, "text") hyphaName := util.HyphaNameFromRq(rq, "text")
if h := hyphae.ByName(hyphaName); h.Exists { if h := hyphae.ByName(hyphaName); h.Exists {
log.Println("Serving", h.TextPath) log.Println("Serving", h.TextPath)
w.Header().Set("Content-Type", "text/plain; charset=utf-8") 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. // handlerBinary serves binary part of the hypha.
func handlerBinary(w http.ResponseWriter, rq *http.Request) { func handlerBinary(w http.ResponseWriter, rq *http.Request) {
prepareRq(rq) util.PrepareRq(rq)
hyphaName := HyphaNameFromRq(rq, "binary") hyphaName := util.HyphaNameFromRq(rq, "binary")
if h := hyphae.ByName(hyphaName); h.Exists { if h := hyphae.ByName(hyphaName); h.Exists {
log.Println("Serving", h.BinaryPath) log.Println("Serving", h.BinaryPath)
w.Header().Set("Content-Type", mimetype.FromExtension(filepath.Ext(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. // 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) { func handlerHypha(w http.ResponseWriter, rq *http.Request) {
prepareRq(rq) util.PrepareRq(rq)
var ( var (
hyphaName = HyphaNameFromRq(rq, "page", "hypha") hyphaName = util.HyphaNameFromRq(rq, "page", "hypha")
h = hyphae.ByName(hyphaName) h = hyphae.ByName(hyphaName)
contents string contents string
openGraph string openGraph string

View File

@ -1,5 +1,5 @@
// http_stuff.go is used for meta stuff about the wiki or all hyphae at once. // http_stuff.go is used for meta stuff about the wiki or all hyphae at once.
package main package web
import ( import (
"github.com/bouncepaw/mycorrhiza/cfg" "github.com/bouncepaw/mycorrhiza/cfg"
@ -25,22 +25,22 @@ func init() {
// handlerList shows a list of all hyphae in the wiki in random order. // handlerList shows a list of all hyphae in the wiki in random order.
func handlerList(w http.ResponseWriter, rq *http.Request) { func handlerList(w http.ResponseWriter, rq *http.Request) {
prepareRq(rq) util.PrepareRq(rq)
util.HTTP200Page(w, base("List of pages", views.HyphaListHTML(), user.FromRequest(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. // handlerReindex reindexes all hyphae by checking the wiki storage directory anew.
func handlerReindex(w http.ResponseWriter, rq *http.Request) { func handlerReindex(w http.ResponseWriter, rq *http.Request) {
prepareRq(rq) util.PrepareRq(rq)
if ok := user.CanProceed(rq, "reindex"); !ok { if ok := user.CanProceed(rq, "reindex"); !ok {
HttpErr(w, http.StatusForbidden, cfg.HomeHypha, "Not enough rights", "You must be an admin to reindex hyphae.") HttpErr(w, http.StatusForbidden, cfg.HomeHypha, "Not enough rights", "You must be an admin to reindex hyphae.")
log.Println("Rejected", rq.URL) log.Println("Rejected", rq.URL)
return return
} }
hyphae.ResetCount() hyphae.ResetCount()
log.Println("Wiki storage directory is", WikiDir) log.Println("Wiki storage directory is", cfg.WikiDir)
log.Println("Start indexing hyphae...") log.Println("Start indexing hyphae...")
hyphae.Index(WikiDir) hyphae.Index(cfg.WikiDir)
log.Println("Indexed", hyphae.Count(), "hyphae") log.Println("Indexed", hyphae.Count(), "hyphae")
http.Redirect(w, rq, "/", http.StatusSeeOther) 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 // See https://mycorrhiza.lesarbr.es/hypha/configuration/header
func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) { func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) {
prepareRq(rq) util.PrepareRq(rq)
if ok := user.CanProceed(rq, "update-header-links"); !ok { 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.") HttpErr(w, http.StatusForbidden, cfg.HomeHypha, "Not enough rights", "You must be a moderator to update header links.")
log.Println("Rejected", rq.URL) log.Println("Rejected", rq.URL)
@ -61,7 +61,7 @@ func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) {
// handlerRandom redirects to a random hypha. // handlerRandom redirects to a random hypha.
func handlerRandom(w http.ResponseWriter, rq *http.Request) { func handlerRandom(w http.ResponseWriter, rq *http.Request) {
prepareRq(rq) util.PrepareRq(rq)
var ( var (
randomHyphaName string randomHyphaName string
amountOfHyphae = hyphae.Count() amountOfHyphae = hyphae.Count()
@ -85,7 +85,7 @@ func handlerRandom(w http.ResponseWriter, rq *http.Request) {
func handlerAbout(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.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(http.StatusOK) 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 { if err != nil {
log.Println(err) log.Println(err)
} }

127
web/web.go Normal file
View File

@ -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(
`<main class="main-width"><p>%s. <a href="/page/%s">Go back to the hypha.<a></p></main>`,
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)
})
}