internal: urlencode client id and secret in header

As per https://tools.ietf.org/html/rfc6749#section-2.3.1 client IDs and secrets must be urlencoded in the authorization header. This patch addresses this by wrapping clientID and clientSecret with url.QueryEscape. A dedicated test for unsafe-url client IDs and secrets has been added as well.

Closes #237

Change-Id: I1f277b52caef4932e14147be8fb1712203da51d0
Reviewed-on: https://go-review.googlesource.com/46473
Reviewed-by: JBD <jbd@google.com>
diff --git a/internal/token.go b/internal/token.go
index 0487c81..53ec23c 100644
--- a/internal/token.go
+++ b/internal/token.go
@@ -188,7 +188,7 @@
 	}
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 	if !bustedAuth {
-		req.SetBasicAuth(clientID, clientSecret)
+		req.SetBasicAuth(url.QueryEscape(clientID), url.QueryEscape(clientSecret))
 	}
 	r, err := ctxhttp.Do(ctx, hc, req)
 	if err != nil {
diff --git a/oauth2_test.go b/oauth2_test.go
index 584084b..09293ed 100644
--- a/oauth2_test.go
+++ b/oauth2_test.go
@@ -72,6 +72,25 @@
 	}
 }
 
+func TestURLUnsafeClientConfig(t *testing.T) {
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		if got, want := r.Header.Get("Authorization"), "Basic Q0xJRU5UX0lEJTNGJTNGOkNMSUVOVF9TRUNSRVQlM0YlM0Y="; got != want {
+			t.Errorf("Authorization header = %q; want %q", got, want)
+		}
+
+		w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
+		w.Write([]byte("access_token=90d64460d14870c08c81352a05dedd3465940a7c&scope=user&token_type=bearer"))
+	}))
+	defer ts.Close()
+	conf := newConf(ts.URL)
+	conf.ClientID = "CLIENT_ID??"
+	conf.ClientSecret = "CLIENT_SECRET??"
+	_, err := conf.Exchange(context.Background(), "exchange-code")
+	if err != nil {
+		t.Error(err)
+	}
+}
+
 func TestExchangeRequest(t *testing.T) {
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		if r.URL.String() != "/token" {