2023-04-19 20:50:10 +03:00
// 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"
2024-01-31 17:55:12 +03:00
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
2023-04-19 20:50:10 +03:00
"code.gitea.io/gitea/modules/util"
2024-01-31 17:55:12 +03:00
"code.gitea.io/gitea/services/convert"
2023-04-19 20:50:10 +03:00
)
// 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.
2023-08-09 09:57:45 +03:00
// - If a WebPath is a "*.md" pattern, then use the unescaped value directly as GitPath, to make users can access the raw file.
2023-04-19 20:50:10 +03:00
// * 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
//
2023-08-09 09:57:45 +03:00
// Although this package now has the ability to support subdirectory, but the route package doesn't:
2024-06-19 01:32:45 +03:00
// * Double-escaping problem: the URL "/wiki/abc%2Fdef" becomes "/wiki/abc/def" by ctx.PathParam, which is incorrect
2023-08-09 09:57:45 +03:00
// * This problem should have been 99% fixed, but it needs more tests.
2023-04-19 20:50:10 +03:00
// * 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 {
2023-04-23 14:16:30 +03:00
if hadDashMarker || strings . Contains ( s , "-" ) || strings . HasSuffix ( s , ".md" ) {
2023-04-19 20:50:10 +03:00
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" ) {
2023-08-09 09:57:45 +03:00
ret , _ := url . PathUnescape ( string ( s ) )
return util . PathJoinRelX ( ret )
2023-04-19 20:50:10 +03:00
}
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 ) )
2023-08-09 09:57:45 +03:00
if strings . HasSuffix ( display , ".md" ) {
display = strings . TrimSuffix ( display , ".md" )
display , _ = url . PathUnescape ( display )
}
2023-04-19 20:50:10 +03:00
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 {
2023-08-09 09:57:45 +03:00
// TODO: no support for subdirectory, because the old wiki code's behavior is always using %2F, instead of subdirectory.
2023-04-19 20:50:10 +03:00
// 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 )
}
2024-01-31 17:55:12 +03:00
// 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 ) ,
}
}