package registry import ( "encoding/json" "errors" "net/http" "regexp" ) var ( ErrNoMorePages = errors.New("no more pages") ) // getPaginatedJSON accepts a string and a pointer, and returns the // next page URL while updating pointed-to variable with a parsed JSON // value. When there are no more pages it returns `ErrNoMorePages`. func (registry *Registry) getPaginatedJSON(url string, response interface{}) (string, error) { resp, err := registry.Client.Get(url) if err != nil { return "", err } defer resp.Body.Close() decoder := json.NewDecoder(resp.Body) err = decoder.Decode(response) if err != nil { return "", err } return getNextLink(resp) } // Matches an RFC 5988 (https://tools.ietf.org/html/rfc5988#section-5) // Link header. For example, // // ; type="application/json"; rel="next" // // The URL is _supposed_ to be wrapped by angle brackets `< ... >`, // but e.g., quay.io does not include them. Similarly, params like // `rel="next"` may not have quoted values in the wild. var nextLinkRE = regexp.MustCompile(`^ *]+)>? *(?:;[^;]*)*; *rel="?next"?(?:;.*)?`) func getNextLink(resp *http.Response) (string, error) { for _, link := range resp.Header[http.CanonicalHeaderKey("Link")] { parts := nextLinkRE.FindStringSubmatch(link) if parts != nil { return parts[1], nil } } return "", ErrNoMorePages }