Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-pages.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'internal/redirects/validations.go')
-rw-r--r--internal/redirects/validations.go108
1 files changed, 92 insertions, 16 deletions
diff --git a/internal/redirects/validations.go b/internal/redirects/validations.go
index 86fb0212..c469ecfc 100644
--- a/internal/redirects/validations.go
+++ b/internal/redirects/validations.go
@@ -18,43 +18,110 @@ var (
regexPlaceholderReplacement = regexp.MustCompile(`(?i):(?P<placeholder>[a-z]+)`)
)
-// validateURL runs validations against a rule URL.
+// validateFromURL validates the from URL in a redirect rule.
+// It checks for various invalid cases like unsupported schemes,
+// relative URLs, domain redirects without scheme, etc.
// Returns `nil` if the URL is valid.
-func validateURL(urlText string) error {
- url, err := url.Parse(urlText)
+// nolint: gocyclo
+func validateFromURL(urlText string) error {
+ fromURL, err := url.Parse(urlText)
if err != nil {
return errFailedToParseURL
}
- // No support for domain-level redirects to outside sites:
- // - `https://google.com`
+ // No support for domain level redirects starting with special characters without scheme:
// - `//google.com`
// - `/\google.com`
- if url.Host != "" || url.Scheme != "" || strings.HasPrefix(url.Path, "/\\") {
- return errNoDomainLevelRedirects
+ if (fromURL.Host == "") != (fromURL.Scheme == "") || strings.HasPrefix(fromURL.Path, "/\\") {
+ return errNoValidStartingInURLPath
+ }
+
+ if fromURL.Scheme != "" && fromURL.Scheme != "http" && fromURL.Scheme != "https" {
+ return errNoValidStartingInURLPath
+ }
+
+ if fromURL.Scheme == "" && fromURL.Host == "" {
+ // No parent traversing relative URL's with `./` or `../`
+ // No ambiguous URLs like bare domains `GitLab.com`
+ if !strings.HasPrefix(urlText, "/") {
+ return errNoValidStartingInURLPath
+ }
+ }
+
+ if feature.RedirectsPlaceholders.Enabled() && strings.Count(fromURL.Path, "/*") > 1 {
+ return errMoreThanOneSplats
}
- // No parent traversing relative URL's with `./` or `../`
- // No ambiguous URLs like bare domains `GitLab.com`
- if !strings.HasPrefix(url.Path, "/") {
- return errNoStartingForwardSlashInURLPath
+ return validateSplatAndPlaceholders(fromURL.Path)
+}
+
+// validateURL runs validations against a rule URL.
+// Returns `nil` if the URL is valid.
+// nolint: gocyclo
+func validateToURL(urlText string, status int) error {
+ toURL, err := url.Parse(urlText)
+ if err != nil {
+ return errFailedToParseURL
}
+ allowedPrefix := []string{"/"}
+ if feature.DomainRedirects.Enabled() {
+ // No support for domain level redirects starting with // or special characters:
+ // - `//google.com`
+ // - `/\google.com`
+ if (toURL.Host == "") != (toURL.Scheme == "") || strings.HasPrefix(toURL.Path, "/\\") {
+ return errNoValidStartingInURLPath
+ }
+
+ // No support for domain level rewrite
+ if isDomainURL(urlText) {
+ if status == http.StatusOK {
+ return errNoDomainLevelRewrite
+ }
+ allowedPrefix = append(allowedPrefix, "http://", "https://")
+ }
+
+ // No parent traversing relative URL's with `./` or `../`
+ // No ambiguous URLs like bare domains `GitLab.com`
+ if !startsWithAnyPrefix(urlText, allowedPrefix...) {
+ return errNoValidStartingInURLPath
+ }
+ } else {
+ // No support for domain-level redirects to outside sites:
+ // - `https://google.com`
+ // - `//google.com`
+ // - `/\google.com`
+ if toURL.Host != "" || toURL.Scheme != "" || strings.HasPrefix(toURL.Path, "/\\") {
+ return errNoDomainLevelRedirects
+ }
+
+ // No parent traversing relative URL's with `./` or `../`
+ // No ambiguous URLs like bare domains `GitLab.com`
+ if !startsWithAnyPrefix(urlText, allowedPrefix...) {
+ return errNoStartingForwardSlashInURLPath
+ }
+ }
+
+ return validateSplatAndPlaceholders(toURL.Path)
+}
+
+func validateSplatAndPlaceholders(path string) error {
if feature.RedirectsPlaceholders.Enabled() {
+ maxPathSegments := cfg.MaxPathSegments
// Limit the number of path segments a rule can contain.
// This prevents the matching logic from generating regular
// expressions that are too large/complex.
- if strings.Count(url.Path, "/") > cfg.MaxPathSegments {
+ if strings.Count(path, "/") > maxPathSegments {
return fmt.Errorf("url path cannot contain more than %d forward slashes", cfg.MaxPathSegments)
}
} else {
// No support for splats, https://docs.netlify.com/routing/redirects/redirect-options/#splats
- if strings.Contains(url.Path, "*") {
+ if strings.Contains(path, "*") {
return errNoSplats
}
// No support for placeholders, https://docs.netlify.com/routing/redirects/redirect-options/#placeholders
- if regexpPlaceholder.MatchString(url.Path) {
+ if regexpPlaceholder.MatchString(path) {
return errNoPlaceholders
}
}
@@ -65,11 +132,11 @@ func validateURL(urlText string) error {
// validateRule runs all validation rules on the provided rule.
// Returns `nil` if the rule is valid
func validateRule(r netlifyRedirects.Rule) error {
- if err := validateURL(r.From); err != nil {
+ if err := validateFromURL(r.From); err != nil {
return err
}
- if err := validateURL(r.To); err != nil {
+ if err := validateToURL(r.To, r.Status); err != nil {
return err
}
@@ -93,3 +160,12 @@ func validateRule(r netlifyRedirects.Rule) error {
return nil
}
+
+func startsWithAnyPrefix(s string, prefixes ...string) bool {
+ for _, prefix := range prefixes {
+ if strings.HasPrefix(s, prefix) {
+ return true
+ }
+ }
+ return false
+}