mirror of https://github.com/go-gitea/gitea.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
5.1 KiB
172 lines
5.1 KiB
// 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 before, ok := strings.CutSuffix(display, ".md"); ok { |
|
display = before |
|
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), |
|
} |
|
}
|
|
|