net/http: Various fixes to Basic authentication

There were some issues with the code sometimes using base64.StdEncoding,
and sometimes base64.URLEncoding.
Encoding basic authentication is now always done by the same code.

Fixes #5970.

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/12397043
diff --git a/src/pkg/net/http/client.go b/src/pkg/net/http/client.go
index 331e8ad..22f2e86 100644
--- a/src/pkg/net/http/client.go
+++ b/src/pkg/net/http/client.go
@@ -161,18 +161,9 @@
 	}
 
 	if u := req.URL.User; u != nil {
-		auth := u.String()
-		// UserInfo.String() only returns the colon when the
-		// password is set, so we must add it here.
-		//
-		// See 2 (end of page 4) http://www.ietf.org/rfc/rfc2617.txt
-		// "To receive authorization, the client sends the userid and password,
-		// separated by a single colon (":") character, within a base64
-		// encoded string in the credentials."
-		if _, hasPassword := u.Password(); !hasPassword {
-			auth += ":"
-		}
-		req.Header.Set("Authorization", "Basic "+base64.URLEncoding.EncodeToString([]byte(auth)))
+		username := u.Username()
+		password, _ := u.Password()
+		req.Header.Set("Authorization", "Basic "+basicAuth(username, password))
 	}
 	resp, err = t.RoundTrip(req)
 	if err != nil {
@@ -184,6 +175,16 @@
 	return resp, nil
 }
 
+// See 2 (end of page 4) http://www.ietf.org/rfc/rfc2617.txt
+// "To receive authorization, the client sends the userid and password,
+// separated by a single colon (":") character, within a base64
+// encoded string in the credentials."
+// It is not meant to be urlencoded.
+func basicAuth(username, password string) string {
+	auth := username + ":" + password
+	return base64.StdEncoding.EncodeToString([]byte(auth))
+}
+
 // True if the specified HTTP status code is one for which the Get utility should
 // automatically redirect.
 func shouldRedirectGet(statusCode int) bool {
diff --git a/src/pkg/net/http/client_test.go b/src/pkg/net/http/client_test.go
index 69fa168..997d041 100644
--- a/src/pkg/net/http/client_test.go
+++ b/src/pkg/net/http/client_test.go
@@ -765,3 +765,37 @@
 	}
 	defer resp.Body.Close()
 }
+
+func TestBasicAuth(t *testing.T) {
+	defer afterTest(t)
+	tr := &recordingTransport{}
+	client := &Client{Transport: tr}
+
+	url := "http://My%20User:My%20Pass@dummy.faketld/"
+	expected := "My User:My Pass"
+	client.Get(url)
+
+	if tr.req.Method != "GET" {
+		t.Errorf("got method %q, want %q", tr.req.Method, "GET")
+	}
+	if tr.req.URL.String() != url {
+		t.Errorf("got URL %q, want %q", tr.req.URL.String(), url)
+	}
+	if tr.req.Header == nil {
+		t.Fatalf("expected non-nil request Header")
+	}
+	auth := tr.req.Header.Get("Authorization")
+	if strings.HasPrefix(auth, "Basic ") {
+		encoded := auth[6:]
+		decoded, err := base64.StdEncoding.DecodeString(encoded)
+		if err != nil {
+			t.Fatal(err)
+		}
+		s := string(decoded)
+		if expected != s {
+			t.Errorf("Invalid Authorization header. Got %q, wanted %q", s, expected)
+		}
+	} else {
+		t.Errorf("Invalid auth %q", auth)
+	}
+}
diff --git a/src/pkg/net/http/request.go b/src/pkg/net/http/request.go
index 90e5622..603299d 100644
--- a/src/pkg/net/http/request.go
+++ b/src/pkg/net/http/request.go
@@ -10,7 +10,6 @@
 	"bufio"
 	"bytes"
 	"crypto/tls"
-	"encoding/base64"
 	"errors"
 	"fmt"
 	"io"
@@ -467,8 +466,7 @@
 // With HTTP Basic Authentication the provided username and password
 // are not encrypted.
 func (r *Request) SetBasicAuth(username, password string) {
-	s := username + ":" + password
-	r.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(s)))
+	r.Header.Set("Authorization", "Basic "+basicAuth(username, password))
 }
 
 // parseRequestLine parses "GET /foo HTTP/1.1" into its three parts.
diff --git a/src/pkg/net/http/transport.go b/src/pkg/net/http/transport.go
index 49a034b..f6871af 100644
--- a/src/pkg/net/http/transport.go
+++ b/src/pkg/net/http/transport.go
@@ -13,7 +13,6 @@
 	"bufio"
 	"compress/gzip"
 	"crypto/tls"
-	"encoding/base64"
 	"errors"
 	"fmt"
 	"io"
@@ -273,7 +272,9 @@
 		return ""
 	}
 	if u := cm.proxyURL.User; u != nil {
-		return "Basic " + base64.URLEncoding.EncodeToString([]byte(u.String()))
+		username := u.Username()
+		password, _ := u.Password()
+		return "Basic " + basicAuth(username, password)
 	}
 	return ""
 }
diff --git a/src/pkg/net/url/url.go b/src/pkg/net/url/url.go
index 043fd48..95432f4 100644
--- a/src/pkg/net/url/url.go
+++ b/src/pkg/net/url/url.go
@@ -451,8 +451,8 @@
 	} else {
 		if u.Scheme != "" || u.Host != "" || u.User != nil {
 			buf.WriteString("//")
-			if u := u.User; u != nil {
-				buf.WriteString(u.String())
+			if ui := u.User; ui != nil {
+				buf.WriteString(ui.String())
 				buf.WriteByte('@')
 			}
 			if h := u.Host; h != "" {