mirror of
https://github.com/osmarks/mycorrhiza.git
synced 2024-12-13 05:50:27 +00:00
Merge remote-tracking branch 'upstream/master' into rss-grouping
This commit is contained in:
commit
b9a8a60edf
@ -1,13 +1,8 @@
|
||||
# 🍄 Mycorrhiza Wiki
|
||||
**Mycorrhiza Wiki** is a lightweight file-system wiki engine that uses Git for keeping history. [Main wiki](https://mycorrhiza.wiki)
|
||||
|
||||
<img src="https://mycorrhiza.wiki/binary/release/1.4/screenshot" alt="A screenshot of mycorrhiza.wiki's home page in the Safari browser" width="600">
|
||||
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/bouncepaw/mycorrhiza)](https://goreportcard.com/report/github.com/bouncepaw/mycorrhiza)
|
||||
|
||||
**Mycorrhiza Wiki** is a lightweight file-system wiki engine that uses Git for keeping history.
|
||||
|
||||
[👉 Main wiki](https://mycorrhiza.wiki)
|
||||
|
||||
## Features
|
||||
|
||||
* **No database required.** Everything is stored in plain files. It makes installation super easy, and you can modify the content directly by yourself.
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/mimetype"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
// Index finds all hypha files in the full `path` and saves them to the hypha storage.
|
||||
@ -20,7 +19,7 @@ func Index(path string) {
|
||||
}(ch)
|
||||
|
||||
for h := range ch {
|
||||
// At this time it is safe to ignore the mutex, because there is only one worker.
|
||||
// It's safe to ignore the mutex because there is a single worker right now.
|
||||
if oh := ByName(h.Name); oh.Exists {
|
||||
oh.MergeIn(h)
|
||||
} else {
|
||||
@ -32,7 +31,9 @@ func Index(path string) {
|
||||
log.Println("Indexed", Count(), "hyphae")
|
||||
}
|
||||
|
||||
// indexHelper finds all hypha files in the full `path` and sends them to the channel. Handling of duplicate entries and attachment and counting them is up to the caller.
|
||||
// indexHelper finds all hypha files in the full `path` and sends them to the
|
||||
// channel. Handling of duplicate entries and attachment and counting them is
|
||||
// up to the caller.
|
||||
func indexHelper(path string, nestLevel uint, ch chan *Hypha) {
|
||||
nodes, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
@ -40,10 +41,10 @@ func indexHelper(path string, nestLevel uint, ch chan *Hypha) {
|
||||
}
|
||||
|
||||
for _, node := range nodes {
|
||||
// If this hypha looks like it can be a hypha path, go deeper. Do not touch the .git and static folders for they have an administrative importance!
|
||||
if node.IsDir() &&
|
||||
util.IsCanonicalName(node.Name()) &&
|
||||
node.Name() != ".git" &&
|
||||
// If this hypha looks like it can be a hypha path, go deeper. Do not
|
||||
// touch the .git and static folders for they have an administrative
|
||||
// importance!
|
||||
if node.IsDir() && IsValidName(node.Name()) && node.Name() != ".git" &&
|
||||
!(nestLevel == 0 && node.Name() == "static") {
|
||||
indexHelper(filepath.Join(path, node.Name()), nestLevel+1, ch)
|
||||
continue
|
||||
|
@ -2,16 +2,31 @@
|
||||
package hyphae
|
||||
|
||||
import (
|
||||
"github.com/bouncepaw/mycorrhiza/files"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/files"
|
||||
)
|
||||
|
||||
// HyphaPattern is a pattern which all hyphae must match.
|
||||
// HyphaPattern is a pattern which all hyphae names must match.
|
||||
var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"'&%{}]+`)
|
||||
|
||||
// IsValidName checks for invalid characters and path traversals.
|
||||
func IsValidName(hyphaName string) bool {
|
||||
if !HyphaPattern.MatchString(hyphaName) {
|
||||
return false
|
||||
}
|
||||
for _, segment := range strings.Split(hyphaName, "/") {
|
||||
if segment == ".git" || segment == ".." {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Hypha keeps vital information about a hypha
|
||||
type Hypha struct {
|
||||
sync.RWMutex
|
||||
|
@ -14,7 +14,7 @@ func canFactory(
|
||||
dispatcher func(*hyphae.Hypha, *user.User, *l18n.Localizer) (string, string),
|
||||
noRightsMsg string,
|
||||
notExistsMsg string,
|
||||
careAboutExistence bool,
|
||||
mustExist bool,
|
||||
) func(*user.User, *hyphae.Hypha, *l18n.Localizer) (string, error) {
|
||||
return func(u *user.User, h *hyphae.Hypha, lc *l18n.Localizer) (string, error) {
|
||||
if !u.CanProceed(action) {
|
||||
@ -22,7 +22,7 @@ func canFactory(
|
||||
return lc.Get("ui.act_no_rights"), errors.New(lc.Get(noRightsMsg))
|
||||
}
|
||||
|
||||
if careAboutExistence && !h.Exists {
|
||||
if mustExist && !h.Exists {
|
||||
rejectLogger(h, u, "does not exist")
|
||||
return lc.Get("ui.act_notexist"), errors.New(lc.Get(notExistsMsg))
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ func canRenameThisToThat(oh *hyphae.Hypha, nh *hyphae.Hypha, u *user.User, lc *l
|
||||
return lc.Get("ui.rename_noname"), errors.New(lc.Get("ui.rename_noname_tip"))
|
||||
}
|
||||
|
||||
if !hyphae.HyphaPattern.MatchString(nh.Name) {
|
||||
if !hyphae.IsValidName(nh.Name) {
|
||||
rejectRenameLog(oh, u, fmt.Sprintf("new name ‘%s’ invalid", nh.Name))
|
||||
return lc.Get("ui.rename_badname"), errors.New(lc.Get("ui.rename_badname_tip", &l18n.Replacements{"chars": "<code>^?!:#@><*|\"\\'&%</code>"}))
|
||||
}
|
||||
|
@ -72,8 +72,7 @@ func uploadHelp(h *hyphae.Hypha, hop *history.Op, ext string, data []byte, u *us
|
||||
originalFullPath = &h.TextPath
|
||||
originalText = "" // for backlink update
|
||||
)
|
||||
// Reject if the path is outside the hyphae dir
|
||||
if !strings.HasPrefix(fullPath, files.HyphaeDir()) {
|
||||
if !isValidPath(fullPath) || !hyphae.IsValidName(h.Name) {
|
||||
err := errors.New("bad path")
|
||||
return hop.WithErrAbort(err), err.Error()
|
||||
}
|
||||
@ -110,3 +109,7 @@ func uploadHelp(h *hyphae.Hypha, hop *history.Op, ext string, data []byte, u *us
|
||||
}
|
||||
return hop.WithFiles(fullPath).WithUser(u).Apply(), ""
|
||||
}
|
||||
|
||||
func isValidPath(pathname string) bool {
|
||||
return strings.HasPrefix(pathname, files.HyphaeDir())
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ func Register(username, password, group, source string, force bool) error {
|
||||
username = util.CanonicalName(username)
|
||||
|
||||
switch {
|
||||
case !util.IsPossibleUsername(username):
|
||||
case !IsValidUsername(username):
|
||||
return fmt.Errorf("illegal username ‘%s’", username)
|
||||
case !ValidGroup(group):
|
||||
return fmt.Errorf("invalid group ‘%s’", group)
|
||||
|
25
user/user.go
25
user/user.go
@ -2,6 +2,8 @@ package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -9,7 +11,9 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// User is a user (duh).
|
||||
var usernamePattern = regexp.MustCompile(`[^?!:#@><*|"'&%{}/]+`)
|
||||
|
||||
// User contains information about a given user required for identification.
|
||||
type User struct {
|
||||
// Name is a username. It must follow hypha naming rules.
|
||||
Name string `json:"name"`
|
||||
@ -117,3 +121,22 @@ func (user *User) ShowLockMaybe(w http.ResponseWriter, rq *http.Request) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsValidUsername checks if the given username is valid.
|
||||
func IsValidUsername(username string) bool {
|
||||
return username != "anon" && username != "wikimind" &&
|
||||
usernamePattern.MatchString(strings.TrimSpace(username)) &&
|
||||
usernameIsWhiteListed(username)
|
||||
}
|
||||
|
||||
func usernameIsWhiteListed(username string) bool {
|
||||
if !cfg.UseWhiteList {
|
||||
return true
|
||||
}
|
||||
for _, allowedUsername := range cfg.WhiteList {
|
||||
if allowedUsername == username {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
28
util/util.go
28
util/util.go
@ -6,7 +6,6 @@ import (
|
||||
"github.com/bouncepaw/mycorrhiza/files"
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/bouncepaw/mycomarkup/v2/util"
|
||||
@ -65,33 +64,6 @@ func CanonicalName(name string) string {
|
||||
return util.CanonicalName(name)
|
||||
}
|
||||
|
||||
// hyphaPattern is a pattern which all hypha names must match.
|
||||
var hyphaPattern = regexp.MustCompile(`[^?!:#@><*|"'&%{}]+`)
|
||||
|
||||
var usernamePattern = regexp.MustCompile(`[^?!:#@><*|"'&%{}/]+`)
|
||||
|
||||
// IsCanonicalName checks if the `name` is canonical.
|
||||
func IsCanonicalName(name string) bool {
|
||||
return hyphaPattern.MatchString(name)
|
||||
}
|
||||
|
||||
// IsPossibleUsername is true if the given username is ok. Same as IsCanonicalName, but cannot have / in it and cannot be equal to "anon" or "wikimind"
|
||||
func IsPossibleUsername(username string) bool {
|
||||
return username != "anon" && username != "wikimind" && usernameIsWhiteListed(username) && usernamePattern.MatchString(strings.TrimSpace(username))
|
||||
}
|
||||
|
||||
func usernameIsWhiteListed(username string) bool {
|
||||
if !cfg.UseWhiteList {
|
||||
return true
|
||||
}
|
||||
for _, allowedUsername := range cfg.WhiteList {
|
||||
if allowedUsername == username {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 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
|
||||
|
Loading…
Reference in New Issue
Block a user