internal/iapclient: use http.PostForm to send request

It has identical behavior but makes the code shorter and easier
to see that it's just a form POST, rather than something custom.

Also cap the body size it's willing to read for error reporting.

(Spotted while reading over this code.)

For golang/go#48739.

Change-Id: I586925d1a0c7e9a7e1efc93d121337a16fcee725
Reviewed-on: https://go-review.googlesource.com/c/build/+/371014
Trust: Dmitri Shuralyov <dmitshur@golang.org>
Run-TryBot: Dmitri Shuralyov <dmitshur@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
diff --git a/internal/iapclient/iapclient.go b/internal/iapclient/iapclient.go
index 4750f61..356877e 100644
--- a/internal/iapclient/iapclient.go
+++ b/internal/iapclient/iapclient.go
@@ -10,12 +10,11 @@
 	"context"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"net/url"
 	"os"
 	"path/filepath"
-	"strings"
 
 	"golang.org/x/oauth2"
 	"golang.org/x/oauth2/google"
@@ -119,32 +118,28 @@
 	refresh  *oauth2.Token
 }
 
-// Exchange a refresh token for a JWT that works with IAP. As of writing, there
+// Token exchanges a refresh token for a JWT that works with IAP. As of writing, there
 // isn't anything to do this in the oauth2 library or google.golang.org/api/idtoken.
 func (s *jwtTokenSource) Token() (*oauth2.Token, error) {
-	v := url.Values{}
-	v.Set("client_id", s.conf.ClientID)
-	v.Set("client_secret", s.conf.ClientSecret)
-	v.Set("refresh_token", s.refresh.RefreshToken)
-	v.Set("grant_type", "refresh_token")
-	v.Set("audience", s.audience)
-	req, err := http.NewRequest("POST", s.conf.Endpoint.TokenURL, strings.NewReader(v.Encode()))
-	if err != nil {
-		return nil, err
-	}
-	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
-	resp, err := http.DefaultClient.Do(req)
+	resp, err := http.PostForm(s.conf.Endpoint.TokenURL, url.Values{
+		"client_id":     []string{s.conf.ClientID},
+		"client_secret": []string{s.conf.ClientSecret},
+		"refresh_token": []string{s.refresh.RefreshToken},
+		"grant_type":    []string{"refresh_token"},
+		"audience":      []string{s.audience},
+	})
 	if err != nil {
 		return nil, err
 	}
 	defer resp.Body.Close()
-	body, err := ioutil.ReadAll(resp.Body)
+	if resp.StatusCode != http.StatusOK {
+		body, _ := io.ReadAll(io.LimitReader(resp.Body, 4<<10))
+		return nil, fmt.Errorf("IAP token exchange failed: status %v, body %q", resp.Status, body)
+	}
+	body, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return nil, err
 	}
-	if resp.StatusCode != http.StatusOK {
-		return nil, fmt.Errorf("IAP token exchange failed: status %v, body %q", resp.Status, body)
-	}
 	var token jwtTokenJSON
 	if err := json.Unmarshal(body, &token); err != nil {
 		return nil, err