2020-08-31 17:52:26 +00:00
|
|
|
//go:generate go get -u github.com/valyala/quicktemplate/qtc
|
|
|
|
//go:generate qtc -dir=templates
|
2020-06-12 16:22:02 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2020-08-05 15:08:59 +00:00
|
|
|
"fmt"
|
2020-12-15 18:59:36 +00:00
|
|
|
"io/ioutil"
|
2020-06-13 11:18:11 +00:00
|
|
|
"log"
|
2020-08-31 18:35:36 +00:00
|
|
|
"math/rand"
|
2020-06-13 11:18:11 +00:00
|
|
|
"net/http"
|
2020-06-12 16:22:02 +00:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2020-08-05 15:08:59 +00:00
|
|
|
"regexp"
|
2020-12-15 18:59:36 +00:00
|
|
|
"strings"
|
2020-08-08 20:10:28 +00:00
|
|
|
|
|
|
|
"github.com/bouncepaw/mycorrhiza/history"
|
2021-01-02 21:25:04 +00:00
|
|
|
"github.com/bouncepaw/mycorrhiza/hyphae"
|
2020-08-31 17:52:26 +00:00
|
|
|
"github.com/bouncepaw/mycorrhiza/templates"
|
2020-11-15 12:58:13 +00:00
|
|
|
"github.com/bouncepaw/mycorrhiza/user"
|
2020-08-09 19:33:47 +00:00
|
|
|
"github.com/bouncepaw/mycorrhiza/util"
|
2020-06-12 16:22:02 +00:00
|
|
|
)
|
|
|
|
|
2020-08-05 15:08:59 +00:00
|
|
|
// WikiDir is a rooted path to the wiki storage directory.
|
|
|
|
var WikiDir string
|
2020-06-14 08:12:22 +00:00
|
|
|
|
2020-10-02 15:31:59 +00:00
|
|
|
// HyphaPattern is a pattern which all hyphae must match.
|
2020-11-26 18:41:26 +00:00
|
|
|
var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}]+`)
|
2020-07-02 19:03:30 +00:00
|
|
|
|
2020-08-05 15:08:59 +00:00
|
|
|
// HyphaStorage is a mapping between canonical hypha names and their meta information.
|
|
|
|
var HyphaStorage = make(map[string]*HyphaData)
|
2020-06-14 08:12:22 +00:00
|
|
|
|
2020-08-05 20:19:14 +00:00
|
|
|
// IterateHyphaNamesWith is a closure to be passed to subpackages to let them iterate all hypha names read-only.
|
|
|
|
func IterateHyphaNamesWith(f func(string)) {
|
2020-10-25 13:50:14 +00:00
|
|
|
for hyphaName := range HyphaStorage {
|
2020-08-05 20:19:14 +00:00
|
|
|
f(hyphaName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-05 15:08:59 +00:00
|
|
|
// 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)
|
2021-01-24 07:30:14 +00:00
|
|
|
fmt.Fprint(
|
|
|
|
w,
|
|
|
|
base(
|
|
|
|
title,
|
|
|
|
fmt.Sprintf(
|
|
|
|
`<main><p>%s. <a href="/page/%s">Go back to the hypha.<a></p></main>`,
|
|
|
|
errMsg,
|
|
|
|
name,
|
|
|
|
),
|
|
|
|
user.EmptyUser(),
|
|
|
|
),
|
|
|
|
)
|
2020-08-05 15:08:59 +00:00
|
|
|
}
|
2020-06-14 08:12:22 +00:00
|
|
|
|
2020-08-05 15:08:59 +00:00
|
|
|
// Show all hyphae
|
|
|
|
func handlerList(w http.ResponseWriter, rq *http.Request) {
|
|
|
|
log.Println(rq.URL)
|
2020-08-31 18:16:22 +00:00
|
|
|
var (
|
|
|
|
tbody string
|
2021-01-02 21:25:04 +00:00
|
|
|
pageCount = hyphae.Count()
|
2021-01-24 07:30:14 +00:00
|
|
|
u = user.FromRequest(rq)
|
2020-08-31 18:16:22 +00:00
|
|
|
)
|
|
|
|
for hyphaName, data := range HyphaStorage {
|
2020-10-22 17:12:12 +00:00
|
|
|
tbody += templates.HyphaListRowHTML(hyphaName, ExtensionToMime(filepath.Ext(data.binaryPath)), data.binaryPath != "")
|
2020-08-05 15:08:59 +00:00
|
|
|
}
|
2021-01-24 07:30:14 +00:00
|
|
|
util.HTTP200Page(w, base("List of pages", templates.HyphaListHTML(tbody, pageCount), u))
|
2020-08-05 15:08:59 +00:00
|
|
|
}
|
2020-06-14 08:12:22 +00:00
|
|
|
|
2020-08-05 15:08:59 +00:00
|
|
|
// This part is present in all html documents.
|
2020-08-31 17:52:26 +00:00
|
|
|
var base = templates.BaseHTML
|
2020-06-13 16:42:43 +00:00
|
|
|
|
2020-08-05 15:08:59 +00:00
|
|
|
// Reindex all hyphae by checking the wiki storage directory anew.
|
|
|
|
func handlerReindex(w http.ResponseWriter, rq *http.Request) {
|
|
|
|
log.Println(rq.URL)
|
2020-11-15 12:58:13 +00:00
|
|
|
if ok := user.CanProceed(rq, "reindex"); !ok {
|
|
|
|
HttpErr(w, http.StatusForbidden, util.HomePage, "Not enough rights", "You must be an admin to reindex hyphae.")
|
|
|
|
log.Println("Rejected", rq.URL)
|
|
|
|
return
|
|
|
|
}
|
2021-01-24 07:06:37 +00:00
|
|
|
hyphae.ResetCount()
|
2020-08-05 15:08:59 +00:00
|
|
|
HyphaStorage = make(map[string]*HyphaData)
|
|
|
|
log.Println("Wiki storage directory is", WikiDir)
|
|
|
|
log.Println("Start indexing hyphae...")
|
|
|
|
Index(WikiDir)
|
2021-01-02 21:25:04 +00:00
|
|
|
log.Println("Indexed", hyphae.Count(), "hyphae")
|
2021-01-23 19:00:58 +00:00
|
|
|
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update header links by reading the configured hypha, if there is any, or resorting to default values.
|
|
|
|
func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) {
|
|
|
|
log.Println(rq.URL)
|
|
|
|
if ok := user.CanProceed(rq, "update-header-links"); !ok {
|
|
|
|
HttpErr(w, http.StatusForbidden, util.HomePage, "Not enough rights", "You must be a moderator to update header links.")
|
|
|
|
log.Println("Rejected", rq.URL)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
setHeaderLinks()
|
|
|
|
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
2020-08-05 15:08:59 +00:00
|
|
|
}
|
2020-06-14 08:12:22 +00:00
|
|
|
|
2020-08-31 18:35:36 +00:00
|
|
|
// Redirect to a random hypha.
|
|
|
|
func handlerRandom(w http.ResponseWriter, rq *http.Request) {
|
|
|
|
log.Println(rq.URL)
|
|
|
|
var randomHyphaName string
|
2021-01-02 21:25:04 +00:00
|
|
|
i := rand.Intn(hyphae.Count())
|
2020-08-31 18:35:36 +00:00
|
|
|
for hyphaName := range HyphaStorage {
|
|
|
|
if i == 0 {
|
|
|
|
randomHyphaName = hyphaName
|
|
|
|
break
|
|
|
|
}
|
|
|
|
i--
|
|
|
|
}
|
|
|
|
http.Redirect(w, rq, "/page/"+randomHyphaName, http.StatusSeeOther)
|
|
|
|
}
|
|
|
|
|
2020-10-25 18:02:52 +00:00
|
|
|
func handlerStyle(w http.ResponseWriter, rq *http.Request) {
|
|
|
|
log.Println(rq.URL)
|
|
|
|
if _, err := os.Stat(WikiDir + "/static/common.css"); err == nil {
|
|
|
|
http.ServeFile(w, rq, WikiDir+"/static/common.css")
|
|
|
|
} else {
|
|
|
|
w.Header().Set("Content-Type", "text/css;charset=utf-8")
|
|
|
|
w.Write([]byte(templates.DefaultCSS()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-15 18:59:36 +00:00
|
|
|
func handlerIcon(w http.ResponseWriter, rq *http.Request) {
|
|
|
|
iconName := strings.TrimPrefix(rq.URL.Path, "/static/icon/")
|
|
|
|
if iconName == "https" {
|
|
|
|
iconName = "http"
|
|
|
|
}
|
|
|
|
files, err := ioutil.ReadDir(WikiDir + "/static/icon")
|
|
|
|
if err == nil {
|
|
|
|
for _, f := range files {
|
|
|
|
if strings.HasPrefix(f.Name(), iconName+"-protocol-icon") {
|
|
|
|
http.ServeFile(w, rq, WikiDir+"/static/icon/"+f.Name())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "image/svg+xml")
|
|
|
|
switch iconName {
|
|
|
|
case "gemini":
|
|
|
|
w.Write([]byte(templates.IconGemini()))
|
|
|
|
case "mailto":
|
|
|
|
w.Write([]byte(templates.IconMailto()))
|
|
|
|
case "gopher":
|
|
|
|
w.Write([]byte(templates.IconGopher()))
|
|
|
|
default:
|
|
|
|
w.Write([]byte(templates.IconHTTP()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-23 13:45:17 +00:00
|
|
|
func handlerAbout(w http.ResponseWriter, rq *http.Request) {
|
|
|
|
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
|
|
|
w.WriteHeader(http.StatusOK)
|
2021-01-24 07:30:14 +00:00
|
|
|
w.Write([]byte(base("About "+util.SiteName, templates.AboutHTML(), user.FromRequest(rq))))
|
2021-01-23 13:45:17 +00:00
|
|
|
}
|
|
|
|
|
2020-12-29 08:10:11 +00:00
|
|
|
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`))
|
|
|
|
}
|
|
|
|
|
2020-08-05 15:08:59 +00:00
|
|
|
func main() {
|
|
|
|
log.Println("Running MycorrhizaWiki β")
|
2020-10-25 15:06:51 +00:00
|
|
|
parseCliArgs()
|
2020-08-19 18:54:23 +00:00
|
|
|
if err := os.Chdir(WikiDir); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2020-08-05 15:08:59 +00:00
|
|
|
log.Println("Wiki storage directory is", WikiDir)
|
|
|
|
Index(WikiDir)
|
2021-01-02 21:25:04 +00:00
|
|
|
log.Println("Indexed", hyphae.Count(), "hyphae")
|
2020-08-05 15:08:59 +00:00
|
|
|
|
2020-08-08 20:10:28 +00:00
|
|
|
history.Start(WikiDir)
|
2021-01-23 19:00:58 +00:00
|
|
|
setHeaderLinks()
|
2020-08-08 20:10:28 +00:00
|
|
|
|
2020-12-08 09:04:24 +00:00
|
|
|
// See http_readers.go for /page/, /text/, /binary/
|
2021-01-19 18:08:59 +00:00
|
|
|
// See http_mutators.go for /upload-binary/, /upload-text/, /edit/, /delete-ask/, /delete-confirm/, /rename-ask/, /rename-confirm/, /unattach-ask/, /unattach-confirm/
|
2020-11-14 13:03:06 +00:00
|
|
|
// See http_auth.go for /login, /login-data, /logout, /logout-confirm
|
2020-12-08 09:04:24 +00:00
|
|
|
// See http_history.go for /history/, /recent-changes
|
2020-08-05 15:08:59 +00:00
|
|
|
http.HandleFunc("/list", handlerList)
|
|
|
|
http.HandleFunc("/reindex", handlerReindex)
|
2021-01-23 19:00:58 +00:00
|
|
|
http.HandleFunc("/update-header-links", handlerUpdateHeaderLinks)
|
2020-08-31 18:35:36 +00:00
|
|
|
http.HandleFunc("/random", handlerRandom)
|
2021-01-23 13:45:17 +00:00
|
|
|
http.HandleFunc("/about", handlerAbout)
|
2020-12-08 09:04:24 +00:00
|
|
|
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(WikiDir+"/static"))))
|
2020-08-05 15:08:59 +00:00
|
|
|
http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, rq *http.Request) {
|
|
|
|
http.ServeFile(w, rq, WikiDir+"/static/favicon.ico")
|
|
|
|
})
|
2020-10-25 18:02:52 +00:00
|
|
|
http.HandleFunc("/static/common.css", handlerStyle)
|
2020-12-15 18:59:36 +00:00
|
|
|
http.HandleFunc("/static/icon/", handlerIcon)
|
2020-08-05 15:08:59 +00:00
|
|
|
http.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) {
|
2020-10-25 15:06:51 +00:00
|
|
|
http.Redirect(w, rq, "/page/"+util.HomePage, http.StatusSeeOther)
|
2020-08-05 15:08:59 +00:00
|
|
|
})
|
2020-12-29 08:10:11 +00:00
|
|
|
http.HandleFunc("/robots.txt", handlerRobotsTxt)
|
2020-10-25 15:06:51 +00:00
|
|
|
log.Fatal(http.ListenAndServe("0.0.0.0:"+util.ServerPort, nil))
|
2020-06-12 16:22:02 +00:00
|
|
|
}
|