url.ForceQuery = true
rest = rest[:len(rest)-1]
} else {
- rest, url.RawQuery = split(rest, "?", true)
+ var q string
+ rest, q = split(rest, "?", true)
+ if validQuery(q) {
+ url.RawQuery = q
+ } else {
+ url.RawQuery = QueryEscape(q)
+ }
}
if !strings.HasPrefix(rest, "/") {
}
return true
}
+
+// validQuery reports whether s is a valid query string per RFC 3986
+// Section 3.4:
+// query = *( pchar / "/" / "?" )
+// pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
+// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+// sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
+// / "*" / "+" / "," / ";" / "="
+func validQuery(s string) bool {
+ pctEnc := 0
+
+ for _, r := range s {
+ if pctEnc > 0 {
+ if uint32(r) > 255 || !ishex(byte(r)) {
+ return false
+ }
+ pctEnc--
+ continue
+ } else if r == '%' {
+ pctEnc = 2
+ continue
+ }
+
+ if 'A' <= r && r <= 'Z' {
+ continue
+ }
+ if 'a' <= r && r <= 'z' {
+ continue
+ }
+ if '0' <= r && r <= '9' {
+ continue
+ }
+ switch r {
+ case '-', '.', '_', '~', '!', '$', '&', '\'', '(', ')',
+ '*', '+', ',', ';', '=', ':', '@', '/', '?':
+ continue
+ default:
+ return false
+ }
+ }
+
+ return true
+}
},
"mailto:?subject=hi",
},
+ {
+ "https://example.com/search?q=Фотки собак&source=lnms",
+ &URL{
+ Scheme: "https",
+ Host: "example.com",
+ Path: "/search",
+ RawQuery: "q%3D%D0%A4%D0%BE%D1%82%D0%BA%D0%B8+%D1%81%D0%BE%D0%B1%D0%B0%D0%BA%26source%3Dlnms",
+ },
+ "https://example.com/search?q%3D%D0%A4%D0%BE%D1%82%D0%BA%D0%B8+%D1%81%D0%BE%D0%B1%D0%B0%D0%BA%26source%3Dlnms",
+ },
}
// more useful string for debugging than fmt's struct printer
{"cache_object:foo", true},
{"cache_object:foo/bar", true},
{"cache_object/:foo/bar", false},
+ {"https://example.com/search?q=Фотки собак&source=lnms", false},
}
for _, tt := range tests {
u, err := Parse(tt.in)