first-commit
This commit is contained in:
172
services/wiki/wiki_path.go
Normal file
172
services/wiki/wiki_path.go
Normal file
@@ -0,0 +1,172 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package wiki
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
)
|
||||
|
||||
// To define the wiki related concepts:
|
||||
// * Display Segment: the text what user see for a wiki page (aka, the title):
|
||||
// - "Home Page"
|
||||
// - "100% Free"
|
||||
// - "2000-01-02 meeting"
|
||||
// * Web Path:
|
||||
// - "/wiki/Home-Page"
|
||||
// - "/wiki/100%25+Free"
|
||||
// - "/wiki/2000-01-02+meeting.-"
|
||||
// - If a segment has a suffix "DashMarker(.-)", it means that there is no dash-space conversion for this segment.
|
||||
// - If a WebPath is a "*.md" pattern, then use the unescaped value directly as GitPath, to make users can access the raw file.
|
||||
// * Git Path (only space doesn't need to be escaped):
|
||||
// - "/.wiki.git/Home-Page.md"
|
||||
// - "/.wiki.git/100%25 Free.md"
|
||||
// - "/.wiki.git/2000-01-02 meeting.-.md"
|
||||
// TODO: support subdirectory in the future
|
||||
//
|
||||
// Although this package now has the ability to support subdirectory, but the route package doesn't:
|
||||
// * Double-escaping problem: the URL "/wiki/abc%2Fdef" becomes "/wiki/abc/def" by ctx.PathParam, which is incorrect
|
||||
// * 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
|
||||
|
||||
var reservedWikiNames = []string{"_pages", "_new", "_edit", "raw"}
|
||||
|
||||
func validateWebPath(name WebPath) error {
|
||||
for _, s := range WebPathSegments(name) {
|
||||
if util.SliceContainsString(reservedWikiNames, s) {
|
||||
return repo_model.ErrWikiReservedName{Title: s}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasDashMarker(s string) bool {
|
||||
return strings.HasSuffix(s, ".-")
|
||||
}
|
||||
|
||||
func removeDashMarker(s string) string {
|
||||
return strings.TrimSuffix(s, ".-")
|
||||
}
|
||||
|
||||
func addDashMarker(s string) string {
|
||||
return s + ".-"
|
||||
}
|
||||
|
||||
func unescapeSegment(s string) (string, error) {
|
||||
if hasDashMarker(s) {
|
||||
s = removeDashMarker(s)
|
||||
} else {
|
||||
s = strings.ReplaceAll(s, "-", " ")
|
||||
}
|
||||
unescaped, err := url.QueryUnescape(s)
|
||||
if err != nil {
|
||||
return s, err // un-escaping failed, but it's still safe to return the original string, because it is only a title for end users
|
||||
}
|
||||
return unescaped, nil
|
||||
}
|
||||
|
||||
func escapeSegToWeb(s string, hadDashMarker bool) string {
|
||||
if hadDashMarker || strings.Contains(s, "-") || strings.HasSuffix(s, ".md") {
|
||||
s = addDashMarker(s)
|
||||
} else {
|
||||
s = strings.ReplaceAll(s, " ", "-")
|
||||
}
|
||||
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),
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user