1
0
Fork 0
forked from forgejo/forgejo

Introduce path Clean/Join helper functions (#23495)

Since #23493 has conflicts with latest commits, this PR is my proposal
for fixing #23371

Details are in the comments

And refactor the `modules/options` module, to make it always use
"filepath" to access local files.

Benefits:

* No need to do `util.CleanPath(strings.ReplaceAll(p, "\\", "/"))),
"/")` any more (not only one before)
* The function behaviors are clearly defined
This commit is contained in:
wxiaoguang 2023-03-22 04:02:49 +08:00 committed by GitHub
parent 253a00aaac
commit ce9dee5a1e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 261 additions and 152 deletions

View file

@ -5,6 +5,7 @@ package util
import (
"errors"
"fmt"
"net/url"
"os"
"path"
@ -14,21 +15,92 @@ import (
"strings"
)
// CleanPath ensure to clean the path
func CleanPath(p string) string {
if strings.HasPrefix(p, "/") {
return path.Clean(p)
// PathJoinRel joins the path elements into a single path, each element is cleaned by path.Clean separately.
// It only returns the following values (like path.Join), any redundant part (empty, relative dots, slashes) is removed.
// It's caller's duty to make every element not bypass its own directly level, to avoid security issues.
//
// empty => ``
// `` => ``
// `..` => `.`
// `dir` => `dir`
// `/dir/` => `dir`
// `foo\..\bar` => `foo\..\bar`
// {`foo`, ``, `bar`} => `foo/bar`
// {`foo`, `..`, `bar`} => `foo/bar`
func PathJoinRel(elem ...string) string {
elems := make([]string, len(elem))
for i, e := range elem {
if e == "" {
continue
}
elems[i] = path.Clean("/" + e)
}
p := path.Join(elems...)
if p == "" {
return ""
} else if p == "/" {
return "."
} else {
return p[1:]
}
return path.Clean("/" + p)[1:]
}
// EnsureAbsolutePath ensure that a path is absolute, making it
// relative to absoluteBase if necessary
func EnsureAbsolutePath(path, absoluteBase string) string {
if filepath.IsAbs(path) {
return path
// PathJoinRelX joins the path elements into a single path like PathJoinRel,
// and covert all backslashes to slashes. (X means "extended", also means the combination of `\` and `/`).
// It's caller's duty to make every element not bypass its own directly level, to avoid security issues.
// It returns similar results as PathJoinRel except:
//
// `foo\..\bar` => `bar` (because it's processed as `foo/../bar`)
//
// All backslashes are handled as slashes, the result only contains slashes.
func PathJoinRelX(elem ...string) string {
elems := make([]string, len(elem))
for i, e := range elem {
if e == "" {
continue
}
elems[i] = path.Clean("/" + strings.ReplaceAll(e, "\\", "/"))
}
return filepath.Join(absoluteBase, path)
return PathJoinRel(elems...)
}
const pathSeparator = string(os.PathSeparator)
// FilePathJoinAbs joins the path elements into a single file path, each element is cleaned by filepath.Clean separately.
// All slashes/backslashes are converted to path separators before cleaning, the result only contains path separators.
// The first element must be an absolute path, caller should prepare the base path.
// It's caller's duty to make every element not bypass its own directly level, to avoid security issues.
// Like PathJoinRel, any redundant part (empty, relative dots, slashes) is removed.
//
// {`/foo`, ``, `bar`} => `/foo/bar`
// {`/foo`, `..`, `bar`} => `/foo/bar`
func FilePathJoinAbs(elem ...string) string {
elems := make([]string, len(elem))
// POISX filesystem can have `\` in file names. Windows: `\` and `/` are both used for path separators
// to keep the behavior consistent, we do not allow `\` in file names, replace all `\` with `/`
if isOSWindows() {
elems[0] = filepath.Clean(elem[0])
} else {
elems[0] = filepath.Clean(strings.ReplaceAll(elem[0], "\\", pathSeparator))
}
if !filepath.IsAbs(elems[0]) {
// This shouldn't happen. If there is really necessary to pass in relative path, return the full path with filepath.Abs() instead
panic(fmt.Sprintf("FilePathJoinAbs: %q (for path %v) is not absolute, do not guess a relative path based on current working directory", elems[0], elems))
}
for i := 1; i < len(elem); i++ {
if elem[i] == "" {
continue
}
if isOSWindows() {
elems[i] = filepath.Clean(pathSeparator + elem[i])
} else {
elems[i] = filepath.Clean(pathSeparator + strings.ReplaceAll(elem[i], "\\", pathSeparator))
}
}
// the elems[0] must be an absolute path, just join them together
return filepath.Join(elems...)
}
// IsDir returns true if given path is a directory,