// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package internal

import (
	
	
	
	
	
	
	
	
	
	
	
	
	
	
)

// Token represents the credentials used to authorize
// the requests to access protected resources on the OAuth 2.0
// provider's backend.
//
// This type is a mirror of [golang.org/x/oauth2.Token] and exists to break
// an otherwise-circular dependency. Other internal packages
// should convert this Token into an [golang.org/x/oauth2.Token] before use.
type Token struct {
	// AccessToken is the token that authorizes and authenticates
	// the requests.
	AccessToken string

	// TokenType is the type of token.
	// The Type method returns either this or "Bearer", the default.
	TokenType string

	// RefreshToken is a token that's used by the application
	// (as opposed to the user) to refresh the access token
	// if it expires.
	RefreshToken string

	// Expiry is the optional expiration time of the access token.
	//
	// If zero, TokenSource implementations will reuse the same
	// token forever and RefreshToken or equivalent
	// mechanisms for that TokenSource will not be used.
	Expiry time.Time

	// ExpiresIn is the OAuth2 wire format "expires_in" field,
	// which specifies how many seconds later the token expires,
	// relative to an unknown time base approximately around "now".
	// It is the application's responsibility to populate
	// `Expiry` from `ExpiresIn` when required.
	ExpiresIn int64 `json:"expires_in,omitempty"`

	// Raw optionally contains extra metadata from the server
	// when updating a token.
	Raw any
}

// tokenJSON is the struct representing the HTTP response from OAuth2
// providers returning a token or error in JSON form.
// https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
type tokenJSON struct {
	AccessToken  string         `json:"access_token"`
	TokenType    string         `json:"token_type"`
	RefreshToken string         `json:"refresh_token"`
	ExpiresIn    expirationTime `json:"expires_in"` // at least PayPal returns string, while most return number
	// error fields
	// https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
	ErrorCode        string `json:"error"`
	ErrorDescription string `json:"error_description"`
	ErrorURI         string `json:"error_uri"`
}

func ( *tokenJSON) () ( time.Time) {
	if  := .ExpiresIn;  != 0 {
		return time.Now().Add(time.Duration() * time.Second)
	}
	return
}

type expirationTime int32

func ( *expirationTime) ( []byte) error {
	if len() == 0 || string() == "null" {
		return nil
	}
	var  json.Number
	 := json.Unmarshal(, &)
	if  != nil {
		return 
	}
	,  := .Int64()
	if  != nil {
		return 
	}
	if  > math.MaxInt32 {
		 = math.MaxInt32
	}
	* = expirationTime()
	return nil
}

// AuthStyle is a copy of the golang.org/x/oauth2 package's AuthStyle type.
type AuthStyle int

const (
	AuthStyleUnknown  AuthStyle = 0
	AuthStyleInParams AuthStyle = 1
	AuthStyleInHeader AuthStyle = 2
)

// LazyAuthStyleCache is a backwards compatibility compromise to let Configs
// have a lazily-initialized AuthStyleCache.
//
// The two users of this, oauth2.Config and oauth2/clientcredentials.Config,
// both would ideally just embed an unexported AuthStyleCache but because both
// were historically allowed to be copied by value we can't retroactively add an
// uncopyable Mutex to them.
//
// We could use an atomic.Pointer, but that was added recently enough (in Go
// 1.18) that we'd break Go 1.17 users where the tests as of 2023-08-03
// still pass. By using an atomic.Value, it supports both Go 1.17 and
// copying by value, even if that's not ideal.
type LazyAuthStyleCache struct {
	v atomic.Value // of *AuthStyleCache
}

func ( *LazyAuthStyleCache) () *AuthStyleCache {
	if ,  := .v.Load().(*AuthStyleCache);  {
		return 
	}
	 := new(AuthStyleCache)
	if !.v.CompareAndSwap(nil, ) {
		 = .v.Load().(*AuthStyleCache)
	}
	return 
}

type authStyleCacheKey struct {
	url      string
	clientID string
}

// AuthStyleCache is the set of tokenURLs we've successfully used via
// RetrieveToken and which style auth we ended up using.
// It's called a cache, but it doesn't (yet?) shrink. It's expected that
// the set of OAuth2 servers a program contacts over time is fixed and
// small.
type AuthStyleCache struct {
	mu sync.Mutex
	m  map[authStyleCacheKey]AuthStyle
}

// lookupAuthStyle reports which auth style we last used with tokenURL
// when calling RetrieveToken and whether we have ever done so.
func ( *AuthStyleCache) (,  string) ( AuthStyle,  bool) {
	.mu.Lock()
	defer .mu.Unlock()
	,  = .m[authStyleCacheKey{, }]
	return
}

// setAuthStyle adds an entry to authStyleCache, documented above.
func ( *AuthStyleCache) (,  string,  AuthStyle) {
	.mu.Lock()
	defer .mu.Unlock()
	if .m == nil {
		.m = make(map[authStyleCacheKey]AuthStyle)
	}
	.m[authStyleCacheKey{, }] = 
}

// newTokenRequest returns a new *http.Request to retrieve a new token
// from tokenURL using the provided clientID, clientSecret, and POST
// body parameters.
//
// inParams is whether the clientID & clientSecret should be encoded
// as the POST body. An 'inParams' value of true means to send it in
// the POST body (along with any values in v); false means to send it
// in the Authorization header.
func newTokenRequest(, ,  string,  url.Values,  AuthStyle) (*http.Request, error) {
	if  == AuthStyleInParams {
		 = cloneURLValues()
		if  != "" {
			.Set("client_id", )
		}
		if  != "" {
			.Set("client_secret", )
		}
	}
	,  := http.NewRequest("POST", , strings.NewReader(.Encode()))
	if  != nil {
		return nil, 
	}
	.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	if  == AuthStyleInHeader {
		.SetBasicAuth(url.QueryEscape(), url.QueryEscape())
	}
	return , nil
}

func cloneURLValues( url.Values) url.Values {
	 := make(url.Values, len())
	for ,  := range  {
		[] = append([]string(nil), ...)
	}
	return 
}

func ( context.Context, , ,  string,  url.Values,  AuthStyle,  *AuthStyleCache) (*Token, error) {
	 :=  == AuthStyleUnknown
	if  {
		if ,  := .lookupAuthStyle(, );  {
			 = 
			 = false
		} else {
			 = AuthStyleInHeader // the first way we'll try
		}
	}
	,  := newTokenRequest(, , , , )
	if  != nil {
		return nil, 
	}
	,  := doTokenRoundTrip(, )
	if  != nil &&  {
		// If we get an error, assume the server wants the
		// clientID & clientSecret in a different form.
		// See https://code.google.com/p/goauth2/issues/detail?id=31 for background.
		// In summary:
		// - Reddit only accepts client secret in the Authorization header
		// - Dropbox accepts either it in URL param or Auth header, but not both.
		// - Google only accepts URL param (not spec compliant?), not Auth header
		// - Stripe only accepts client secret in Auth header with Bearer method, not Basic
		//
		// We used to maintain a big table in this code of all the sites and which way
		// they went, but maintaining it didn't scale & got annoying.
		// So just try both ways.
		 = AuthStyleInParams // the second way we'll try
		, _ = newTokenRequest(, , , , )
		,  = doTokenRoundTrip(, )
	}
	if  &&  == nil {
		.setAuthStyle(, , )
	}
	// Don't overwrite `RefreshToken` with an empty value
	// if this was a token refreshing request.
	if  != nil && .RefreshToken == "" {
		.RefreshToken = .Get("refresh_token")
	}
	return , 
}

func doTokenRoundTrip( context.Context,  *http.Request) (*Token, error) {
	,  := ContextClient().Do(.WithContext())
	if  != nil {
		return nil, 
	}
	,  := io.ReadAll(io.LimitReader(.Body, 1<<20))
	.Body.Close()
	if  != nil {
		return nil, fmt.Errorf("oauth2: cannot fetch token: %v", )
	}

	 := .StatusCode < 200 || .StatusCode > 299
	 := &RetrieveError{
		Response: ,
		Body:     ,
		// attempt to populate error detail below
	}

	var  *Token
	, ,  := mime.ParseMediaType(.Header.Get("Content-Type"))
	switch  {
	case "application/x-www-form-urlencoded", "text/plain":
		// some endpoints return a query string
		,  := url.ParseQuery(string())
		if  != nil {
			if  {
				return nil, 
			}
			return nil, fmt.Errorf("oauth2: cannot parse response: %v", )
		}
		.ErrorCode = .Get("error")
		.ErrorDescription = .Get("error_description")
		.ErrorURI = .Get("error_uri")
		 = &Token{
			AccessToken:  .Get("access_token"),
			TokenType:    .Get("token_type"),
			RefreshToken: .Get("refresh_token"),
			Raw:          ,
		}
		 := .Get("expires_in")
		,  := strconv.Atoi()
		if  != 0 {
			.Expiry = time.Now().Add(time.Duration() * time.Second)
		}
	default:
		var  tokenJSON
		if  = json.Unmarshal(, &);  != nil {
			if  {
				return nil, 
			}
			return nil, fmt.Errorf("oauth2: cannot parse json: %v", )
		}
		.ErrorCode = .ErrorCode
		.ErrorDescription = .ErrorDescription
		.ErrorURI = .ErrorURI
		 = &Token{
			AccessToken:  .AccessToken,
			TokenType:    .TokenType,
			RefreshToken: .RefreshToken,
			Expiry:       .expiry(),
			ExpiresIn:    int64(.ExpiresIn),
			Raw:          make(map[string]any),
		}
		json.Unmarshal(, &.Raw) // no error checks for optional fields
	}
	// according to spec, servers should respond status 400 in error case
	// https://www.rfc-editor.org/rfc/rfc6749#section-5.2
	// but some unorthodox servers respond 200 in error case
	if  || .ErrorCode != "" {
		return nil, 
	}
	if .AccessToken == "" {
		return nil, errors.New("oauth2: server response missing access_token")
	}
	return , nil
}

// mirrors oauth2.RetrieveError
type RetrieveError struct {
	Response         *http.Response
	Body             []byte
	ErrorCode        string
	ErrorDescription string
	ErrorURI         string
}

func ( *RetrieveError) () string {
	if .ErrorCode != "" {
		 := fmt.Sprintf("oauth2: %q", .ErrorCode)
		if .ErrorDescription != "" {
			 += fmt.Sprintf(" %q", .ErrorDescription)
		}
		if .ErrorURI != "" {
			 += fmt.Sprintf(" %q", .ErrorURI)
		}
		return 
	}
	return fmt.Sprintf("oauth2: cannot fetch token: %v\nResponse: %s", .Response.Status, .Body)
}