net/url, net/http: relax CTL-in-URL validation to only ASCII CTLs

CL 159157 was doing UTF-8 decoding of URLs. URLs aren't really UTF-8,
even if sometimes they are in some contexts.

Instead, only reject ASCII CTLs.

Updates #27302
Updates #22907

Change-Id: Ibd64efa5d3a93263d175aadf1c9f87deb4670c62
Reviewed-on: https://go-review.googlesource.com/c/160178
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
diff --git a/src/net/http/http.go b/src/net/http/http.go
index 5c03c16..e5d59e1 100644
--- a/src/net/http/http.go
+++ b/src/net/http/http.go
@@ -59,10 +59,15 @@
 	return true
 }
 
-// isCTL reports whether r is an ASCII control character, including
-// the Extended ASCII control characters included in Unicode.
-func isCTL(r rune) bool {
-	return r < ' ' || 0x7f <= r && r <= 0x9f
+// stringContainsCTLByte reports whether s contains any ASCII control character.
+func stringContainsCTLByte(s string) bool {
+	for i := 0; i < len(s); i++ {
+		b := s[i]
+		if b < ' ' || b == 0x7f {
+			return true
+		}
+	}
+	return false
 }
 
 func hexEscapeNonASCII(s string) string {
diff --git a/src/net/http/request.go b/src/net/http/request.go
index 01ba1dc..dcad2b6 100644
--- a/src/net/http/request.go
+++ b/src/net/http/request.go
@@ -550,7 +550,7 @@
 			ruri = r.URL.Opaque
 		}
 	}
-	if strings.IndexFunc(ruri, isCTL) != -1 {
+	if stringContainsCTLByte(ruri) {
 		return errors.New("net/http: can't write control character in Request.URL")
 	}
 	// TODO: validate r.Method too? At least it's less likely to
diff --git a/src/net/url/url.go b/src/net/url/url.go
index 77078ad..64274a0 100644
--- a/src/net/url/url.go
+++ b/src/net/url/url.go
@@ -513,7 +513,7 @@
 	var rest string
 	var err error
 
-	if strings.IndexFunc(rawurl, isCTL) != -1 {
+	if stringContainsCTLByte(rawurl) {
 		return nil, errors.New("net/url: invalid control character in URL")
 	}
 
@@ -1139,8 +1139,13 @@
 	return true
 }
 
-// isCTL reports whether r is an ASCII control character, including
-// the Extended ASCII control characters included in Unicode.
-func isCTL(r rune) bool {
-	return r < ' ' || 0x7f <= r && r <= 0x9f
+// stringContainsCTLByte reports whether s contains any ASCII control character.
+func stringContainsCTLByte(s string) bool {
+	for i := 0; i < len(s); i++ {
+		b := s[i]
+		if b < ' ' || b == 0x7f {
+			return true
+		}
+	}
+	return false
 }
diff --git a/src/net/url/url_test.go b/src/net/url/url_test.go
index 43d77f0..c5fc90d 100644
--- a/src/net/url/url_test.go
+++ b/src/net/url/url_test.go
@@ -1757,6 +1757,12 @@
 			t.Errorf("Parse(%q) error = %q; want substring %q", s, got, wantSub)
 		}
 	}
+
+	// But don't reject non-ASCII CTLs, at least for now:
+	if _, err := Parse("http://foo.com/ctl\x80"); err != nil {
+		t.Errorf("error parsing URL with non-ASCII control byte: %v", err)
+	}
+
 }
 
 var escapeBenchmarks = []struct {