From 6b2f67ea014f80d9ec0168223c7a9563599debde Mon Sep 17 00:00:00 2001 From: Snoweuph Date: Sun, 19 Jan 2025 13:51:11 +0100 Subject: [PATCH] refactor: centralize wiki path --- routers/api/v1/repo/wiki.go | 42 +++---- routers/web/repo/wiki.go | 92 +++++++-------- routers/web/repo/wiki_test.go | 26 ++--- services/wiki/wiki.go | 34 +++--- services/wiki/wiki_path.go | 205 +++++++++++++++++++--------------- services/wiki/wiki_test.go | 83 +++++++------- tests/test_utils.go | 2 +- 7 files changed, 257 insertions(+), 227 deletions(-) diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go index 12aaa8edf8..99b865be02 100644 --- a/routers/api/v1/repo/wiki.go +++ b/routers/api/v1/repo/wiki.go @@ -65,7 +65,7 @@ func NewWikiPage(ctx *context.APIContext) { return } - wikiName := wiki_service.UserTitleToWebPath("", form.Title) + wikiName := wiki_service.TitleToPath(form.Title) if len(form.Message) == 0 { form.Message = fmt.Sprintf("Add %q", form.Title) @@ -92,7 +92,7 @@ func NewWikiPage(ctx *context.APIContext) { wikiPage := getWikiPage(ctx, wikiName) if !ctx.Written() { - notify_service.NewWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(wikiName), form.Message) + notify_service.NewWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(wikiName.WebPath()), form.Message) ctx.JSON(http.StatusCreated, wikiPage) } } @@ -140,10 +140,10 @@ func EditWikiPage(ctx *context.APIContext) { form := web.GetForm(ctx).(*api.CreateWikiPageOptions) - oldWikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw(":pageName")) - newWikiName := wiki_service.UserTitleToWebPath("", form.Title) + oldWikiName := wiki_service.RequestToPath(ctx.PathParamRaw(":pageName")) + newWikiName := wiki_service.TitleToPath(form.Title) - if len(newWikiName) == 0 { + if len(newWikiName.WebPath()) == 0 { newWikiName = oldWikiName } @@ -166,12 +166,12 @@ func EditWikiPage(ctx *context.APIContext) { wikiPage := getWikiPage(ctx, newWikiName) if !ctx.Written() { - notify_service.EditWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(newWikiName), form.Message) + notify_service.EditWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(newWikiName.WebPath()), form.Message) ctx.JSON(http.StatusOK, wikiPage) } } -func getWikiPage(ctx *context.APIContext, wikiName wiki_service.WebPath) *api.WikiPage { +func getWikiPage(ctx *context.APIContext, wikiName wiki_service.Path) *api.WikiPage { wikiRepo, commit := findWikiRepoCommit(ctx) if wikiRepo != nil { defer wikiRepo.Close() @@ -186,12 +186,12 @@ func getWikiPage(ctx *context.APIContext, wikiName wiki_service.WebPath) *api.Wi return nil } - sidebarContent, _ := wikiContentsByName(ctx, commit, "_Sidebar", true) + sidebarContent, _ := wikiContentsByName(ctx, commit, wiki_service.TitleToPath("_Sidebar"), true) if ctx.Written() { return nil } - footerContent, _ := wikiContentsByName(ctx, commit, "_Footer", true) + footerContent, _ := wikiContentsByName(ctx, commit, wiki_service.TitleToPath("_Footer"), true) if ctx.Written() { return nil } @@ -246,7 +246,7 @@ func DeleteWikiPage(ctx *context.APIContext) { // "423": // "$ref": "#/responses/repoArchivedError" - wikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw(":pageName")) + wikiName := wiki_service.RequestToPath(ctx.PathParamRaw(":pageName")) if err := wiki_service.DeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName); err != nil { if err.Error() == "file does not exist" { @@ -257,7 +257,7 @@ func DeleteWikiPage(ctx *context.APIContext) { return } - notify_service.DeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(wikiName)) + notify_service.DeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(wikiName.WebPath())) ctx.Status(http.StatusNoContent) } @@ -329,7 +329,7 @@ func ListWikiPages(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "GetCommit", err) return } - wikiName, err := wiki_service.GitPathToWebPath(entry.Name()) + wikiName, err := wiki_service.GitPathToPath(wiki_service.GitPath(entry.Name())) if err != nil { if repo_model.IsErrWikiInvalidFileName(err) { continue @@ -337,7 +337,7 @@ func ListWikiPages(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "WikiFilenameToName", err) return } - pages = append(pages, wiki_service.ToWikiPageMetaData(wikiName, c, ctx.Repo.Repository)) + pages = append(pages, wiki_service.ToWikiPageMetaData(*wikiName, c, ctx.Repo.Repository)) } ctx.SetTotalCountHeader(int64(len(entries))) @@ -374,7 +374,7 @@ func GetWikiPage(ctx *context.APIContext) { // "$ref": "#/responses/notFound" // get requested pagename - pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw(":pageName")) + pageName := wiki_service.RequestToPath(ctx.PathParamRaw(":pageName")) wikiPage := getWikiPage(ctx, pageName) if !ctx.Written() { @@ -424,9 +424,9 @@ func ListPageRevisions(ctx *context.APIContext) { } // get requested pagename - pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw(":pageName")) - if len(pageName) == 0 { - pageName = "Home" + pageName := wiki_service.RequestToPath(ctx.PathParamRaw(":pageName")) + if len(pageName.WebPath()) == 0 { + pageName = wiki_service.WebPathToPath("Home") } // lookup filename in wiki - get filecontent, gitTree entry , real filename @@ -519,9 +519,9 @@ func wikiContentsByEntry(ctx *context.APIContext, entry *git.TreeEntry) string { // wikiContentsByName returns the contents of a wiki page, along with a boolean // indicating whether the page exists. Writes to ctx if an error occurs. -func wikiContentsByName(ctx *context.APIContext, commit *git.Commit, wikiName wiki_service.WebPath, isSidebarOrFooter bool) (string, string) { - gitFilename := wiki_service.WebPathToGitPath(wikiName) - entry, err := findEntryForFile(commit, gitFilename) +func wikiContentsByName(ctx *context.APIContext, commit *git.Commit, wikiName wiki_service.Path, isSidebarOrFooter bool) (string, string) { + gitFilename := wikiName.GitPath() + entry, err := findEntryForFile(commit, string(gitFilename)) if err != nil { if git.IsErrNotExist(err) { if !isSidebarOrFooter { @@ -532,5 +532,5 @@ func wikiContentsByName(ctx *context.APIContext, commit *git.Commit, wikiName wi } return "", "" } - return wikiContentsByEntry(ctx, entry), gitFilename + return wikiContentsByEntry(ctx, entry), string(gitFilename) } diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index 070d07cdf3..b6f2cf608d 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -127,16 +127,16 @@ func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte { // wikiContentsByName returns the contents of a wiki page, along with a boolean // indicating whether the page exists. Writes to ctx if an error occurs. -func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName wiki_service.WebPath) ([]byte, *git.TreeEntry, string, bool) { - gitFilename := wiki_service.WebPathToGitPath(wikiName) - entry, err := findEntryForFile(commit, gitFilename) +func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName wiki_service.Path) ([]byte, *git.TreeEntry, string, bool) { + gitFilename := wikiName.GitPath() + entry, err := findEntryForFile(commit, string(gitFilename)) if err != nil && !git.IsErrNotExist(err) { ctx.ServerError("findEntryForFile", err) return nil, nil, "", false } else if entry == nil { return nil, nil, "", true } - return wikiContentsByEntry(ctx, entry), entry, gitFilename, false + return wikiContentsByEntry(ctx, entry), entry, string(gitFilename), false } func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { @@ -165,7 +165,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { if !entry.IsRegular() { continue } - wikiName, err := wiki_service.GitPathToWebPath(entry.Name()) + wikiName, err := wiki_service.GitPathToPath(wiki_service.GitPath(entry.Name())) if err != nil { if repo_model.IsErrWikiInvalidFileName(err) { continue @@ -175,32 +175,32 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { } ctx.ServerError("WikiFilenameToName", err) return nil, nil - } else if wikiName == "_Sidebar" || wikiName == "_Footer" { + } else if wikiName.WebPath() == "_Sidebar" || wikiName.WebPath() == "_Footer" { continue } - _, displayName := wiki_service.WebPathToUserTitle(wikiName) + _, displayName := wikiName.DisplayName() pages = append(pages, PageMeta{ Name: displayName, - SubURL: wiki_service.WebPathToURLPath(wikiName), + SubURL: string(wikiName.URLPath()), GitEntryName: entry.Name(), }) } ctx.Data["Pages"] = pages // get requested page name - pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*")) - if len(pageName) == 0 { - pageName = "Home" + pageName := wiki_service.RequestToPath(ctx.PathParamRaw("*")) + if len(pageName.WebPath()) == 0 { + pageName = wiki_service.WebPathToPath("Home") } - _, displayName := wiki_service.WebPathToUserTitle(pageName) - ctx.Data["PageURL"] = wiki_service.WebPathToURLPath(pageName) + _, displayName := pageName.DisplayName() + ctx.Data["PageURL"] = pageName.URLPath() ctx.Data["old_title"] = displayName ctx.Data["Title"] = displayName ctx.Data["title"] = displayName - isSideBar := pageName == "_Sidebar" - isFooter := pageName == "_Footer" + isSideBar := pageName.WebPath() == "_Sidebar" + isFooter := pageName.WebPath() == "_Footer" // lookup filename in wiki - get filecontent, gitTree entry , real filename data, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName) @@ -216,7 +216,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { var sidebarContent []byte if !isSideBar { - sidebarContent, _, _, _ = wikiContentsByName(ctx, commit, "_Sidebar") + sidebarContent, _, _, _ = wikiContentsByName(ctx, commit, wiki_service.WebPathToPath("_Sidebar")) if ctx.Written() { if wikiRepo != nil { wikiRepo.Close() @@ -229,7 +229,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { var footerContent []byte if !isFooter { - footerContent, _, _, _ = wikiContentsByName(ctx, commit, "_Footer") + footerContent, _, _, _ = wikiContentsByName(ctx, commit, wiki_service.WebPathToPath("_Footer")) if ctx.Written() { if wikiRepo != nil { wikiRepo.Close() @@ -337,13 +337,13 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) } // get requested pagename - pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*")) - if len(pageName) == 0 { - pageName = "Home" + pageName := wiki_service.RequestToPath(ctx.PathParamRaw("*")) + if len(pageName.WebPath()) == 0 { + pageName = wiki_service.WebPathToPath("Home") } - _, displayName := wiki_service.WebPathToUserTitle(pageName) - ctx.Data["PageURL"] = wiki_service.WebPathToURLPath(pageName) + _, displayName := pageName.DisplayName() + ctx.Data["PageURL"] = pageName.URLPath() ctx.Data["old_title"] = displayName ctx.Data["Title"] = displayName ctx.Data["title"] = displayName @@ -421,13 +421,13 @@ func renderEditPage(ctx *context.Context) { }() // get requested pagename - pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*")) - if len(pageName) == 0 { - pageName = "Home" + pageName := wiki_service.RequestToPath(ctx.PathParamRaw("*")) + if len(pageName.WebPath()) == 0 { + pageName = wiki_service.WebPathToPath("Home") } - _, displayName := wiki_service.WebPathToUserTitle(pageName) - ctx.Data["PageURL"] = wiki_service.WebPathToURLPath(pageName) + _, displayName := pageName.DisplayName() + ctx.Data["PageURL"] = pageName.URLPath() ctx.Data["old_title"] = displayName ctx.Data["Title"] = displayName ctx.Data["title"] = displayName @@ -627,7 +627,7 @@ func WikiPages(ctx *context.Context) { if !entry.Entry.IsRegular() { continue } - wikiName, err := wiki_service.GitPathToWebPath(entry.Entry.Name()) + wikiName, err := wiki_service.GitPathToPath(wiki_service.GitPath(entry.Entry.Name())) if err != nil { if repo_model.IsErrWikiInvalidFileName(err) { continue @@ -635,10 +635,10 @@ func WikiPages(ctx *context.Context) { ctx.ServerError("WikiFilenameToName", err) return } - _, displayName := wiki_service.WebPathToUserTitle(wikiName) + _, displayName := wikiName.DisplayName() pages = append(pages, PageMeta{ Name: displayName, - SubURL: wiki_service.WebPathToURLPath(wikiName), + SubURL: string(wikiName.URLPath()), GitEntryName: entry.Entry.Name(), UpdatedUnix: timeutil.TimeStamp(entry.Commit.Author.When.Unix()), }) @@ -666,12 +666,12 @@ func WikiRaw(ctx *context.Context) { return } - providedWebPath := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*")) - providedGitPath := wiki_service.WebPathToGitPath(providedWebPath) + providedWebPath := wiki_service.RequestToPath(ctx.PathParamRaw("*")) + providedGitPath := providedWebPath.GitPath() var entry *git.TreeEntry if commit != nil { // Try to find a file with that name - entry, err = findEntryForFile(commit, providedGitPath) + entry, err = findEntryForFile(commit, string(providedGitPath)) if err != nil && !git.IsErrNotExist(err) { ctx.ServerError("findFile", err) return @@ -679,8 +679,8 @@ func WikiRaw(ctx *context.Context) { if entry == nil { // Try to find a wiki page with that name - providedGitPath = strings.TrimSuffix(providedGitPath, ".md") - entry, err = findEntryForFile(commit, providedGitPath) + providedGitPath = wiki_service.GitPath(strings.TrimSuffix(string(providedGitPath), ".md")) + entry, err = findEntryForFile(commit, string(providedGitPath)) if err != nil && !git.IsErrNotExist(err) { ctx.ServerError("findFile", err) return @@ -727,7 +727,7 @@ func NewWikiPost(ctx *context.Context) { return } - wikiName := wiki_service.UserTitleToWebPath("", form.Title) + wikiName := wiki_service.TitleToPath(form.Title) if len(form.Message) == 0 { form.Message = ctx.Locale.TrString("repo.editor.add", form.Title) @@ -746,9 +746,9 @@ func NewWikiPost(ctx *context.Context) { return } - notify_service.NewWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(wikiName), form.Message) + notify_service.NewWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(wikiName.WebPath()), form.Message) - ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + wiki_service.WebPathToURLPath(wikiName)) + ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + string(wikiName.URLPath())) } // EditWiki render wiki modify page @@ -778,8 +778,8 @@ func EditWikiPost(ctx *context.Context) { return } - oldWikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*")) - newWikiName := wiki_service.UserTitleToWebPath("", form.Title) + oldWikiName := wiki_service.RequestToPath(ctx.PathParamRaw("*")) + newWikiName := wiki_service.TitleToPath(form.Title) if len(form.Message) == 0 { form.Message = ctx.Locale.TrString("repo.editor.update", form.Title) @@ -790,16 +790,16 @@ func EditWikiPost(ctx *context.Context) { return } - notify_service.EditWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(newWikiName), form.Message) + notify_service.EditWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(newWikiName.WebPath()), form.Message) - ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + wiki_service.WebPathToURLPath(newWikiName)) + ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + string(newWikiName.URLPath())) } // DeleteWikiPagePost delete wiki page func DeleteWikiPagePost(ctx *context.Context) { - wikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*")) - if len(wikiName) == 0 { - wikiName = "Home" + wikiName := wiki_service.RequestToPath(ctx.PathParamRaw("*")) + if len(wikiName.WebPath()) == 0 { + wikiName = wiki_service.WebPathToPath("Home") } if err := wiki_service.DeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName); err != nil { @@ -807,7 +807,7 @@ func DeleteWikiPagePost(ctx *context.Context) { return } - notify_service.DeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(wikiName)) + notify_service.DeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(wikiName.WebPath())) ctx.JSONRedirect(ctx.Repo.RepoLink + "/wiki/") } diff --git a/routers/web/repo/wiki_test.go b/routers/web/repo/wiki_test.go index 0c49e7d902..2d44d46d5a 100644 --- a/routers/web/repo/wiki_test.go +++ b/routers/web/repo/wiki_test.go @@ -27,7 +27,7 @@ const ( message = "Wiki commit message for unit tests" ) -func wikiEntry(t *testing.T, repo *repo_model.Repository, wikiName wiki_service.WebPath) *git.TreeEntry { +func wikiEntry(t *testing.T, repo *repo_model.Repository, wikiName wiki_service.Path) *git.TreeEntry { wikiRepo, err := gitrepo.OpenWikiRepository(git.DefaultContext, repo) require.NoError(t, err) defer wikiRepo.Close() @@ -36,14 +36,14 @@ func wikiEntry(t *testing.T, repo *repo_model.Repository, wikiName wiki_service. entries, err := commit.ListEntries() require.NoError(t, err) for _, entry := range entries { - if entry.Name() == wiki_service.WebPathToGitPath(wikiName) { + if entry.Name() == string(wikiName.GitPath()) { return entry } } return nil } -func wikiContent(t *testing.T, repo *repo_model.Repository, wikiName wiki_service.WebPath) string { +func wikiContent(t *testing.T, repo *repo_model.Repository, wikiName wiki_service.Path) string { entry := wikiEntry(t, repo, wikiName) if !assert.NotNil(t, entry) { return "" @@ -56,11 +56,11 @@ func wikiContent(t *testing.T, repo *repo_model.Repository, wikiName wiki_servic return string(bytes) } -func assertWikiExists(t *testing.T, repo *repo_model.Repository, wikiName wiki_service.WebPath) { +func assertWikiExists(t *testing.T, repo *repo_model.Repository, wikiName wiki_service.Path) { assert.NotNil(t, wikiEntry(t, repo, wikiName)) } -func assertWikiNotExists(t *testing.T, repo *repo_model.Repository, wikiName wiki_service.WebPath) { +func assertWikiNotExists(t *testing.T, repo *repo_model.Repository, wikiName wiki_service.Path) { assert.Nil(t, wikiEntry(t, repo, wikiName)) } @@ -127,8 +127,8 @@ func TestNewWikiPost(t *testing.T) { }) NewWikiPost(ctx) assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status()) - assertWikiExists(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title)) - assert.Equal(t, content, wikiContent(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title))) + assertWikiExists(t, ctx.Repo.Repository, wiki_service.TitleToPath(title)) + assert.Equal(t, content, wikiContent(t, ctx.Repo.Repository, wiki_service.TitleToPath(title))) } } @@ -146,7 +146,7 @@ func TestNewWikiPost_ReservedName(t *testing.T) { NewWikiPost(ctx) assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) assert.EqualValues(t, ctx.Tr("repo.wiki.reserved_page"), ctx.Flash.ErrorMsg) - assertWikiNotExists(t, ctx.Repo.Repository, "_edit") + assertWikiNotExists(t, ctx.Repo.Repository, wiki_service.WebPathToPath("_edit")) } func TestEditWiki(t *testing.T) { @@ -159,7 +159,7 @@ func TestEditWiki(t *testing.T) { EditWiki(ctx) assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) assert.EqualValues(t, "Home", ctx.Data["Title"]) - assert.Equal(t, wikiContent(t, ctx.Repo.Repository, "Home"), ctx.Data["content"]) + assert.Equal(t, wikiContent(t, ctx.Repo.Repository, wiki_service.WebPathToPath("Home")), ctx.Data["content"]) } func TestEditWikiPost(t *testing.T) { @@ -179,10 +179,10 @@ func TestEditWikiPost(t *testing.T) { }) EditWikiPost(ctx) assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status()) - assertWikiExists(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title)) - assert.Equal(t, content, wikiContent(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title))) + assertWikiExists(t, ctx.Repo.Repository, wiki_service.TitleToPath(title)) + assert.Equal(t, content, wikiContent(t, ctx.Repo.Repository, wiki_service.TitleToPath(title))) if title != "Home" { - assertWikiNotExists(t, ctx.Repo.Repository, "Home") + assertWikiNotExists(t, ctx.Repo.Repository, wiki_service.WebPathToPath("Home")) } } } @@ -195,7 +195,7 @@ func TestDeleteWikiPagePost(t *testing.T) { contexttest.LoadRepo(t, ctx, 1) DeleteWikiPagePost(ctx) assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) - assertWikiNotExists(t, ctx.Repo.Repository, "Home") + assertWikiNotExists(t, ctx.Repo.Repository, wiki_service.WebPathToPath("Home")) } func TestWikiRaw(t *testing.T) { diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index 63196aa862..3893e5757e 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -98,12 +98,12 @@ func NormalizeWikiBranch(ctx context.Context, repo *repo_model.Repository, to st // prepareGitPath try to find a suitable file path with file name by the given raw wiki name. // return: existence, prepared file path with name, error -func prepareGitPath(gitRepo *git.Repository, branch string, wikiPath WebPath) (bool, string, error) { - unescaped := string(wikiPath) + ".md" - gitPath := WebPathToGitPath(wikiPath) +func prepareGitPath(gitRepo *git.Repository, branch string, wikiPath Path) (bool, GitPath, error) { + unescaped := string(wikiPath.WebPath()) + ".md" + gitPath := wikiPath.GitPath() // Look for both files - filesInIndex, err := gitRepo.LsTree(branch, unescaped, gitPath) + filesInIndex, err := gitRepo.LsTree(branch, unescaped, string(gitPath)) if err != nil { if strings.Contains(err.Error(), "Not a valid object name "+branch) { return false, gitPath, nil @@ -117,8 +117,8 @@ func prepareGitPath(gitRepo *git.Repository, branch string, wikiPath WebPath) (b switch filename { case unescaped: // if we find the unescaped file return it - return true, unescaped, nil - case gitPath: + return true, GitPath(unescaped), nil + case string(gitPath): foundEscaped = true } } @@ -128,7 +128,7 @@ func prepareGitPath(gitRepo *git.Repository, branch string, wikiPath WebPath) (b } // updateWikiPage adds a new page or edits an existing page in repository wiki. -func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldWikiName, newWikiName WebPath, content, message string, isNew bool) (err error) { +func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldWikiName, newWikiName Path, content, message string, isNew bool) (err error) { err = repo.MustNotBeArchived() if err != nil { return err @@ -192,7 +192,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model if isNew { if isWikiExist { return repo_model.ErrWikiAlreadyExist{ - Title: newWikiPath, + Title: string(newWikiPath), } } } else { @@ -207,7 +207,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model } if isOldWikiExist { - err := gitRepo.RemoveFilesFromIndex(oldWikiPath) + err := gitRepo.RemoveFilesFromIndex(string(oldWikiPath)) if err != nil { log.Error("RemoveFilesFromIndex failed: %v", err) return err @@ -223,7 +223,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model return err } - if err := gitRepo.AddObjectToIndex("100644", objectHash, newWikiPath); err != nil { + if err := gitRepo.AddObjectToIndex("100644", objectHash, string(newWikiPath)); err != nil { log.Error("AddObjectToIndex failed: %v", err) return err } @@ -281,18 +281,18 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model } // AddWikiPage adds a new wiki page with a given wikiPath. -func AddWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, wikiName WebPath, content, message string) error { - return updateWikiPage(ctx, doer, repo, "", wikiName, content, message, true) +func AddWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, wikiName Path, content, message string) error { + return updateWikiPage(ctx, doer, repo, WebPathToPath(""), wikiName, content, message, true) } // EditWikiPage updates a wiki page identified by its wikiPath, // optionally also changing wikiPath. -func EditWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldWikiName, newWikiName WebPath, content, message string) error { +func EditWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldWikiName, newWikiName Path, content, message string) error { return updateWikiPage(ctx, doer, repo, oldWikiName, newWikiName, content, message, false) } // DeleteWikiPage deletes a wiki page identified by its path. -func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, wikiName WebPath) (err error) { +func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, wikiName Path) (err error) { err = repo.MustNotBeArchived() if err != nil { return err @@ -341,7 +341,7 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model return err } if found { - err := gitRepo.RemoveFilesFromIndex(wikiPath) + err := gitRepo.RemoveFilesFromIndex(string(wikiPath)) if err != nil { return err } @@ -433,11 +433,11 @@ func SearchWikiContents(ctx context.Context, repo *repo_model.Repository, keywor res := make([]SearchContentsResult, 0, len(grepRes)) for _, entry := range grepRes { - wp, err := GitPathToWebPath(entry.Filename) + wp, err := GitPathToPath(GitPath(entry.Filename)) if err != nil { return nil, err } - _, title := WebPathToUserTitle(wp) + _, title := wp.DisplayName() res = append(res, SearchContentsResult{ GrepResult: entry, diff --git a/services/wiki/wiki_path.go b/services/wiki/wiki_path.go index 74c7064043..8da7b8ebcd 100644 --- a/services/wiki/wiki_path.go +++ b/services/wiki/wiki_path.go @@ -37,12 +37,125 @@ import ( // * This problem should have been 99% fixed, but it needs more tests. // * The old wiki code's behavior is always using %2F, instead of subdirectory, so there are a lot of legacy "%2F" files in user wikis. -type WebPath string +type ( + WebPath string + GitPath string + URLPath string +) + +type Path struct { + file WebPath +} + +func WebPathToPath(web WebPath) Path { + return Path{ + file: web, + } +} + +func RequestToPath(req string) Path { + s := util.PathJoinRelX(req) + // The old wiki code's behavior is always using %2F, instead of subdirectory. + s = strings.ReplaceAll(s, "/", "%2F") + return Path{ + file: WebPath(s), + } +} + +func TitleToPath(title string) Path { + // TODO: no support for subdirectory, because the old wiki code's behavior is always using %2F, instead of subdirectory. + // So we do not add the support for writing slashes in title at the moment. + title = strings.TrimSpace(title) + title = util.PathJoinRelX(escapeSegToWeb(title, false)) + if title == "" || title == "." { + title = "unnamed" + } + return Path{ + WebPath(title), + } +} + +func GitPathToPath(g GitPath) (*Path, error) { + gitPath := string(g) + if !strings.HasSuffix(gitPath, ".md") { + return nil, repo_model.ErrWikiInvalidFileName{FileName: gitPath} + } + gitPath = strings.TrimSuffix(gitPath, ".md") + a := strings.Split(gitPath, "/") + for i := range a { + shouldAddDashMarker := hasDashMarker(a[i]) + s, err := unescapeSegment(a[i]) + if err != nil { + return nil, err + } + a[i] = s + a[i] = escapeSegToWeb(a[i], shouldAddDashMarker) + } + return &Path{ + file: WebPath(strings.Join(a, "/")), + }, nil +} + +func (w Path) DisplayName() (dir, display string) { + dir = path.Dir(string(w.file)) + display = path.Base(string(w.file)) + if strings.HasSuffix(display, ".md") { + display = strings.TrimSuffix(display, ".md") + display, _ = url.PathUnescape(display) + } + display, _ = unescapeSegment(display) + return dir, display +} + +func (w Path) WebPath() WebPath { + return w.file +} + +func (w Path) GitPath() GitPath { + if strings.HasSuffix(string(w.file), ".md") { + ret, _ := url.PathUnescape(string(w.file)) + return GitPath(util.PathJoinRelX(ret)) + } + + a := strings.Split(string(w.file), "/") + for i := range a { + shouldAddDashMarker := hasDashMarker(a[i]) + a[i], _ = unescapeSegment(a[i]) + a[i] = escapeSegToWeb(a[i], shouldAddDashMarker) + a[i] = strings.ReplaceAll(a[i], "%20", " ") // space is safe to be kept in git path + a[i] = strings.ReplaceAll(a[i], "+", " ") + } + return GitPath(strings.Join(a, "/") + ".md") +} + +func (w Path) URLPath() URLPath { + return URLPath(w.file) +} + +func WebPathSegments(s WebPath) []string { + a := strings.Split(string(s), "/") + for i := range a { + a[i], _ = unescapeSegment(a[i]) + } + return a +} + +// ToWikiPageMetaData converts meta information to a WikiPageMetaData +func ToWikiPageMetaData(wikiPath Path, lastCommit *git.Commit, repo *repo_model.Repository) *api.WikiPageMetaData { + subURL := string(wikiPath.WebPath()) + _, title := wikiPath.DisplayName() + return &api.WikiPageMetaData{ + Title: title, + HTMLURL: util.URLJoin(repo.HTMLURL(), "wiki", subURL), + SubURL: subURL, + LastCommit: convert.ToWikiCommit(lastCommit), + } +} var reservedWikiNames = []string{"_pages", "_new", "_edit", "raw"} -func validateWebPath(name WebPath) error { - for _, s := range WebPathSegments(name) { +func validateWebPath(name Path) error { + for _, s := range WebPathSegments(name.WebPath()) { if util.SliceContainsString(reservedWikiNames, s) { return repo_model.ErrWikiReservedName{Title: s} } @@ -84,89 +197,3 @@ func escapeSegToWeb(s string, hadDashMarker bool) string { s = url.QueryEscape(s) return s } - -func WebPathSegments(s WebPath) []string { - a := strings.Split(string(s), "/") - for i := range a { - a[i], _ = unescapeSegment(a[i]) - } - return a -} - -func WebPathToGitPath(s WebPath) string { - if strings.HasSuffix(string(s), ".md") { - ret, _ := url.PathUnescape(string(s)) - return util.PathJoinRelX(ret) - } - - a := strings.Split(string(s), "/") - for i := range a { - shouldAddDashMarker := hasDashMarker(a[i]) - a[i], _ = unescapeSegment(a[i]) - a[i] = escapeSegToWeb(a[i], shouldAddDashMarker) - a[i] = strings.ReplaceAll(a[i], "%20", " ") // space is safe to be kept in git path - a[i] = strings.ReplaceAll(a[i], "+", " ") - } - return strings.Join(a, "/") + ".md" -} - -func GitPathToWebPath(s string) (wp WebPath, err error) { - if !strings.HasSuffix(s, ".md") { - return "", repo_model.ErrWikiInvalidFileName{FileName: s} - } - s = strings.TrimSuffix(s, ".md") - a := strings.Split(s, "/") - for i := range a { - shouldAddDashMarker := hasDashMarker(a[i]) - if a[i], err = unescapeSegment(a[i]); err != nil { - return "", err - } - a[i] = escapeSegToWeb(a[i], shouldAddDashMarker) - } - return WebPath(strings.Join(a, "/")), nil -} - -func WebPathToUserTitle(s WebPath) (dir, display string) { - dir = path.Dir(string(s)) - display = path.Base(string(s)) - if strings.HasSuffix(display, ".md") { - display = strings.TrimSuffix(display, ".md") - display, _ = url.PathUnescape(display) - } - display, _ = unescapeSegment(display) - return dir, display -} - -func WebPathToURLPath(s WebPath) string { - return string(s) -} - -func WebPathFromRequest(s string) WebPath { - s = util.PathJoinRelX(s) - // The old wiki code's behavior is always using %2F, instead of subdirectory. - s = strings.ReplaceAll(s, "/", "%2F") - return WebPath(s) -} - -func UserTitleToWebPath(base, title string) WebPath { - // TODO: no support for subdirectory, because the old wiki code's behavior is always using %2F, instead of subdirectory. - // So we do not add the support for writing slashes in title at the moment. - title = strings.TrimSpace(title) - title = util.PathJoinRelX(base, escapeSegToWeb(title, false)) - if title == "" || title == "." { - title = "unnamed" - } - return WebPath(title) -} - -// ToWikiPageMetaData converts meta information to a WikiPageMetaData -func ToWikiPageMetaData(wikiName WebPath, lastCommit *git.Commit, repo *repo_model.Repository) *api.WikiPageMetaData { - subURL := string(wikiName) - _, title := WebPathToUserTitle(wikiName) - return &api.WikiPageMetaData{ - Title: title, - HTMLURL: util.URLJoin(repo.HTMLURL(), "wiki", subURL), - SubURL: subURL, - LastCommit: convert.ToWikiCommit(lastCommit), - } -} diff --git a/services/wiki/wiki_test.go b/services/wiki/wiki_test.go index efcc13db99..aebb349244 100644 --- a/services/wiki/wiki_test.go +++ b/services/wiki/wiki_test.go @@ -31,7 +31,7 @@ func TestWebPathSegments(t *testing.T) { func TestUserTitleToWebPath(t *testing.T) { type test struct { - Expected string + Expected WebPath UserTitle string } for _, test := range []test{ @@ -45,7 +45,7 @@ func TestUserTitleToWebPath(t *testing.T) { {"a%2Fb", "a/b"}, {"a%25b", "a%b"}, } { - assert.EqualValues(t, test.Expected, UserTitleToWebPath("", test.UserTitle)) + assert.EqualValues(t, test.Expected, TitleToPath(test.UserTitle).WebPath()) } } @@ -62,15 +62,15 @@ func TestWebPathToDisplayName(t *testing.T) { {"2000-01-02 meeting", "2000-01-02+meeting.-.md"}, {"a b", "a%20b.md"}, } { - _, displayName := WebPathToUserTitle(test.WebPath) + _, displayName := WebPathToPath(test.WebPath).DisplayName() assert.EqualValues(t, test.Expected, displayName) } } func TestWebPathToGitPath(t *testing.T) { type test struct { - Expected string - WikiName WebPath + Expected GitPath + WebPath WebPath } for _, test := range []test{ {"wiki-name.md", "wiki%20name"}, @@ -80,14 +80,14 @@ func TestWebPathToGitPath(t *testing.T) { {"2000-01-02-meeting.md", "2000-01-02+meeting"}, {"2000-01-02 meeting.-.md", "2000-01-02%20meeting.-"}, } { - assert.EqualValues(t, test.Expected, WebPathToGitPath(test.WikiName)) + assert.EqualValues(t, test.Expected, WebPathToPath(test.WebPath).GitPath()) } } func TestGitPathToWebPath(t *testing.T) { type test struct { - Expected string - Filename string + Expected WebPath + Filename GitPath } for _, test := range []test{ {"hello-world", "hello-world.md"}, // this shouldn't happen, because it should always have a ".-" suffix @@ -96,19 +96,19 @@ func TestGitPathToWebPath(t *testing.T) { {"hello+world.-", "hello world.-.md"}, {"symbols-%2F", "symbols %2F.md"}, } { - name, err := GitPathToWebPath(test.Filename) + wikiPath, err := GitPathToPath(test.Filename) require.NoError(t, err) - assert.EqualValues(t, test.Expected, name) + assert.EqualValues(t, test.Expected, wikiPath.WebPath()) } - for _, badFilename := range []string{ + for _, badFilename := range []GitPath{ "nofileextension", "wrongfileextension.txt", } { - _, err := GitPathToWebPath(badFilename) + _, err := GitPathToPath(badFilename) require.Error(t, err) assert.True(t, repo_model.IsErrWikiInvalidFileName(err)) } - _, err := GitPathToWebPath("badescaping%%.md") + _, err := GitPathToPath("badescaping%%.md") require.Error(t, err) assert.False(t, repo_model.IsErrWikiInvalidFileName(err)) } @@ -127,12 +127,15 @@ func TestUserWebGitPathConsistency(t *testing.T) { if userTitle == "" || userTitle == "." || userTitle == ".." { continue } - webPath := UserTitleToWebPath("", userTitle) - gitPath := WebPathToGitPath(webPath) + wikiPath := TitleToPath(userTitle) + webPath := wikiPath.WebPath() + gitPath := wikiPath.GitPath() - webPath1, _ := GitPathToWebPath(gitPath) - _, userTitle1 := WebPathToUserTitle(webPath1) - gitPath1 := WebPathToGitPath(webPath1) + wikiPath1, err := GitPathToPath(gitPath) + require.NoError(t, err) + webPath1 := wikiPath1.WebPath() + _, userTitle1 := WebPathToPath(webPath1).DisplayName() + gitPath1 := WebPathToPath(webPath1).GitPath() assert.EqualValues(t, userTitle, userTitle1, "UserTitle for userTitle: %q", userTitle) assert.EqualValues(t, webPath, webPath1, "WebPath for userTitle: %q", userTitle) @@ -163,8 +166,8 @@ func TestRepository_AddWikiPage(t *testing.T) { "Here's a and a/slash", } { t.Run("test wiki exist: "+userTitle, func(t *testing.T) { - webPath := UserTitleToWebPath("", userTitle) - require.NoError(t, AddWikiPage(git.DefaultContext, doer, repo, webPath, wikiContent, commitMsg)) + wikiPath := TitleToPath(userTitle) + require.NoError(t, AddWikiPage(git.DefaultContext, doer, repo, wikiPath, wikiContent, commitMsg)) // Now need to show that the page has been added: gitRepo, err := gitrepo.OpenWikiRepository(git.DefaultContext, repo) require.NoError(t, err) @@ -172,8 +175,8 @@ func TestRepository_AddWikiPage(t *testing.T) { defer gitRepo.Close() masterTree, err := gitRepo.GetTree("master") require.NoError(t, err) - gitPath := WebPathToGitPath(webPath) - entry, err := masterTree.GetTreeEntryByPath(gitPath) + gitPath := wikiPath.GitPath() + entry, err := masterTree.GetTreeEntryByPath(string(gitPath)) require.NoError(t, err) assert.EqualValues(t, gitPath, entry.Name(), "%s not added correctly", userTitle) }) @@ -182,7 +185,7 @@ func TestRepository_AddWikiPage(t *testing.T) { t.Run("check wiki already exist", func(t *testing.T) { t.Parallel() // test for already-existing wiki name - err := AddWikiPage(git.DefaultContext, doer, repo, "Home", wikiContent, commitMsg) + err := AddWikiPage(git.DefaultContext, doer, repo, TitleToPath("Home"), wikiContent, commitMsg) require.Error(t, err) assert.True(t, repo_model.IsErrWikiAlreadyExist(err)) }) @@ -190,7 +193,7 @@ func TestRepository_AddWikiPage(t *testing.T) { t.Run("check wiki reserved name", func(t *testing.T) { t.Parallel() // test for reserved wiki name - err := AddWikiPage(git.DefaultContext, doer, repo, "_edit", wikiContent, commitMsg) + err := AddWikiPage(git.DefaultContext, doer, repo, TitleToPath("_edit"), wikiContent, commitMsg) require.Error(t, err) assert.True(t, repo_model.IsErrWikiReservedName(err)) }) @@ -208,17 +211,17 @@ func TestRepository_EditWikiPage(t *testing.T) { "New home", "New/name/with/slashes", } { - webPath := UserTitleToWebPath("", newWikiName) + wikiPath := TitleToPath(newWikiName) unittest.PrepareTestEnv(t) - require.NoError(t, EditWikiPage(git.DefaultContext, doer, repo, "Home", webPath, newWikiContent, commitMsg)) + require.NoError(t, EditWikiPage(git.DefaultContext, doer, repo, TitleToPath("Home"), wikiPath, newWikiContent, commitMsg)) // Now need to show that the page has been added: gitRepo, err := gitrepo.OpenWikiRepository(git.DefaultContext, repo) require.NoError(t, err) masterTree, err := gitRepo.GetTree("master") require.NoError(t, err) - gitPath := WebPathToGitPath(webPath) - entry, err := masterTree.GetTreeEntryByPath(gitPath) + gitPath := wikiPath.GitPath() + entry, err := masterTree.GetTreeEntryByPath(string(gitPath)) require.NoError(t, err) assert.EqualValues(t, gitPath, entry.Name(), "%s not edited correctly", newWikiName) @@ -234,7 +237,7 @@ func TestRepository_DeleteWikiPage(t *testing.T) { unittest.PrepareTestEnv(t) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - require.NoError(t, DeleteWikiPage(git.DefaultContext, doer, repo, "Home")) + require.NoError(t, DeleteWikiPage(git.DefaultContext, doer, repo, TitleToPath("Home"))) // Now need to show that the page has been added: gitRepo, err := gitrepo.OpenWikiRepository(git.DefaultContext, repo) @@ -242,8 +245,8 @@ func TestRepository_DeleteWikiPage(t *testing.T) { defer gitRepo.Close() masterTree, err := gitRepo.GetTree("master") require.NoError(t, err) - gitPath := WebPathToGitPath("Home") - _, err = masterTree.GetTreeEntryByPath(gitPath) + gitPath := WebPathToPath("Home").GitPath() + _, err = masterTree.GetTreeEntryByPath(string(gitPath)) require.Error(t, err) } @@ -276,8 +279,8 @@ func TestPrepareWikiFileName(t *testing.T) { }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - webPath := UserTitleToWebPath("", tt.arg) - existence, newWikiPath, err := prepareGitPath(gitRepo, "master", webPath) + wikiPath := TitleToPath(tt.arg) + existence, newWikiPath, err := prepareGitPath(gitRepo, "master", wikiPath) if (err != nil) != tt.wantErr { require.NoError(t, err) return @@ -308,20 +311,20 @@ func TestPrepareWikiFileName_FirstPage(t *testing.T) { defer gitRepo.Close() - existence, newWikiPath, err := prepareGitPath(gitRepo, "master", "Home") + existence, newWikiPath, err := prepareGitPath(gitRepo, "master", TitleToPath("Home")) assert.False(t, existence) require.NoError(t, err) assert.EqualValues(t, "Home.md", newWikiPath) } func TestWebPathConversion(t *testing.T) { - assert.Equal(t, "path/wiki", WebPathToURLPath(WebPath("path/wiki"))) - assert.Equal(t, "wiki", WebPathToURLPath(WebPath("wiki"))) - assert.Equal(t, "", WebPathToURLPath(WebPath(""))) + assert.Equal(t, "path/wiki", string(WebPathToPath("path/wiki").URLPath())) + assert.Equal(t, "wiki", string(WebPathToPath("wiki").URLPath())) + assert.Equal(t, "", string(WebPathToPath("").URLPath())) } func TestWebPathFromRequest(t *testing.T) { - assert.Equal(t, WebPath("a%2Fb"), WebPathFromRequest("a/b")) - assert.Equal(t, WebPath("a"), WebPathFromRequest("a")) - assert.Equal(t, WebPath("b"), WebPathFromRequest("a/../b")) + assert.Equal(t, WebPath("a%2Fb"), RequestToPath("a/b").WebPath()) + assert.Equal(t, WebPath("a"), RequestToPath("a").WebPath()) + assert.Equal(t, WebPath("b"), RequestToPath("a/../b").WebPath()) } diff --git a/tests/test_utils.go b/tests/test_utils.go index b3c03a30a1..8e08ea7304 100644 --- a/tests/test_utils.go +++ b/tests/test_utils.go @@ -457,7 +457,7 @@ func CreateDeclarativeRepoWithOptions(t *testing.T, owner *user_model.User, opts require.NoError(t, err) // Add a new wiki page - err = wiki_service.AddWikiPage(db.DefaultContext, owner, repo, "Home", "Welcome to the wiki!", "Add a Home page") + err = wiki_service.AddWikiPage(db.DefaultContext, owner, repo, wiki_service.WebPathToPath("Home"), "Welcome to the wiki!", "Add a Home page") require.NoError(t, err) }