Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

directory support for wiki #8891

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 117 additions & 32 deletions models/wiki.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@ import (
"net/url"
"os"
"path/filepath"
"reflect"
"regexp"
"strings"

"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/sync"
"code.gitea.io/gitea/modules/util"

"github.com/unknwon/com"
)

var (
reservedWikiNames = []string{"_pages", "_new", "_edit", "raw"}
reservedWikiNames = []string{"_pages", "_new", "_edit", "_delete", "raw"}
wikiWorkingPool = sync.NewExclusivePool()
)

Expand All @@ -28,9 +31,17 @@ func NormalizeWikiName(name string) string {
return strings.Replace(name, "-", " ", -1)
}

// WikiNameToSubURL converts a wiki name to its corresponding sub-URL.
// WikiNameToSubURL converts a wiki name to its corresponding sub-URL. This will escape dangerous letters.
func WikiNameToSubURL(name string) string {
return url.QueryEscape(strings.Replace(name, " ", "-", -1))
// remove path up
re1 := regexp.MustCompile(`(\.\.\/)`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be simpler:

name = strings.ReplaceAll(name, "/../", "/")

(And TrimSuffix/TrimPrefix for /.. and ../)

But strings starting with ../, ending with /.. or containing /../ should be rejected as invalid instead of sanitized, IMHO.

name = re1.ReplaceAllString(name, "")
// trim whitespace and /
name = strings.Trim(name, "\n\r\t /")
name = url.QueryEscape(name)
//restore spaces
re3 := regexp.MustCompile(`(?m)(%20|\+)`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regexps that are not dynamic should be stored outside of the function to avoid compiling them multiple times. That also makes it easier to make a unit test for them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, return strings.ReplaceAll(name, "+", "%20") should do the trick as well.

return re3.ReplaceAllString(name, "%20")
}

// WikiNameToFilename converts a wiki name to its corresponding filename.
Expand All @@ -39,17 +50,53 @@ func WikiNameToFilename(name string) string {
return url.QueryEscape(name) + ".md"
}

// WikiNameToPathFilename converts a wiki name to its corresponding filename, keep directory paths.
func WikiNameToPathFilename(name string) string {
var restore = [1][2]string{
{`(\.\.\/)`, ""}, // remove path up
}
for _, kv := range restore {
loopRe := regexp.MustCompile(kv[0])
name = loopRe.ReplaceAllString(name, kv[1])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my other comments above.

}
name = strings.Trim(name, "\n\r\t ./") // trim whitespace and / .
return name + ".md"
}

// FilenameToPathFilename converts a wiki filename to filename with filepath.
func FilenameToPathFilename(name string) string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import "net/url"

And you have net.PathEscape(), net.PathUnescape().

// restore spaces and slashes
var restore = [4][2]string{
{`(?m)%2F`, "/"}, //recover slashes /
{`(?m)(%20|\+)`, " "}, //restore spaces
{`(?m)(%25)`, "%"}, //restore %
{`(?m)(%26)`, "&"}, //restore &
}
for _, kv := range restore {
loopRe := regexp.MustCompile(kv[0])
name = loopRe.ReplaceAllString(name, kv[1])
}
return name
}

// WikiNameToRawPrefix Get raw file path inside wiki, removes last path element and returns
func WikiNameToRawPrefix(repositoryName string, wikiPage string) string {
a := strings.Split(wikiPage, "/")
a = a[:len(a)-1]
return util.URLJoin(repositoryName, "wiki", "raw", strings.Join(a, "/"))
}

// WikiFilenameToName converts a wiki filename to its corresponding page name.
func WikiFilenameToName(filename string) (string, error) {
func WikiFilenameToName(filename string) (string, string, error) {
if !strings.HasSuffix(filename, ".md") {
return "", ErrWikiInvalidFileName{filename}
return "", "", ErrWikiInvalidFileName{filename}
}
basename := filename[:len(filename)-3]
unescaped, err := url.QueryUnescape(basename)
if err != nil {
return "", err
return basename, basename, err
}
return NormalizeWikiName(unescaped), nil
return unescaped, basename, nil
}

// WikiCloneLink returns clone URLs of repository wiki.
Expand Down Expand Up @@ -97,6 +144,21 @@ func nameAllowed(name string) error {
return nil
}

// checkNewWikiFilename check filename or file exists inside repository
func checkNewWikiFilename(repo *git.Repository, name string) (bool, error) {
filesInIndex, err := repo.LsFiles(name)
if err != nil {
log.Error("%v", err)
return false, err
}
for _, file := range filesInIndex {
if file == name {
return true, ErrWikiAlreadyExist{name}
}
}
return false, nil
}

// updateWikiPage adds a new page to the repository wiki.
func (repo *Repository) updateWikiPage(doer *User, oldWikiName, newWikiName, content, message string, isNew bool) (err error) {
if err = nameAllowed(newWikiName); err != nil {
Expand Down Expand Up @@ -136,6 +198,9 @@ func (repo *Repository) updateWikiPage(doer *User, oldWikiName, newWikiName, con
}

gitRepo, err := git.OpenRepository(basePath)

fmt.Println(reflect.TypeOf(gitRepo))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be here


if err != nil {
log.Error("Unable to open temporary repository: %s (%v)", basePath, err)
return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err)
Expand All @@ -149,40 +214,50 @@ func (repo *Repository) updateWikiPage(doer *User, oldWikiName, newWikiName, con
}

newWikiPath := WikiNameToFilename(newWikiName)
newWikiDirPath := WikiNameToPathFilename(newWikiName)

if isNew {
filesInIndex, err := gitRepo.LsFiles(newWikiPath)
if err != nil {
log.Error("%v", err)
// check file already exists - plain structure
if _, err := checkNewWikiFilename(gitRepo, newWikiPath); err != nil {
return err
}
for _, file := range filesInIndex {
if file == newWikiPath {
return ErrWikiAlreadyExist{newWikiPath}
}

// check file already exists - directory structure
if _, err := checkNewWikiFilename(gitRepo, newWikiDirPath); err != nil {
return err
}
} else {
var found bool

// check file already exists - plain structure
oldWikiPath := WikiNameToFilename(oldWikiName)
filesInIndex, err := gitRepo.LsFiles(oldWikiPath)
if err != nil {
log.Error("%v", err)
if found, err = checkNewWikiFilename(gitRepo, oldWikiPath); err != nil && !found {
return err
}
found := false
for _, file := range filesInIndex {
if file == oldWikiPath {
found = true
break
if found {
err := gitRepo.RemoveFilesFromIndex(oldWikiPath)
if err != nil {
log.Error("%v", err)
return err
}
}

// check file already exists - directory structure
oldWikiDirPath := WikiNameToPathFilename(oldWikiName)
if found, err = checkNewWikiFilename(gitRepo, oldWikiDirPath); err != nil && !found {
return err
}
if found {
err := gitRepo.RemoveFilesFromIndex(oldWikiPath)
err := gitRepo.RemoveFilesFromIndex(oldWikiDirPath)
if err != nil {
log.Error("%v", err)
return err
}
}
}

newWikiDirPath = FilenameToPathFilename(newWikiDirPath)

// FIXME: The wiki doesn't have lfs support at present - if this changes need to check attributes here

objectHash, err := gitRepo.HashObject(strings.NewReader(content))
Expand All @@ -191,7 +266,7 @@ func (repo *Repository) updateWikiPage(doer *User, oldWikiName, newWikiName, con
return err
}

if err := gitRepo.AddObjectToIndex("100644", objectHash, newWikiPath); err != nil {
if err := gitRepo.AddObjectToIndex("100644", objectHash, newWikiDirPath); err != nil {
log.Error("%v", err)
return err
}
Expand Down Expand Up @@ -289,22 +364,32 @@ func (repo *Repository) DeleteWikiPage(doer *User, wikiName string) (err error)
return fmt.Errorf("Unable to read HEAD tree to index in: %s %v", basePath, err)
}

var found bool

// check file exists - plain structure
wikiPath := WikiNameToFilename(wikiName)
filesInIndex, err := gitRepo.LsFiles(wikiPath)
found := false
for _, file := range filesInIndex {
if file == wikiPath {
found = true
break
}
if found, err = checkNewWikiFilename(gitRepo, wikiName); err != nil && !found {
return err
}
if found {
err := gitRepo.RemoveFilesFromIndex(wikiPath)
if err != nil {
return err
}
} else {
return os.ErrNotExist
// check file exists - plain structure
wikiDirPath := WikiNameToPathFilename(wikiName)
if found, err = checkNewWikiFilename(gitRepo, wikiDirPath); err != nil && !found {
return err
}
if found {
err := gitRepo.RemoveFilesFromIndex(wikiDirPath)
if err != nil {
return err
}
} else {
return os.ErrNotExist
}
}

// FIXME: The wiki doesn't have lfs support at present - if this changes need to check attributes here
Expand Down
Loading