upgraded bootstrap, fixed style, added share blacklisting
This commit is contained in:
parent
85f93bae69
commit
3fb1cfc17d
37 changed files with 811 additions and 892 deletions
6
pkg/cache/cache.go
vendored
6
pkg/cache/cache.go
vendored
|
|
@ -34,11 +34,13 @@ func (c *Cache) Add(title string, p *core.Page) {
|
|||
c.Cache[title] = &CachedPage{p, now}
|
||||
}
|
||||
|
||||
func (c *Cache) Get(title string) *core.Page {
|
||||
func (c *Cache) Get(title string) (p *core.Page) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
if cp, ok := c.Cache[title]; ok {
|
||||
return cp.Page
|
||||
p = new(core.Page)
|
||||
*p = *cp.Page
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (C) 2017 Marius Schellenberger
|
||||
// Copyright (C) 2018 Marius Schellenberger
|
||||
|
||||
package cmd
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (C) 2017 Marius Schellenberger
|
||||
// Copyright (C) 2018 Marius Schellenberger
|
||||
|
||||
package db
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (C) 2017 Marius Schellenberger
|
||||
// Copyright (C) 2018 Marius Schellenberger
|
||||
|
||||
package db
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
var reservedUsers = []string{
|
||||
"all",
|
||||
"blacklist",
|
||||
"login",
|
||||
"logout",
|
||||
"new",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"git.giftfish.de/ston1th/gowiki/pkg/core"
|
||||
"git.giftfish.de/ston1th/gowiki/pkg/render"
|
||||
"git.giftfish.de/ston1th/gowiki/pkg/store"
|
||||
"git.giftfish.de/ston1th/gowiki/pkg/util"
|
||||
"sort"
|
||||
)
|
||||
|
||||
|
|
@ -65,7 +64,7 @@ func (db *DB) CreatePage(title, section, markdown, username string, p core.Permi
|
|||
err = errPrivateWikiPage
|
||||
return
|
||||
}
|
||||
st := util.StoreTitle(section, title)
|
||||
st := render.StoreTitle(section, title)
|
||||
if _, err = db.getPage(st, username); err != nil && err != store.ErrKeyNotFound {
|
||||
return
|
||||
}
|
||||
|
|
@ -104,7 +103,7 @@ func (db *DB) UpdatePage(title, section, markdown, username string, p core.Permi
|
|||
err = errPrivateWikiPage
|
||||
return
|
||||
}
|
||||
st := util.StoreTitle(section, title)
|
||||
st := render.StoreTitle(section, title)
|
||||
page, err := db.getPage(st, username)
|
||||
if err != nil {
|
||||
return
|
||||
|
|
@ -140,7 +139,7 @@ func (db *DB) DeletePage(title, section, username string) (err error) {
|
|||
err = errDeleteIndexPage
|
||||
return
|
||||
}
|
||||
st := util.StoreTitle(section, title)
|
||||
st := render.StoreTitle(section, title)
|
||||
if page, err = db.getPage(st, username); err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ var (
|
|||
errUserReserved = dbErr("username is reserved")
|
||||
)
|
||||
|
||||
func (db *DB) GetUsers() (users core.Users, err error) {
|
||||
func (db *DB) DumpUsers() (users core.Users, err error) {
|
||||
db.users.ForEach(func(username string, _ []byte) error {
|
||||
u, err := db.GetUser(username)
|
||||
if err == nil {
|
||||
|
|
@ -21,6 +21,14 @@ func (db *DB) GetUsers() (users core.Users, err error) {
|
|||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) GetUsers() (users core.Users, err error) {
|
||||
users, err = db.DumpUsers()
|
||||
for i := range users {
|
||||
users[i].Password = ""
|
||||
}
|
||||
sort.Sort(users)
|
||||
return
|
||||
}
|
||||
|
|
@ -41,28 +49,7 @@ func (db *DB) UserExists(username string) error {
|
|||
}
|
||||
|
||||
// TODO
|
||||
/*func (bs *BoltStore) DumpUsers() (users []User, err error) {
|
||||
var buf [][]byte
|
||||
err = bs.db.View(func(tx *bolt.Tx) error {
|
||||
return tx.Bucket([]byte(userTable)).ForEach(func(k, v []byte) error {
|
||||
buf = append(buf, copyBuf(v))
|
||||
return nil
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, b := range buf {
|
||||
u := new(User)
|
||||
if err = bs.Unmarshal(b, u); err != nil {
|
||||
return
|
||||
}
|
||||
users = append(users, *u)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (bs *BoltStore) RestoreUsers(users []User) error {
|
||||
/*func (bs *BoltStore) RestoreUsers(users []User) error {
|
||||
return bs.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte(userTable))
|
||||
err := b.ForEach(func(k, v []byte) error {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (C) 2017 Marius Schellenberger
|
||||
// Copyright (C) 2018 Marius Schellenberger
|
||||
|
||||
package log
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (C) 2017 Marius Schellenberger
|
||||
// Copyright (C) 2018 Marius Schellenberger
|
||||
|
||||
package render
|
||||
|
||||
|
|
@ -22,8 +22,9 @@ func makeFilter() analysis.CharFilter {
|
|||
}
|
||||
|
||||
var (
|
||||
reHeader = regexp.MustCompile("(<h[1-6])>(.+)(</h[1-6]>)")
|
||||
reLink = regexp.MustCompile(`id="(.+)">`)
|
||||
//reHeader = regexp.MustCompile("(<h[1-6])>(.+)(</h[1-6]>)")
|
||||
reHeader = regexp.MustCompile("(<h[1-6]>)(.+)(</h[1-6]>)")
|
||||
reLink = regexp.MustCompile(`id="(.+?)"`)
|
||||
whiteSpace = regexp.MustCompile(`\s+`)
|
||||
reIndex = regexp.MustCompile("<h([1-6])>(.+)</h[1-6]>")
|
||||
titleRe = regexp.MustCompile("[^a-zA-Z0-9 -]+")
|
||||
|
|
@ -42,13 +43,20 @@ func Render(p *core.Page) string {
|
|||
|
||||
func spaceReplace(s string) string {
|
||||
return whiteSpace.ReplaceAllString(s, "+")
|
||||
//TODO remove
|
||||
//return strings.Replace(s, " ", "+", -1)
|
||||
}
|
||||
|
||||
func makeLinkTitle(t string) (newTitle string, title string) {
|
||||
title = titleRe.ReplaceAllString(t, "")
|
||||
newTitle = spaceReplace(title)
|
||||
func Title(title string) string {
|
||||
return spaceReplace(titleRe.ReplaceAllString(title, ""))
|
||||
}
|
||||
|
||||
func StoreTitle(section, title string) string {
|
||||
return section + "/" + title
|
||||
}
|
||||
|
||||
func UnstoreTitle(storeTitle string) (section, title string) {
|
||||
if a := strings.Split(storeTitle, "/"); len(a) == 2 {
|
||||
return a[0], a[1]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -56,10 +64,11 @@ func buildIndex(html string) (string, string) {
|
|||
var ret string
|
||||
m := make(map[string]int)
|
||||
hn := reIndex.FindAllStringSubmatch(html, -1)
|
||||
html = reHeader.ReplaceAllString(html, `${1} id="${2}">${2}${3}`)
|
||||
//html = reHeader.ReplaceAllString(html, `${1} id="${2}">${2}${3}`)
|
||||
html = reHeader.ReplaceAllString(html, `<a id="${2}" class="anchor"></a>${1}${2}${3}`)
|
||||
html = reLink.ReplaceAllStringFunc(html, spaceReplace)
|
||||
for k, v := range hn {
|
||||
link, _ := makeLinkTitle(v[2])
|
||||
link := Title(v[2])
|
||||
i := m[link]
|
||||
m[link] = i + 1
|
||||
newLink := link + strconv.Itoa(i)
|
||||
|
|
@ -119,11 +128,9 @@ func closeTag(j int) (ret string) {
|
|||
}
|
||||
|
||||
func renderResult(res []core.Result) (html string) {
|
||||
//TODO fmt.Sprintf ?
|
||||
for _, r := range res {
|
||||
html += `<a href="/` + r.StoreTitle + `"><b>` + r.Title + "</b></a>" +
|
||||
`<pre style="white-space: pre-wrap">` + r.Text + "</pre><br>"
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (C) 2017 Marius Schellenberger
|
||||
// Copyright (C) 2018 Marius Schellenberger
|
||||
|
||||
package server
|
||||
|
||||
|
|
@ -99,7 +99,7 @@ func (c *Context) Exec() {
|
|||
c.Data.Admin = c.Admin()
|
||||
c.Data.User = c.User()
|
||||
c.Data.Login = c.LoggedOn()
|
||||
c.Data.Time = time.Since(c.Time).Nanoseconds() / 1e6
|
||||
c.Data.Time = time.Since(c.Time).Nanoseconds() / 1e5
|
||||
|
||||
c.Err = c.T.Execute(c.Response, c.Data)
|
||||
if c.Err != nil {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
// Copyright (C) 2017 Marius Schellenberger
|
||||
// Copyright (C) 2018 Marius Schellenberger
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"git.giftfish.de/ston1th/gowiki/pkg/core"
|
||||
"git.giftfish.de/ston1th/gowiki/pkg/log"
|
||||
"git.giftfish.de/ston1th/gowiki/pkg/util"
|
||||
"git.giftfish.de/ston1th/gowiki/pkg/render"
|
||||
"git.giftfish.de/ston1th/jwt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
|
@ -122,14 +122,15 @@ func searchHandler(ctx *Context) {
|
|||
return
|
||||
}
|
||||
|
||||
res, err := ctx.Srv.DB.Index.Search(search)
|
||||
if err != nil {
|
||||
ctx.Data.Msg = err.Error()
|
||||
ctx.Exec()
|
||||
return
|
||||
}
|
||||
//TODO
|
||||
//res, err := ctx.Srv.DB.Index.Search(search)
|
||||
//if err != nil {
|
||||
// ctx.Data.Msg = err.Error()
|
||||
// ctx.Exec()
|
||||
// return
|
||||
//}
|
||||
//ctx.Data.Data = res
|
||||
ctx.Data.Search = search
|
||||
ctx.Data.Data = res
|
||||
ctx.Exec()
|
||||
}
|
||||
}
|
||||
|
|
@ -150,15 +151,17 @@ func pageNewHandler(ctx *Context) {
|
|||
ctx.Data = webData{Title: "New Page"}
|
||||
switch ctx.Method() {
|
||||
case "GET":
|
||||
ctx.Data.Data = core.Page{Perm: core.Public}
|
||||
ctx.Exec()
|
||||
case "POST":
|
||||
section := ctx.Form("section")
|
||||
title := ctx.Form("title")
|
||||
title := render.Title(ctx.Form("title"))
|
||||
markdown := ctx.Form("markdown")
|
||||
perm := ctx.Form("perm")
|
||||
perm := core.ParsePermString(ctx.Form("perm"))
|
||||
p := core.Page{
|
||||
Title: title,
|
||||
Markdown: markdown,
|
||||
Perm: perm,
|
||||
}
|
||||
ctx.Data.Data = p
|
||||
if !ctx.CheckXsrf(nil) {
|
||||
|
|
@ -167,7 +170,7 @@ func pageNewHandler(ctx *Context) {
|
|||
if section != core.WikiSection {
|
||||
section = ctx.User()
|
||||
}
|
||||
page, err := ctx.Srv.DB.CreatePage(title, section, markdown, ctx.User(), core.ParsePermString(perm))
|
||||
page, err := ctx.Srv.DB.CreatePage(title, section, markdown, ctx.User(), perm)
|
||||
if err != nil {
|
||||
ctx.Data.Msg = err.Error()
|
||||
ctx.Exec()
|
||||
|
|
@ -208,7 +211,7 @@ func pageHandler(ctx *Context) {
|
|||
ctx.Template("pageHandler")
|
||||
}
|
||||
}
|
||||
page, err := ctx.Srv.DB.GetPage(util.StoreTitle(section, title), user)
|
||||
page, err := ctx.Srv.DB.GetPage(render.StoreTitle(section, title), user)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
ctx.NotFound()
|
||||
|
|
@ -227,91 +230,135 @@ func pageMDHandler(f ctxHandler) ctxHandler {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO add share token blacklisting
|
||||
func pageShareHandler(ctx *Context) {
|
||||
ctx.Template("pageShareHandler")
|
||||
ctx.Data = webData{}
|
||||
section := ctx.Var("section")
|
||||
title := ctx.Var("title")
|
||||
title := render.Title(ctx.Var("title"))
|
||||
user := ctx.User()
|
||||
st := util.StoreTitle(section, title)
|
||||
st := render.StoreTitle(section, title)
|
||||
page, err := ctx.Srv.DB.GetPage(st, user)
|
||||
if err != nil {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
t := jwt.NewToken(map[string]interface{}{
|
||||
sharedClaim: user,
|
||||
sectionClaim: section,
|
||||
titleClaim: title,
|
||||
jwt.ExpClaim: jwt.NewExp(time.Hour * 24),
|
||||
}, nil)
|
||||
|
||||
if err = ctx.Srv.JWT.Sign(t); err != nil {
|
||||
log.Println("SignToken:", err)
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
/* TODO
|
||||
host := ctx.Request.Header.Get("X-Forwarded-For")
|
||||
if host == "" {
|
||||
ctx.Request.Host
|
||||
}
|
||||
ctx.Data.Data = "https://" + host + "/view/" + t.String()
|
||||
*/
|
||||
ctx.Data.Key = t.String()
|
||||
ctx.Data.Data = st
|
||||
ctx.Data.Title = page.Title
|
||||
ctx.Data.BodyTitle = page.Title
|
||||
ctx.Exec()
|
||||
switch ctx.Method() {
|
||||
case "GET":
|
||||
ctx.Exec()
|
||||
case "POST":
|
||||
duration := ctx.Form("duration")
|
||||
if !ctx.CheckXsrf(nil) {
|
||||
return
|
||||
}
|
||||
d, err := time.ParseDuration(duration)
|
||||
if err != nil {
|
||||
ctx.Data.Msg = err.Error()
|
||||
ctx.Exec()
|
||||
return
|
||||
}
|
||||
t := jwt.NewToken(map[string]interface{}{
|
||||
sharedClaim: user,
|
||||
sectionClaim: section,
|
||||
titleClaim: title,
|
||||
jwt.ExpClaim: jwt.NewExp(d),
|
||||
}, nil)
|
||||
|
||||
if err = ctx.Srv.JWT.Sign(t); err != nil {
|
||||
log.Println("SignToken:", err)
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
ctx.Data.Key = t.String()
|
||||
ctx.Exec()
|
||||
}
|
||||
}
|
||||
|
||||
func pageBlacklistHandler(ctx *Context) {
|
||||
ctx.Template("pageBlacklistHandler")
|
||||
ctx.Data = webData{
|
||||
Title: "Blacklist",
|
||||
BodyTitle: "Blacklist",
|
||||
}
|
||||
switch ctx.Method() {
|
||||
case "GET":
|
||||
ctx.Exec()
|
||||
case "POST":
|
||||
if !ctx.CheckXsrf(nil) {
|
||||
return
|
||||
}
|
||||
t, err := jwt.DecodeToken(ctx.Form("share"))
|
||||
if err != nil {
|
||||
ctx.Data.Msg = err.Error()
|
||||
ctx.Exec()
|
||||
return
|
||||
}
|
||||
err = ctx.Srv.JWT.Verify(t)
|
||||
if err != nil {
|
||||
ctx.Data.Msg = err.Error()
|
||||
ctx.Exec()
|
||||
return
|
||||
}
|
||||
user := t.Claims.GetString(sharedClaim)
|
||||
if user == "" {
|
||||
ctx.Data.Msg = "blacklisting denied: no share token"
|
||||
ctx.Exec()
|
||||
return
|
||||
}
|
||||
if ctx.User() != user {
|
||||
ctx.Data.Msg = "blacklisting denied: token was issued by " + user
|
||||
ctx.Exec()
|
||||
return
|
||||
}
|
||||
err = ctx.Srv.JWT.Invalidate(t)
|
||||
if err != nil {
|
||||
ctx.Data.Msg = err.Error()
|
||||
ctx.Exec()
|
||||
return
|
||||
}
|
||||
ctx.Exec()
|
||||
}
|
||||
}
|
||||
|
||||
func pageEditHandler(ctx *Context) {
|
||||
ctx.Template("pageEditHandler")
|
||||
ctx.Data = webData{}
|
||||
section := ctx.Var("section")
|
||||
title := ctx.Var("title")
|
||||
page, err := ctx.Srv.DB.GetPage(util.StoreTitle(section, title), ctx.User())
|
||||
title := render.Title(ctx.Var("title"))
|
||||
page, err := ctx.Srv.DB.GetPage(render.StoreTitle(section, title), ctx.User())
|
||||
if err != nil {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
ctx.Data.Title = page.Title
|
||||
ctx.Data.BodyTitle = page.Title
|
||||
switch ctx.Method() {
|
||||
case "GET":
|
||||
ctx.Data.Data = page
|
||||
ctx.Data.Title = page.Title
|
||||
ctx.Data.BodyTitle = page.Title
|
||||
ctx.Exec()
|
||||
case "POST":
|
||||
storeTitle := ctx.Form("store_title")
|
||||
section, title = util.UnstoreTitle(storeTitle)
|
||||
markdown := ctx.Form("markdown")
|
||||
perm := core.ParsePermString(ctx.Form("perm"))
|
||||
ctx.Data.Data = core.Page{
|
||||
Title: page.Title,
|
||||
StoreTitle: storeTitle,
|
||||
Markdown: markdown,
|
||||
Perm: perm,
|
||||
}
|
||||
ctx.Data.Title = page.Title
|
||||
ctx.Data.BodyTitle = page.Title
|
||||
page.Markdown = ctx.Form("markdown")
|
||||
page.Perm = core.ParsePermString(ctx.Form("perm"))
|
||||
ctx.Data.Data = page
|
||||
if !ctx.CheckXsrf(nil) {
|
||||
return
|
||||
}
|
||||
err = ctx.Srv.DB.UpdatePage(title, section, markdown, ctx.User(), perm)
|
||||
err = ctx.Srv.DB.UpdatePage(title, section, page.Markdown, ctx.User(), page.Perm)
|
||||
if err != nil {
|
||||
ctx.Data.Msg = err.Error()
|
||||
ctx.Exec()
|
||||
return
|
||||
}
|
||||
ctx.Redirect("/"+page.StoreTitle, 302)
|
||||
ctx.Redirect(page.StoreTitle, 302)
|
||||
}
|
||||
}
|
||||
|
||||
func pageDelHandler(ctx *Context) {
|
||||
section := ctx.Var("section")
|
||||
title := ctx.Var("title")
|
||||
st := util.StoreTitle(section, title)
|
||||
title := render.Title(ctx.Var("title"))
|
||||
st := render.StoreTitle(section, title)
|
||||
ctx.Template("pageDelHandler")
|
||||
ctx.Data.Data = st
|
||||
ctx.Data.Title = title
|
||||
|
|
@ -473,6 +520,7 @@ func userDelHandler(ctx *Context) {
|
|||
}
|
||||
|
||||
func logoutHandler(ctx *Context) {
|
||||
ctx.Srv.JWT.Invalidate(&ctx.Token)
|
||||
ctx.SetCookie(nil)
|
||||
ctx.Redirect(core.IndexURI, 302)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,12 @@ var routes = []route{
|
|||
allHandler,
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
"/blacklist",
|
||||
authHandler(
|
||||
pageBlacklistHandler),
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
{
|
||||
"/user",
|
||||
adminAuthHandler(
|
||||
|
|
@ -105,7 +111,7 @@ var routes = []route{
|
|||
{
|
||||
"/{section}/{title}/share",
|
||||
pageShareHandler,
|
||||
[]string{"GET"},
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
{
|
||||
"/{section}/{title}/edit",
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,47 +0,0 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const timeFmt = "2006-01-02 15:04:05"
|
||||
|
||||
func now() string {
|
||||
return time.Now().Format(timeFmt)
|
||||
}
|
||||
|
||||
func since(t time.Time) string {
|
||||
s := time.Since(t)
|
||||
u := uint64(s)
|
||||
if u <= 0 {
|
||||
return "0s"
|
||||
}
|
||||
str := s.String()
|
||||
switch {
|
||||
case u >= uint64(time.Second):
|
||||
f, _ := strconv.ParseFloat(str[:len(str)-1], 64)
|
||||
return fmt.Sprintf("%.fs", f)
|
||||
case u < uint64(time.Microsecond):
|
||||
f, _ := strconv.ParseFloat(str[:len(str)-2], 64)
|
||||
return fmt.Sprintf("%.fns", f)
|
||||
case u < uint64(time.Millisecond):
|
||||
f, _ := strconv.ParseFloat(str[:len(str)-3], 64)
|
||||
return fmt.Sprintf("%.fµs", f)
|
||||
default:
|
||||
f, _ := strconv.ParseFloat(str[:len(str)-2], 64)
|
||||
return fmt.Sprintf("%.fms", f)
|
||||
}
|
||||
}
|
||||
func StoreTitle(section, title string) string {
|
||||
return section + "/" + title
|
||||
}
|
||||
|
||||
func UnstoreTitle(storeTitle string) (section, title string) {
|
||||
if a := strings.Split(storeTitle, "/"); len(a) == 2 {
|
||||
return a[0], a[1]
|
||||
}
|
||||
return
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue