internal: tolerate malformed expires_in values more

Fixes golang/oauth2#239

Change-Id: Id3fdfbfb64bc1a12ab0e952e83ae444b50de1bb5
Reviewed-on: https://go-review.googlesource.com/c/161964
Reviewed-by: Ross Light <light@google.com>
Run-TryBot: Ross Light <light@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/internal/token.go b/internal/token.go
index 0f75a18..955d5a0 100644
--- a/internal/token.go
+++ b/internal/token.go
@@ -78,6 +78,9 @@
 type expirationTime int32
 
 func (e *expirationTime) UnmarshalJSON(b []byte) error {
+	if len(b) == 0 || string(b) == "null" {
+		return nil
+	}
 	var n json.Number
 	err := json.Unmarshal(b, &n)
 	if err != nil {
@@ -257,7 +260,7 @@
 			Raw:          vals,
 		}
 		e := vals.Get("expires_in")
-		if e == "" {
+		if e == "" || e == "null" {
 			// TODO(jbd): Facebook's OAuth2 implementation is broken and
 			// returns expires_in field in expires. Remove the fallback to expires,
 			// when Facebook fixes their implementation.
diff --git a/oauth2_test.go b/oauth2_test.go
index a059b8b..b76eaae 100644
--- a/oauth2_test.go
+++ b/oauth2_test.go
@@ -275,17 +275,22 @@
 func TestExchangeRequest_JSONResponse_Expiry(t *testing.T) {
 	seconds := int32(day.Seconds())
 	for _, c := range []struct {
+		name    string
 		expires string
 		want    bool
 	}{
-		{fmt.Sprintf(`"expires_in": %d`, seconds), true},
-		{fmt.Sprintf(`"expires_in": "%d"`, seconds), true}, // PayPal case
-		{fmt.Sprintf(`"expires": %d`, seconds), true},      // Facebook case
-		{`"expires": false`, false},                        // wrong type
-		{`"expires": {}`, false},                           // wrong type
-		{`"expires": "zzz"`, false},                        // wrong value
+		{"normal", fmt.Sprintf(`"expires_in": %d`, seconds), true},
+		{"paypal", fmt.Sprintf(`"expires_in": "%d"`, seconds), true},
+		{"facebook", fmt.Sprintf(`"expires": %d`, seconds), true},
+		{"issue_239", fmt.Sprintf(`"expires_in": null, "expires": %d`, seconds), true},
+
+		{"wrong_type", `"expires": false`, false},
+		{"wrong_type2", `"expires": {}`, false},
+		{"wrong_value", `"expires": "zzz"`, false},
 	} {
-		testExchangeRequest_JSONResponse_expiry(t, c.expires, c.want)
+		t.Run(c.name, func(t *testing.T) {
+			testExchangeRequest_JSONResponse_expiry(t, c.expires, c.want)
+		})
 	}
 }