diff --git a/main.go b/main.go index df478d7..b27535a 100644 --- a/main.go +++ b/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2017 Marius Schellenberger +// Copyright (C) 2018 Marius Schellenberger //go:generate ./templates.sh diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index a881164..5935e4c 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -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 } diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index b4bb16f..813c420 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2017 Marius Schellenberger +// Copyright (C) 2018 Marius Schellenberger package cmd diff --git a/pkg/db/db.go b/pkg/db/db.go index 90cfd83..be73cbf 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -1,4 +1,4 @@ -// Copyright (C) 2017 Marius Schellenberger +// Copyright (C) 2018 Marius Schellenberger package db diff --git a/pkg/db/dump.go b/pkg/db/dump.go index 55f3c93..8fb7230 100644 --- a/pkg/db/dump.go +++ b/pkg/db/dump.go @@ -1,4 +1,4 @@ -// Copyright (C) 2017 Marius Schellenberger +// Copyright (C) 2018 Marius Schellenberger package db diff --git a/pkg/db/helper.go b/pkg/db/helper.go index e2dc880..ba527c8 100644 --- a/pkg/db/helper.go +++ b/pkg/db/helper.go @@ -7,6 +7,7 @@ import ( var reservedUsers = []string{ "all", + "blacklist", "login", "logout", "new", diff --git a/pkg/db/page.go b/pkg/db/page.go index f8ce39b..eb46bde 100644 --- a/pkg/db/page.go +++ b/pkg/db/page.go @@ -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 } diff --git a/pkg/db/user.go b/pkg/db/user.go index 28f3502..816f796 100644 --- a/pkg/db/user.go +++ b/pkg/db/user.go @@ -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 { diff --git a/pkg/log/log.go b/pkg/log/log.go index 0fb6fa7..4640c78 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -1,4 +1,4 @@ -// Copyright (C) 2017 Marius Schellenberger +// Copyright (C) 2018 Marius Schellenberger package log diff --git a/pkg/render/render.go b/pkg/render/render.go index d52a106..1c1b8ee 100644 --- a/pkg/render/render.go +++ b/pkg/render/render.go @@ -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("((.+)()") - reLink = regexp.MustCompile(`id="(.+)">`) + //reHeader = regexp.MustCompile("((.+)()") + reHeader = regexp.MustCompile("()(.+)()") + reLink = regexp.MustCompile(`id="(.+?)"`) whiteSpace = regexp.MustCompile(`\s+`) reIndex = regexp.MustCompile("(.+)") 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, `${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 += `` + r.Title + "" + `
` + r.Text + "

" - } return } diff --git a/pkg/server/context.go b/pkg/server/context.go index f6c3cae..a221f0c 100644 --- a/pkg/server/context.go +++ b/pkg/server/context.go @@ -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 { diff --git a/pkg/server/handler.go b/pkg/server/handler.go index 9ec70a5..2f4c58b 100644 --- a/pkg/server/handler.go +++ b/pkg/server/handler.go @@ -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) } diff --git a/pkg/server/routes.go b/pkg/server/routes.go index fdb8c0e..bd1e9c8 100644 --- a/pkg/server/routes.go +++ b/pkg/server/routes.go @@ -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", diff --git a/pkg/server/templates.go b/pkg/server/templates.go index 60bac8b..66b8f3a 100644 --- a/pkg/server/templates.go +++ b/pkg/server/templates.go @@ -18,13 +18,11 @@ const (

{{.BodyTitle}}

-
+
    - {{if .Data}} {{range $item := .Data}}
  • {{$item.Title}}
  • {{end}} - {{end}}
{{end}}` @@ -32,80 +30,65 @@ const ( GoWiki | {{.Title}} - - + + - -