package shorturl import ( "fmt" "strings" ) // MaxPathLength caps the long URL length we'll accept. 2KB is well beyond any // real `?hl=` URL we generate while preventing pathological storage abuse. const MaxPathLength = 2048 // ValidatePath verifies that s is a same-site relative URL — starts with "/", // no scheme/host, no NUL bytes, within length. Prevents using the shortener // as an open redirect to arbitrary external domains. func ValidatePath(s string) error { if s == "" { return fmt.Errorf("url is empty") } if len(s) > MaxPathLength { return fmt.Errorf("url exceeds %d bytes", MaxPathLength) } if !strings.HasPrefix(s, "/") { return fmt.Errorf("url must start with '/'") } // "//host/…" is protocol-relative — disallow. if strings.HasPrefix(s, "//") { return fmt.Errorf("url must not be protocol-relative") } if strings.ContainsRune(s, 0) { return fmt.Errorf("url contains NUL byte") } // No scheme allowed: scan up to the first '/' or '?' for ':'. for i, c := range s { if c == '?' || c == '#' { break } if c == ':' && i > 0 { return fmt.Errorf("url must not contain a scheme") } } return nil }