Copyright 2020 Google LLC. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

package idtoken

import (
	
	
	
	
	
	
	
	
)

type cachingClient struct {
	client *http.Client
clock optionally specifies a func to return the current time. If nil, time.Now is used.
	clock func() time.Time

	mu    sync.Mutex
	certs map[string]*cachedResponse
}

func ( *http.Client) *cachingClient {
	return &cachingClient{
		client: ,
		certs:  make(map[string]*cachedResponse, 2),
	}
}

type cachedResponse struct {
	resp *certResponse
	exp  time.Time
}

func ( *cachingClient) ( context.Context,  string) (*certResponse, error) {
	if ,  := .get();  {
		return , nil
	}
	,  := http.NewRequest(http.MethodGet, , nil)
	if  != nil {
		return nil, 
	}
	 = .WithContext()
	,  := .client.Do()
	if  != nil {
		return nil, 
	}
	defer .Body.Close()
	if .StatusCode != http.StatusOK {
		return nil, fmt.Errorf("idtoken: unable to retrieve cert, got status code %d", .StatusCode)
	}

	 := &certResponse{}
	if  := json.NewDecoder(.Body).Decode();  != nil {
		return nil, 

	}
	.set(, , .Header)
	return , nil
}

func ( *cachingClient) () time.Time {
	if .clock != nil {
		return .clock()
	}
	return time.Now()
}

func ( *cachingClient) ( string) (*certResponse, bool) {
	.mu.Lock()
	defer .mu.Unlock()
	,  := .certs[]
	if ! {
		return nil, false
	}
	if .now().After(.exp) {
		return nil, false
	}
	return .resp, true
}

func ( *cachingClient) ( string,  *certResponse,  http.Header) {
	 := .calculateExpireTime()
	.mu.Lock()
	.certs[] = &cachedResponse{resp: , exp: }
	.mu.Unlock()
}
calculateExpireTime will determine the expire time for the cache based on HTTP headers. If there is any difficulty reading the headers the fallback is to set the cache to expire now.
func ( *cachingClient) ( http.Header) time.Time {
	var  int
	 := strings.Split(.Get("cache-control"), ",")
	for ,  := range  {
		if strings.Contains(, "max-age") {
			 := strings.Split(, "=")
			if len() < 2 {
				return .now()
			}
			,  := strconv.Atoi([1])
			if  != nil {
				return .now()
			}
			 = 
		}
	}
	,  := strconv.Atoi(.Get("age"))
	if  != nil {
		return .now()
	}
	return .now().Add(time.Duration(-) * time.Second)