diff options
author | Runxi Yu <me@runxiyu.org> | 2025-04-05 19:45:17 +0800 |
---|---|---|
committer | Runxi Yu <me@runxiyu.org> | 2025-04-05 19:45:17 +0800 |
commit | 54a19febc0c7c49caa014254cabab571abad60ab (patch) | |
tree | a0b5ffe5aaaff956d5d41b09ca1d2127f771d17a /misc/url.go | |
parent | git2c: Fix regression in potential integer overflow (diff) | |
download | forge-54a19febc0c7c49caa014254cabab571abad60ab.tar.gz forge-54a19febc0c7c49caa014254cabab571abad60ab.tar.zst forge-54a19febc0c7c49caa014254cabab571abad60ab.zip |
misc: Move url.go into the misc package
Diffstat (limited to 'misc/url.go')
-rw-r--r-- | misc/url.go | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/misc/url.go b/misc/url.go new file mode 100644 index 0000000..b77d8ce --- /dev/null +++ b/misc/url.go @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> + +package misc + +import ( + "errors" + "net/http" + "net/url" + "strings" +) + +var ( + ErrDupRefSpec = errors.New("duplicate ref spec") + ErrNoRefSpec = errors.New("no ref spec") +) + +// getParamRefTypeName looks at the query parameters in an HTTP request and +// returns its ref name and type, if any. +func GetParamRefTypeName(request *http.Request) (retRefType, retRefName string, err error) { + rawQuery := request.URL.RawQuery + queryValues, err := url.ParseQuery(rawQuery) + if err != nil { + return + } + done := false + for _, refType := range []string{"commit", "branch", "tag"} { + refName, ok := queryValues[refType] + if ok { + if done { + err = ErrDupRefSpec + return + } + done = true + if len(refName) != 1 { + err = ErrDupRefSpec + return + } + retRefName = refName[0] + retRefType = refType + } + } + if !done { + err = ErrNoRefSpec + } + return +} + +// ParseReqURI parses an HTTP request URL, and returns a slice of path segments +// and the query parameters. It handles %2F correctly. +func ParseReqURI(requestURI string) (segments []string, params url.Values, err error) { + path, paramsStr, _ := strings.Cut(requestURI, "?") + + segments, err = PathToSegments(path) + if err != nil { + return + } + + params, err = url.ParseQuery(paramsStr) + return +} + +func PathToSegments(path string) (segments []string, err error) { + segments = strings.Split(strings.TrimPrefix(path, "/"), "/") + + for i, segment := range segments { + segments[i], err = url.PathUnescape(segment) + if err != nil { + return + } + } + + return +} + +// RedirectDir returns true and redirects the user to a version of the URL with +// a trailing slash, if and only if the request URL does not already have a +// trailing slash. +func RedirectDir(writer http.ResponseWriter, request *http.Request) bool { + requestURI := request.RequestURI + + pathEnd := strings.IndexAny(requestURI, "?#") + var path, rest string + if pathEnd == -1 { + path = requestURI + } else { + path = requestURI[:pathEnd] + rest = requestURI[pathEnd:] + } + + if !strings.HasSuffix(path, "/") { + http.Redirect(writer, request, path+"/"+rest, http.StatusSeeOther) + return true + } + return false +} + +// RedirectNoDir returns true and redirects the user to a version of the URL +// without a trailing slash, if and only if the request URL has a trailing +// slash. +func RedirectNoDir(writer http.ResponseWriter, request *http.Request) bool { + requestURI := request.RequestURI + + pathEnd := strings.IndexAny(requestURI, "?#") + var path, rest string + if pathEnd == -1 { + path = requestURI + } else { + path = requestURI[:pathEnd] + rest = requestURI[pathEnd:] + } + + if strings.HasSuffix(path, "/") { + http.Redirect(writer, request, strings.TrimSuffix(path, "/")+rest, http.StatusSeeOther) + return true + } + return false +} + +// RedirectUnconditionally unconditionally redirects the user back to the +// current page while preserving query parameters. +func RedirectUnconditionally(writer http.ResponseWriter, request *http.Request) { + requestURI := request.RequestURI + + pathEnd := strings.IndexAny(requestURI, "?#") + var path, rest string + if pathEnd == -1 { + path = requestURI + } else { + path = requestURI[:pathEnd] + rest = requestURI[pathEnd:] + } + + http.Redirect(writer, request, path+rest, http.StatusSeeOther) +} + +// SegmentsToURL joins URL segments to the path component of a URL. +// Each segment is escaped properly first. +func SegmentsToURL(segments []string) string { + for i, segment := range segments { + segments[i] = url.PathEscape(segment) + } + return strings.Join(segments, "/") +} + +// AnyContain returns true if and only if ss contains a string that contains c. +func AnyContain(ss []string, c string) bool { + for _, s := range ss { + if strings.Contains(s, c) { + return true + } + } + return false +} |