mirror of
				https://github.com/osmarks/mycorrhiza.git
				synced 2025-10-31 23:53:01 +00:00 
			
		
		
		
	Improve validation helpers
Still, there are a lot of bugs in the shroom module to be fixed later.
This commit is contained in:
		| @@ -6,7 +6,6 @@ import ( | |||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
|  |  | ||||||
| 	"github.com/bouncepaw/mycorrhiza/mimetype" | 	"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. | // 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) | 	}(ch) | ||||||
|  |  | ||||||
| 	for h := range 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 { | 		if oh := ByName(h.Name); oh.Exists { | ||||||
| 			oh.MergeIn(h) | 			oh.MergeIn(h) | ||||||
| 		} else { | 		} else { | ||||||
| @@ -32,7 +31,9 @@ func Index(path string) { | |||||||
| 	log.Println("Indexed", Count(), "hyphae") | 	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) { | func indexHelper(path string, nestLevel uint, ch chan *Hypha) { | ||||||
| 	nodes, err := os.ReadDir(path) | 	nodes, err := os.ReadDir(path) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -40,10 +41,10 @@ func indexHelper(path string, nestLevel uint, ch chan *Hypha) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, node := range nodes { | 	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 this hypha looks like it can be a hypha path, go deeper. Do not | ||||||
| 		if node.IsDir() && | 		// touch the .git and static folders for they have an administrative | ||||||
| 			util.IsCanonicalName(node.Name()) && | 		// importance! | ||||||
| 			node.Name() != ".git" && | 		if node.IsDir() && IsValidName(node.Name()) && node.Name() != ".git" && | ||||||
| 			!(nestLevel == 0 && node.Name() == "static") { | 			!(nestLevel == 0 && node.Name() == "static") { | ||||||
| 			indexHelper(filepath.Join(path, node.Name()), nestLevel+1, ch) | 			indexHelper(filepath.Join(path, node.Name()), nestLevel+1, ch) | ||||||
| 			continue | 			continue | ||||||
|   | |||||||
| @@ -2,16 +2,31 @@ | |||||||
| package hyphae | package hyphae | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"github.com/bouncepaw/mycorrhiza/files" |  | ||||||
| 	"log" | 	"log" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"regexp" | 	"regexp" | ||||||
|  | 	"strings" | ||||||
| 	"sync" | 	"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(`[^?!:#@><*|"'&%{}]+`) | 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 | // Hypha keeps vital information about a hypha | ||||||
| type Hypha struct { | type Hypha struct { | ||||||
| 	sync.RWMutex | 	sync.RWMutex | ||||||
|   | |||||||
| @@ -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")) | 		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)) | 		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>"})) | 		return lc.Get("ui.rename_badname"), errors.New(lc.Get("ui.rename_badname_tip", &l18n.Replacements{"chars": "<code>^?!:#@><*|\"\\'&%</code>"})) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -72,7 +72,7 @@ func uploadHelp(h *hyphae.Hypha, hop *history.Op, ext string, data []byte, u *us | |||||||
| 		originalFullPath = &h.TextPath | 		originalFullPath = &h.TextPath | ||||||
| 		originalText     = "" // for backlink update | 		originalText     = "" // for backlink update | ||||||
| 	) | 	) | ||||||
| 	if isBadPath(fullPath) { | 	if !isValidPath(fullPath) || !hyphae.IsValidName(h.Name) { | ||||||
| 		err := errors.New("bad path") | 		err := errors.New("bad path") | ||||||
| 		return hop.WithErrAbort(err), err.Error() | 		return hop.WithErrAbort(err), err.Error() | ||||||
| 	} | 	} | ||||||
| @@ -110,8 +110,6 @@ func uploadHelp(h *hyphae.Hypha, hop *history.Op, ext string, data []byte, u *us | |||||||
| 	return hop.WithFiles(fullPath).WithUser(u).Apply(), "" | 	return hop.WithFiles(fullPath).WithUser(u).Apply(), "" | ||||||
| } | } | ||||||
|  |  | ||||||
| func isBadPath(pathname string) bool { | func isValidPath(pathname string) bool { | ||||||
| 	return !strings.HasPrefix(pathname, files.HyphaeDir()) || | 	return strings.HasPrefix(pathname, files.HyphaeDir()) | ||||||
| 		strings.Contains(pathname, "..") || |  | ||||||
| 		strings.Contains(pathname, "/.git/") |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								tools.go
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								tools.go
									
									
									
									
									
								
							| @@ -1,3 +1,4 @@ | |||||||
|  | //go:build tools | ||||||
| // +build tools | // +build tools | ||||||
|  |  | ||||||
| package tools | package tools | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								tree/tree.go
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								tree/tree.go
									
									
									
									
									
								
							| @@ -127,7 +127,7 @@ type child struct { | |||||||
| func figureOutChildren(hyphaName string, exists bool) child { | func figureOutChildren(hyphaName string, exists bool) child { | ||||||
| 	var ( | 	var ( | ||||||
| 		descPrefix = hyphaName + "/" | 		descPrefix = hyphaName + "/" | ||||||
| 		child = child{hyphaName, true, make([]child, 0)} | 		child      = child{hyphaName, true, make([]child, 0)} | ||||||
| 	) | 	) | ||||||
|  |  | ||||||
| 	for desc := range hyphae.YieldExistingHyphae() { | 	for desc := range hyphae.YieldExistingHyphae() { | ||||||
| @@ -153,9 +153,9 @@ func addHyphaToChild(hyphaName, subPath string, child *child) { | |||||||
| 	} else { | 	} else { | ||||||
| 		var ( | 		var ( | ||||||
| 			firstSlash = strings.IndexRune(subPath, '/') | 			firstSlash = strings.IndexRune(subPath, '/') | ||||||
| 			firstDir = subPath[:firstSlash] | 			firstDir   = subPath[:firstSlash] | ||||||
| 			restOfPath = subPath[firstSlash + 1:] | 			restOfPath = subPath[firstSlash+1:] | ||||||
| 			subchild = findOrCreateSubchild(firstDir, child) | 			subchild   = findOrCreateSubchild(firstDir, child) | ||||||
| 		) | 		) | ||||||
| 		addHyphaToChild(hyphaName, restOfPath, subchild) | 		addHyphaToChild(hyphaName, restOfPath, subchild) | ||||||
| 	} | 	} | ||||||
| @@ -172,7 +172,7 @@ func findOrCreateSubchild(name string, baseChild *child) *child { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	baseChild.children = append(baseChild.children, child{fullName, false, make([]child, 0)}) | 	baseChild.children = append(baseChild.children, child{fullName, false, make([]child, 0)}) | ||||||
| 	return &baseChild.children[len(baseChild.children) - 1] | 	return &baseChild.children[len(baseChild.children)-1] | ||||||
| } | } | ||||||
|  |  | ||||||
| type sibling struct { | type sibling struct { | ||||||
|   | |||||||
| @@ -44,7 +44,7 @@ func Register(username, password, group, source string, force bool) error { | |||||||
| 	username = util.CanonicalName(username) | 	username = util.CanonicalName(username) | ||||||
|  |  | ||||||
| 	switch { | 	switch { | ||||||
| 	case !util.IsPossibleUsername(username): | 	case !IsValidUsername(username): | ||||||
| 		return fmt.Errorf("illegal username ‘%s’", username) | 		return fmt.Errorf("illegal username ‘%s’", username) | ||||||
| 	case !ValidGroup(group): | 	case !ValidGroup(group): | ||||||
| 		return fmt.Errorf("invalid group ‘%s’", group) | 		return fmt.Errorf("invalid group ‘%s’", group) | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								user/user.go
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								user/user.go
									
									
									
									
									
								
							| @@ -2,6 +2,8 @@ package user | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | 	"regexp" | ||||||
|  | 	"strings" | ||||||
| 	"sync" | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| @@ -9,7 +11,9 @@ import ( | |||||||
| 	"golang.org/x/crypto/bcrypt" | 	"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 { | type User struct { | ||||||
| 	// Name is a username. It must follow hypha naming rules. | 	// Name is a username. It must follow hypha naming rules. | ||||||
| 	Name         string    `json:"name"` | 	Name         string    `json:"name"` | ||||||
| @@ -117,3 +121,22 @@ func (user *User) ShowLockMaybe(w http.ResponseWriter, rq *http.Request) bool { | |||||||
| 	} | 	} | ||||||
| 	return false | 	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" | 	"github.com/bouncepaw/mycorrhiza/files" | ||||||
| 	"log" | 	"log" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"regexp" |  | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"github.com/bouncepaw/mycomarkup/v2/util" | 	"github.com/bouncepaw/mycomarkup/v2/util" | ||||||
| @@ -65,33 +64,6 @@ func CanonicalName(name string) string { | |||||||
| 	return util.CanonicalName(name) | 	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". | // 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 { | func HyphaNameFromRq(rq *http.Request, actions ...string) string { | ||||||
| 	p := rq.URL.Path | 	p := rq.URL.Path | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Umar Getagazov
					Umar Getagazov