github.com/google/go-github/v52@v52.0.0/github/github.go (about)

     1  // Copyright 2013 The go-github AUTHORS. All rights reserved.
     2  //
     3  // Use of this source code is governed by a BSD-style
     4  // license that can be found in the LICENSE file.
     5  
     6  //go:generate go run gen-accessors.go
     7  //go:generate go run gen-stringify-test.go
     8  
     9  package github
    10  
    11  import (
    12  	"bytes"
    13  	"context"
    14  	"encoding/json"
    15  	"errors"
    16  	"fmt"
    17  	"io"
    18  	"net/http"
    19  	"net/url"
    20  	"reflect"
    21  	"strconv"
    22  	"strings"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/google/go-querystring/query"
    27  	"golang.org/x/oauth2"
    28  )
    29  
    30  const (
    31  	Version = "v52.0.0"
    32  
    33  	defaultAPIVersion = "2022-11-28"
    34  	defaultBaseURL    = "https://api.github.com/"
    35  	defaultUserAgent  = "go-github" + "/" + Version
    36  	uploadBaseURL     = "https://uploads.github.com/"
    37  
    38  	headerAPIVersion    = "X-GitHub-Api-Version"
    39  	headerRateLimit     = "X-RateLimit-Limit"
    40  	headerRateRemaining = "X-RateLimit-Remaining"
    41  	headerRateReset     = "X-RateLimit-Reset"
    42  	headerOTP           = "X-GitHub-OTP"
    43  
    44  	headerTokenExpiration = "GitHub-Authentication-Token-Expiration"
    45  
    46  	mediaTypeV3                = "application/vnd.github.v3+json"
    47  	defaultMediaType           = "application/octet-stream"
    48  	mediaTypeV3SHA             = "application/vnd.github.v3.sha"
    49  	mediaTypeV3Diff            = "application/vnd.github.v3.diff"
    50  	mediaTypeV3Patch           = "application/vnd.github.v3.patch"
    51  	mediaTypeOrgPermissionRepo = "application/vnd.github.v3.repository+json"
    52  	mediaTypeIssueImportAPI    = "application/vnd.github.golden-comet-preview+json"
    53  
    54  	// Media Type values to access preview APIs
    55  	// These media types will be added to the API request as headers
    56  	// and used to enable particular features on GitHub API that are still in preview.
    57  	// After some time, specific media types will be promoted (to a "stable" state).
    58  	// From then on, the preview headers are not required anymore to activate the additional
    59  	// feature on GitHub.com's API. However, this API header might still be needed for users
    60  	// to run a GitHub Enterprise Server on-premise.
    61  	// It's not uncommon for GitHub Enterprise Server customers to run older versions which
    62  	// would probably rely on the preview headers for some time.
    63  	// While the header promotion is going out for GitHub.com, it may be some time before it
    64  	// even arrives in GitHub Enterprise Server.
    65  	// We keep those preview headers around to avoid breaking older GitHub Enterprise Server
    66  	// versions. Additionally, non-functional (preview) headers don't create any side effects
    67  	// on GitHub Cloud version.
    68  	//
    69  	// See https://github.com/google/go-github/pull/2125 for full context.
    70  
    71  	// https://developer.github.com/changes/2014-12-09-new-attributes-for-stars-api/
    72  	mediaTypeStarringPreview = "application/vnd.github.v3.star+json"
    73  
    74  	// https://help.github.com/enterprise/2.4/admin/guides/migrations/exporting-the-github-com-organization-s-repositories/
    75  	mediaTypeMigrationsPreview = "application/vnd.github.wyandotte-preview+json"
    76  
    77  	// https://developer.github.com/changes/2016-04-06-deployment-and-deployment-status-enhancements/
    78  	mediaTypeDeploymentStatusPreview = "application/vnd.github.ant-man-preview+json"
    79  
    80  	// https://developer.github.com/changes/2018-10-16-deployments-environments-states-and-auto-inactive-updates/
    81  	mediaTypeExpandDeploymentStatusPreview = "application/vnd.github.flash-preview+json"
    82  
    83  	// https://developer.github.com/changes/2016-05-12-reactions-api-preview/
    84  	mediaTypeReactionsPreview = "application/vnd.github.squirrel-girl-preview"
    85  
    86  	// https://developer.github.com/changes/2016-05-23-timeline-preview-api/
    87  	mediaTypeTimelinePreview = "application/vnd.github.mockingbird-preview+json"
    88  
    89  	// https://developer.github.com/changes/2016-09-14-projects-api/
    90  	mediaTypeProjectsPreview = "application/vnd.github.inertia-preview+json"
    91  
    92  	// https://developer.github.com/changes/2017-01-05-commit-search-api/
    93  	mediaTypeCommitSearchPreview = "application/vnd.github.cloak-preview+json"
    94  
    95  	// https://developer.github.com/changes/2017-02-28-user-blocking-apis-and-webhook/
    96  	mediaTypeBlockUsersPreview = "application/vnd.github.giant-sentry-fist-preview+json"
    97  
    98  	// https://developer.github.com/changes/2017-05-23-coc-api/
    99  	mediaTypeCodesOfConductPreview = "application/vnd.github.scarlet-witch-preview+json"
   100  
   101  	// https://developer.github.com/changes/2017-07-17-update-topics-on-repositories/
   102  	mediaTypeTopicsPreview = "application/vnd.github.mercy-preview+json"
   103  
   104  	// https://developer.github.com/changes/2018-03-16-protected-branches-required-approving-reviews/
   105  	mediaTypeRequiredApprovingReviewsPreview = "application/vnd.github.luke-cage-preview+json"
   106  
   107  	// https://developer.github.com/changes/2018-05-07-new-checks-api-public-beta/
   108  	mediaTypeCheckRunsPreview = "application/vnd.github.antiope-preview+json"
   109  
   110  	// https://developer.github.com/enterprise/2.13/v3/repos/pre_receive_hooks/
   111  	mediaTypePreReceiveHooksPreview = "application/vnd.github.eye-scream-preview"
   112  
   113  	// https://developer.github.com/changes/2018-02-22-protected-branches-required-signatures/
   114  	mediaTypeSignaturePreview = "application/vnd.github.zzzax-preview+json"
   115  
   116  	// https://developer.github.com/changes/2018-09-05-project-card-events/
   117  	mediaTypeProjectCardDetailsPreview = "application/vnd.github.starfox-preview+json"
   118  
   119  	// https://developer.github.com/changes/2018-12-18-interactions-preview/
   120  	mediaTypeInteractionRestrictionsPreview = "application/vnd.github.sombra-preview+json"
   121  
   122  	// https://developer.github.com/changes/2019-03-14-enabling-disabling-pages/
   123  	mediaTypeEnablePagesAPIPreview = "application/vnd.github.switcheroo-preview+json"
   124  
   125  	// https://developer.github.com/changes/2019-04-24-vulnerability-alerts/
   126  	mediaTypeRequiredVulnerabilityAlertsPreview = "application/vnd.github.dorian-preview+json"
   127  
   128  	// https://developer.github.com/changes/2019-06-04-automated-security-fixes/
   129  	mediaTypeRequiredAutomatedSecurityFixesPreview = "application/vnd.github.london-preview+json"
   130  
   131  	// https://developer.github.com/changes/2019-05-29-update-branch-api/
   132  	mediaTypeUpdatePullRequestBranchPreview = "application/vnd.github.lydian-preview+json"
   133  
   134  	// https://developer.github.com/changes/2019-04-11-pulls-branches-for-commit/
   135  	mediaTypeListPullsOrBranchesForCommitPreview = "application/vnd.github.groot-preview+json"
   136  
   137  	// https://docs.github.com/en/rest/previews/#repository-creation-permissions
   138  	mediaTypeMemberAllowedRepoCreationTypePreview = "application/vnd.github.surtur-preview+json"
   139  
   140  	// https://docs.github.com/en/rest/previews/#create-and-use-repository-templates
   141  	mediaTypeRepositoryTemplatePreview = "application/vnd.github.baptiste-preview+json"
   142  
   143  	// https://developer.github.com/changes/2019-10-03-multi-line-comments/
   144  	mediaTypeMultiLineCommentsPreview = "application/vnd.github.comfort-fade-preview+json"
   145  
   146  	// https://developer.github.com/changes/2019-11-05-deprecated-passwords-and-authorizations-api/
   147  	mediaTypeOAuthAppPreview = "application/vnd.github.doctor-strange-preview+json"
   148  
   149  	// https://developer.github.com/changes/2019-12-03-internal-visibility-changes/
   150  	mediaTypeRepositoryVisibilityPreview = "application/vnd.github.nebula-preview+json"
   151  
   152  	// https://developer.github.com/changes/2018-12-10-content-attachments-api/
   153  	mediaTypeContentAttachmentsPreview = "application/vnd.github.corsair-preview+json"
   154  )
   155  
   156  var errNonNilContext = errors.New("context must be non-nil")
   157  
   158  // A Client manages communication with the GitHub API.
   159  type Client struct {
   160  	clientMu sync.Mutex   // clientMu protects the client during calls that modify the CheckRedirect func.
   161  	client   *http.Client // HTTP client used to communicate with the API.
   162  
   163  	// Base URL for API requests. Defaults to the public GitHub API, but can be
   164  	// set to a domain endpoint to use with GitHub Enterprise. BaseURL should
   165  	// always be specified with a trailing slash.
   166  	BaseURL *url.URL
   167  
   168  	// Base URL for uploading files.
   169  	UploadURL *url.URL
   170  
   171  	// User agent used when communicating with the GitHub API.
   172  	UserAgent string
   173  
   174  	rateMu                  sync.Mutex
   175  	rateLimits              [categories]Rate // Rate limits for the client as determined by the most recent API calls.
   176  	secondaryRateLimitReset time.Time        // Secondary rate limit reset for the client as determined by the most recent API calls.
   177  
   178  	common service // Reuse a single struct instead of allocating one for each service on the heap.
   179  
   180  	// Services used for talking to different parts of the GitHub API.
   181  	Actions        *ActionsService
   182  	Activity       *ActivityService
   183  	Admin          *AdminService
   184  	Apps           *AppsService
   185  	Authorizations *AuthorizationsService
   186  	Billing        *BillingService
   187  	Checks         *ChecksService
   188  	CodeScanning   *CodeScanningService
   189  	Dependabot     *DependabotService
   190  	Enterprise     *EnterpriseService
   191  	Gists          *GistsService
   192  	Git            *GitService
   193  	Gitignores     *GitignoresService
   194  	Interactions   *InteractionsService
   195  	IssueImport    *IssueImportService
   196  	Issues         *IssuesService
   197  	Licenses       *LicensesService
   198  	Marketplace    *MarketplaceService
   199  	Migrations     *MigrationService
   200  	Organizations  *OrganizationsService
   201  	Projects       *ProjectsService
   202  	PullRequests   *PullRequestsService
   203  	Reactions      *ReactionsService
   204  	Repositories   *RepositoriesService
   205  	SCIM           *SCIMService
   206  	Search         *SearchService
   207  	SecretScanning *SecretScanningService
   208  	Teams          *TeamsService
   209  	Users          *UsersService
   210  }
   211  
   212  type service struct {
   213  	client *Client
   214  }
   215  
   216  // Client returns the http.Client used by this GitHub client.
   217  func (c *Client) Client() *http.Client {
   218  	c.clientMu.Lock()
   219  	defer c.clientMu.Unlock()
   220  	clientCopy := *c.client
   221  	return &clientCopy
   222  }
   223  
   224  // ListOptions specifies the optional parameters to various List methods that
   225  // support offset pagination.
   226  type ListOptions struct {
   227  	// For paginated result sets, page of results to retrieve.
   228  	Page int `url:"page,omitempty"`
   229  
   230  	// For paginated result sets, the number of results to include per page.
   231  	PerPage int `url:"per_page,omitempty"`
   232  }
   233  
   234  // ListCursorOptions specifies the optional parameters to various List methods that
   235  // support cursor pagination.
   236  type ListCursorOptions struct {
   237  	// For paginated result sets, page of results to retrieve.
   238  	Page string `url:"page,omitempty"`
   239  
   240  	// For paginated result sets, the number of results to include per page.
   241  	PerPage int `url:"per_page,omitempty"`
   242  
   243  	// For paginated result sets, the number of results per page (max 100), starting from the first matching result.
   244  	// This parameter must not be used in combination with last.
   245  	First int `url:"first,omitempty"`
   246  
   247  	// For paginated result sets, the number of results per page (max 100), starting from the last matching result.
   248  	// This parameter must not be used in combination with first.
   249  	Last int `url:"last,omitempty"`
   250  
   251  	// A cursor, as given in the Link header. If specified, the query only searches for events after this cursor.
   252  	After string `url:"after,omitempty"`
   253  
   254  	// A cursor, as given in the Link header. If specified, the query only searches for events before this cursor.
   255  	Before string `url:"before,omitempty"`
   256  
   257  	// A cursor, as given in the Link header. If specified, the query continues the search using this cursor.
   258  	Cursor string `url:"cursor,omitempty"`
   259  }
   260  
   261  // UploadOptions specifies the parameters to methods that support uploads.
   262  type UploadOptions struct {
   263  	Name      string `url:"name,omitempty"`
   264  	Label     string `url:"label,omitempty"`
   265  	MediaType string `url:"-"`
   266  }
   267  
   268  // RawType represents type of raw format of a request instead of JSON.
   269  type RawType uint8
   270  
   271  const (
   272  	// Diff format.
   273  	Diff RawType = 1 + iota
   274  	// Patch format.
   275  	Patch
   276  )
   277  
   278  // RawOptions specifies parameters when user wants to get raw format of
   279  // a response instead of JSON.
   280  type RawOptions struct {
   281  	Type RawType
   282  }
   283  
   284  // addOptions adds the parameters in opts as URL query parameters to s. opts
   285  // must be a struct whose fields may contain "url" tags.
   286  func addOptions(s string, opts interface{}) (string, error) {
   287  	v := reflect.ValueOf(opts)
   288  	if v.Kind() == reflect.Ptr && v.IsNil() {
   289  		return s, nil
   290  	}
   291  
   292  	u, err := url.Parse(s)
   293  	if err != nil {
   294  		return s, err
   295  	}
   296  
   297  	qs, err := query.Values(opts)
   298  	if err != nil {
   299  		return s, err
   300  	}
   301  
   302  	u.RawQuery = qs.Encode()
   303  	return u.String(), nil
   304  }
   305  
   306  // NewClient returns a new GitHub API client. If a nil httpClient is
   307  // provided, a new http.Client will be used. To use API methods which require
   308  // authentication, provide an http.Client that will perform the authentication
   309  // for you (such as that provided by the golang.org/x/oauth2 library).
   310  func NewClient(httpClient *http.Client) *Client {
   311  	if httpClient == nil {
   312  		httpClient = &http.Client{}
   313  	}
   314  	baseURL, _ := url.Parse(defaultBaseURL)
   315  	uploadURL, _ := url.Parse(uploadBaseURL)
   316  
   317  	c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: defaultUserAgent, UploadURL: uploadURL}
   318  	c.common.client = c
   319  	c.Actions = (*ActionsService)(&c.common)
   320  	c.Activity = (*ActivityService)(&c.common)
   321  	c.Admin = (*AdminService)(&c.common)
   322  	c.Apps = (*AppsService)(&c.common)
   323  	c.Authorizations = (*AuthorizationsService)(&c.common)
   324  	c.Billing = (*BillingService)(&c.common)
   325  	c.Checks = (*ChecksService)(&c.common)
   326  	c.CodeScanning = (*CodeScanningService)(&c.common)
   327  	c.Dependabot = (*DependabotService)(&c.common)
   328  	c.Enterprise = (*EnterpriseService)(&c.common)
   329  	c.Gists = (*GistsService)(&c.common)
   330  	c.Git = (*GitService)(&c.common)
   331  	c.Gitignores = (*GitignoresService)(&c.common)
   332  	c.Interactions = (*InteractionsService)(&c.common)
   333  	c.IssueImport = (*IssueImportService)(&c.common)
   334  	c.Issues = (*IssuesService)(&c.common)
   335  	c.Licenses = (*LicensesService)(&c.common)
   336  	c.Marketplace = &MarketplaceService{client: c}
   337  	c.Migrations = (*MigrationService)(&c.common)
   338  	c.Organizations = (*OrganizationsService)(&c.common)
   339  	c.Projects = (*ProjectsService)(&c.common)
   340  	c.PullRequests = (*PullRequestsService)(&c.common)
   341  	c.Reactions = (*ReactionsService)(&c.common)
   342  	c.Repositories = (*RepositoriesService)(&c.common)
   343  	c.SCIM = (*SCIMService)(&c.common)
   344  	c.Search = (*SearchService)(&c.common)
   345  	c.SecretScanning = (*SecretScanningService)(&c.common)
   346  	c.Teams = (*TeamsService)(&c.common)
   347  	c.Users = (*UsersService)(&c.common)
   348  	return c
   349  }
   350  
   351  // NewClientWithEnvProxy enhances NewClient with the HttpProxy env.
   352  func NewClientWithEnvProxy() *Client {
   353  	return NewClient(&http.Client{Transport: &http.Transport{Proxy: http.ProxyFromEnvironment}})
   354  }
   355  
   356  // NewTokenClient returns a new GitHub API client authenticated with the provided token.
   357  func NewTokenClient(ctx context.Context, token string) *Client {
   358  	return NewClient(oauth2.NewClient(ctx, oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})))
   359  }
   360  
   361  // NewEnterpriseClient returns a new GitHub API client with provided
   362  // base URL and upload URL (often is your GitHub Enterprise hostname).
   363  // If the base URL does not have the suffix "/api/v3/", it will be added automatically.
   364  // If the upload URL does not have the suffix "/api/uploads", it will be added automatically.
   365  // If a nil httpClient is provided, a new http.Client will be used.
   366  //
   367  // Note that NewEnterpriseClient is a convenience helper only;
   368  // its behavior is equivalent to using NewClient, followed by setting
   369  // the BaseURL and UploadURL fields.
   370  //
   371  // Another important thing is that by default, the GitHub Enterprise URL format
   372  // should be http(s)://[hostname]/api/v3/ or you will always receive the 406 status code.
   373  // The upload URL format should be http(s)://[hostname]/api/uploads/.
   374  func NewEnterpriseClient(baseURL, uploadURL string, httpClient *http.Client) (*Client, error) {
   375  	baseEndpoint, err := url.Parse(baseURL)
   376  	if err != nil {
   377  		return nil, err
   378  	}
   379  
   380  	if !strings.HasSuffix(baseEndpoint.Path, "/") {
   381  		baseEndpoint.Path += "/"
   382  	}
   383  	if !strings.HasSuffix(baseEndpoint.Path, "/api/v3/") &&
   384  		!strings.HasPrefix(baseEndpoint.Host, "api.") &&
   385  		!strings.Contains(baseEndpoint.Host, ".api.") {
   386  		baseEndpoint.Path += "api/v3/"
   387  	}
   388  
   389  	uploadEndpoint, err := url.Parse(uploadURL)
   390  	if err != nil {
   391  		return nil, err
   392  	}
   393  
   394  	if !strings.HasSuffix(uploadEndpoint.Path, "/") {
   395  		uploadEndpoint.Path += "/"
   396  	}
   397  	if !strings.HasSuffix(uploadEndpoint.Path, "/api/uploads/") &&
   398  		!strings.HasPrefix(uploadEndpoint.Host, "api.") &&
   399  		!strings.Contains(uploadEndpoint.Host, ".api.") {
   400  		uploadEndpoint.Path += "api/uploads/"
   401  	}
   402  
   403  	c := NewClient(httpClient)
   404  	c.BaseURL = baseEndpoint
   405  	c.UploadURL = uploadEndpoint
   406  	return c, nil
   407  }
   408  
   409  // RequestOption represents an option that can modify an http.Request.
   410  type RequestOption func(req *http.Request)
   411  
   412  // WithVersion overrides the GitHub v3 API version for this individual request.
   413  // For more information, see:
   414  // https://github.blog/2022-11-28-to-infinity-and-beyond-enabling-the-future-of-githubs-rest-api-with-api-versioning/
   415  func WithVersion(version string) RequestOption {
   416  	return func(req *http.Request) {
   417  		req.Header.Set(headerAPIVersion, version)
   418  	}
   419  }
   420  
   421  // NewRequest creates an API request. A relative URL can be provided in urlStr,
   422  // in which case it is resolved relative to the BaseURL of the Client.
   423  // Relative URLs should always be specified without a preceding slash. If
   424  // specified, the value pointed to by body is JSON encoded and included as the
   425  // request body.
   426  func (c *Client) NewRequest(method, urlStr string, body interface{}, opts ...RequestOption) (*http.Request, error) {
   427  	if !strings.HasSuffix(c.BaseURL.Path, "/") {
   428  		return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL)
   429  	}
   430  
   431  	u, err := c.BaseURL.Parse(urlStr)
   432  	if err != nil {
   433  		return nil, err
   434  	}
   435  
   436  	var buf io.ReadWriter
   437  	if body != nil {
   438  		buf = &bytes.Buffer{}
   439  		enc := json.NewEncoder(buf)
   440  		enc.SetEscapeHTML(false)
   441  		err := enc.Encode(body)
   442  		if err != nil {
   443  			return nil, err
   444  		}
   445  	}
   446  
   447  	req, err := http.NewRequest(method, u.String(), buf)
   448  	if err != nil {
   449  		return nil, err
   450  	}
   451  
   452  	if body != nil {
   453  		req.Header.Set("Content-Type", "application/json")
   454  	}
   455  	req.Header.Set("Accept", mediaTypeV3)
   456  	if c.UserAgent != "" {
   457  		req.Header.Set("User-Agent", c.UserAgent)
   458  	}
   459  	req.Header.Set(headerAPIVersion, defaultAPIVersion)
   460  
   461  	for _, opt := range opts {
   462  		opt(req)
   463  	}
   464  
   465  	return req, nil
   466  }
   467  
   468  // NewFormRequest creates an API request. A relative URL can be provided in urlStr,
   469  // in which case it is resolved relative to the BaseURL of the Client.
   470  // Relative URLs should always be specified without a preceding slash.
   471  // Body is sent with Content-Type: application/x-www-form-urlencoded.
   472  func (c *Client) NewFormRequest(urlStr string, body io.Reader, opts ...RequestOption) (*http.Request, error) {
   473  	if !strings.HasSuffix(c.BaseURL.Path, "/") {
   474  		return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL)
   475  	}
   476  
   477  	u, err := c.BaseURL.Parse(urlStr)
   478  	if err != nil {
   479  		return nil, err
   480  	}
   481  
   482  	req, err := http.NewRequest(http.MethodPost, u.String(), body)
   483  	if err != nil {
   484  		return nil, err
   485  	}
   486  
   487  	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
   488  	req.Header.Set("Accept", mediaTypeV3)
   489  	if c.UserAgent != "" {
   490  		req.Header.Set("User-Agent", c.UserAgent)
   491  	}
   492  	req.Header.Set(headerAPIVersion, defaultAPIVersion)
   493  
   494  	for _, opt := range opts {
   495  		opt(req)
   496  	}
   497  
   498  	return req, nil
   499  }
   500  
   501  // NewUploadRequest creates an upload request. A relative URL can be provided in
   502  // urlStr, in which case it is resolved relative to the UploadURL of the Client.
   503  // Relative URLs should always be specified without a preceding slash.
   504  func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, size int64, mediaType string, opts ...RequestOption) (*http.Request, error) {
   505  	if !strings.HasSuffix(c.UploadURL.Path, "/") {
   506  		return nil, fmt.Errorf("UploadURL must have a trailing slash, but %q does not", c.UploadURL)
   507  	}
   508  	u, err := c.UploadURL.Parse(urlStr)
   509  	if err != nil {
   510  		return nil, err
   511  	}
   512  
   513  	req, err := http.NewRequest("POST", u.String(), reader)
   514  	if err != nil {
   515  		return nil, err
   516  	}
   517  
   518  	req.ContentLength = size
   519  
   520  	if mediaType == "" {
   521  		mediaType = defaultMediaType
   522  	}
   523  	req.Header.Set("Content-Type", mediaType)
   524  	req.Header.Set("Accept", mediaTypeV3)
   525  	req.Header.Set("User-Agent", c.UserAgent)
   526  	req.Header.Set(headerAPIVersion, defaultAPIVersion)
   527  
   528  	for _, opt := range opts {
   529  		opt(req)
   530  	}
   531  
   532  	return req, nil
   533  }
   534  
   535  // Response is a GitHub API response. This wraps the standard http.Response
   536  // returned from GitHub and provides convenient access to things like
   537  // pagination links.
   538  type Response struct {
   539  	*http.Response
   540  
   541  	// These fields provide the page values for paginating through a set of
   542  	// results. Any or all of these may be set to the zero value for
   543  	// responses that are not part of a paginated set, or for which there
   544  	// are no additional pages.
   545  	//
   546  	// These fields support what is called "offset pagination" and should
   547  	// be used with the ListOptions struct.
   548  	NextPage  int
   549  	PrevPage  int
   550  	FirstPage int
   551  	LastPage  int
   552  
   553  	// Additionally, some APIs support "cursor pagination" instead of offset.
   554  	// This means that a token points directly to the next record which
   555  	// can lead to O(1) performance compared to O(n) performance provided
   556  	// by offset pagination.
   557  	//
   558  	// For APIs that support cursor pagination (such as
   559  	// TeamsService.ListIDPGroupsInOrganization), the following field
   560  	// will be populated to point to the next page.
   561  	//
   562  	// To use this token, set ListCursorOptions.Page to this value before
   563  	// calling the endpoint again.
   564  	NextPageToken string
   565  
   566  	// For APIs that support cursor pagination, such as RepositoriesService.ListHookDeliveries,
   567  	// the following field will be populated to point to the next page.
   568  	// Set ListCursorOptions.Cursor to this value when calling the endpoint again.
   569  	Cursor string
   570  
   571  	// For APIs that support before/after pagination, such as OrganizationsService.AuditLog.
   572  	Before string
   573  	After  string
   574  
   575  	// Explicitly specify the Rate type so Rate's String() receiver doesn't
   576  	// propagate to Response.
   577  	Rate Rate
   578  
   579  	// token's expiration date. Timestamp is 0001-01-01 when token doesn't expire.
   580  	// So it is valid for TokenExpiration.Equal(Timestamp{}) or TokenExpiration.Time.After(time.Now())
   581  	TokenExpiration Timestamp
   582  }
   583  
   584  // newResponse creates a new Response for the provided http.Response.
   585  // r must not be nil.
   586  func newResponse(r *http.Response) *Response {
   587  	response := &Response{Response: r}
   588  	response.populatePageValues()
   589  	response.Rate = parseRate(r)
   590  	response.TokenExpiration = parseTokenExpiration(r)
   591  	return response
   592  }
   593  
   594  // populatePageValues parses the HTTP Link response headers and populates the
   595  // various pagination link values in the Response.
   596  func (r *Response) populatePageValues() {
   597  	if links, ok := r.Response.Header["Link"]; ok && len(links) > 0 {
   598  		for _, link := range strings.Split(links[0], ",") {
   599  			segments := strings.Split(strings.TrimSpace(link), ";")
   600  
   601  			// link must at least have href and rel
   602  			if len(segments) < 2 {
   603  				continue
   604  			}
   605  
   606  			// ensure href is properly formatted
   607  			if !strings.HasPrefix(segments[0], "<") || !strings.HasSuffix(segments[0], ">") {
   608  				continue
   609  			}
   610  
   611  			// try to pull out page parameter
   612  			url, err := url.Parse(segments[0][1 : len(segments[0])-1])
   613  			if err != nil {
   614  				continue
   615  			}
   616  
   617  			q := url.Query()
   618  
   619  			if cursor := q.Get("cursor"); cursor != "" {
   620  				for _, segment := range segments[1:] {
   621  					switch strings.TrimSpace(segment) {
   622  					case `rel="next"`:
   623  						r.Cursor = cursor
   624  					}
   625  				}
   626  
   627  				continue
   628  			}
   629  
   630  			page := q.Get("page")
   631  			since := q.Get("since")
   632  			before := q.Get("before")
   633  			after := q.Get("after")
   634  
   635  			if page == "" && before == "" && after == "" && since == "" {
   636  				continue
   637  			}
   638  
   639  			if since != "" && page == "" {
   640  				page = since
   641  			}
   642  
   643  			for _, segment := range segments[1:] {
   644  				switch strings.TrimSpace(segment) {
   645  				case `rel="next"`:
   646  					if r.NextPage, err = strconv.Atoi(page); err != nil {
   647  						r.NextPageToken = page
   648  					}
   649  					r.After = after
   650  				case `rel="prev"`:
   651  					r.PrevPage, _ = strconv.Atoi(page)
   652  					r.Before = before
   653  				case `rel="first"`:
   654  					r.FirstPage, _ = strconv.Atoi(page)
   655  				case `rel="last"`:
   656  					r.LastPage, _ = strconv.Atoi(page)
   657  				}
   658  			}
   659  		}
   660  	}
   661  }
   662  
   663  // parseRate parses the rate related headers.
   664  func parseRate(r *http.Response) Rate {
   665  	var rate Rate
   666  	if limit := r.Header.Get(headerRateLimit); limit != "" {
   667  		rate.Limit, _ = strconv.Atoi(limit)
   668  	}
   669  	if remaining := r.Header.Get(headerRateRemaining); remaining != "" {
   670  		rate.Remaining, _ = strconv.Atoi(remaining)
   671  	}
   672  	if reset := r.Header.Get(headerRateReset); reset != "" {
   673  		if v, _ := strconv.ParseInt(reset, 10, 64); v != 0 {
   674  			rate.Reset = Timestamp{time.Unix(v, 0)}
   675  		}
   676  	}
   677  	return rate
   678  }
   679  
   680  // parseTokenExpiration parses the TokenExpiration related headers.
   681  // Returns 0001-01-01 if the header is not defined or could not be parsed.
   682  func parseTokenExpiration(r *http.Response) Timestamp {
   683  	if v := r.Header.Get(headerTokenExpiration); v != "" {
   684  		if t, err := time.Parse("2006-01-02 15:04:05 MST", v); err == nil {
   685  			return Timestamp{t.Local()}
   686  		}
   687  		// Some tokens include the timezone offset instead of the timezone.
   688  		// https://github.com/google/go-github/issues/2649
   689  		if t, err := time.Parse("2006-01-02 15:04:05 -0700", v); err == nil {
   690  			return Timestamp{t.Local()}
   691  		}
   692  	}
   693  	return Timestamp{} // 0001-01-01 00:00:00
   694  }
   695  
   696  type requestContext uint8
   697  
   698  const (
   699  	bypassRateLimitCheck requestContext = iota
   700  )
   701  
   702  // BareDo sends an API request and lets you handle the api response. If an error
   703  // or API Error occurs, the error will contain more information. Otherwise you
   704  // are supposed to read and close the response's Body. If rate limit is exceeded
   705  // and reset time is in the future, BareDo returns *RateLimitError immediately
   706  // without making a network API call.
   707  //
   708  // The provided ctx must be non-nil, if it is nil an error is returned. If it is
   709  // canceled or times out, ctx.Err() will be returned.
   710  func (c *Client) BareDo(ctx context.Context, req *http.Request) (*Response, error) {
   711  	if ctx == nil {
   712  		return nil, errNonNilContext
   713  	}
   714  
   715  	req = withContext(ctx, req)
   716  
   717  	rateLimitCategory := category(req.Method, req.URL.Path)
   718  
   719  	if bypass := ctx.Value(bypassRateLimitCheck); bypass == nil {
   720  		// If we've hit rate limit, don't make further requests before Reset time.
   721  		if err := c.checkRateLimitBeforeDo(req, rateLimitCategory); err != nil {
   722  			return &Response{
   723  				Response: err.Response,
   724  				Rate:     err.Rate,
   725  			}, err
   726  		}
   727  		// If we've hit a secondary rate limit, don't make further requests before Retry After.
   728  		if err := c.checkSecondaryRateLimitBeforeDo(ctx, req); err != nil {
   729  			return &Response{
   730  				Response: err.Response,
   731  			}, err
   732  		}
   733  	}
   734  
   735  	resp, err := c.client.Do(req)
   736  	if err != nil {
   737  		// If we got an error, and the context has been canceled,
   738  		// the context's error is probably more useful.
   739  		select {
   740  		case <-ctx.Done():
   741  			return nil, ctx.Err()
   742  		default:
   743  		}
   744  
   745  		// If the error type is *url.Error, sanitize its URL before returning.
   746  		if e, ok := err.(*url.Error); ok {
   747  			if url, err := url.Parse(e.URL); err == nil {
   748  				e.URL = sanitizeURL(url).String()
   749  				return nil, e
   750  			}
   751  		}
   752  
   753  		return nil, err
   754  	}
   755  
   756  	response := newResponse(resp)
   757  
   758  	// Don't update the rate limits if this was a cached response.
   759  	// X-From-Cache is set by https://github.com/gregjones/httpcache
   760  	if response.Header.Get("X-From-Cache") == "" {
   761  		c.rateMu.Lock()
   762  		c.rateLimits[rateLimitCategory] = response.Rate
   763  		c.rateMu.Unlock()
   764  	}
   765  
   766  	err = CheckResponse(resp)
   767  	if err != nil {
   768  		defer resp.Body.Close()
   769  		// Special case for AcceptedErrors. If an AcceptedError
   770  		// has been encountered, the response's payload will be
   771  		// added to the AcceptedError and returned.
   772  		//
   773  		// Issue #1022
   774  		aerr, ok := err.(*AcceptedError)
   775  		if ok {
   776  			b, readErr := io.ReadAll(resp.Body)
   777  			if readErr != nil {
   778  				return response, readErr
   779  			}
   780  
   781  			aerr.Raw = b
   782  			err = aerr
   783  		}
   784  
   785  		// Update the secondary rate limit if we hit it.
   786  		rerr, ok := err.(*AbuseRateLimitError)
   787  		if ok && rerr.RetryAfter != nil {
   788  			c.rateMu.Lock()
   789  			c.secondaryRateLimitReset = time.Now().Add(*rerr.RetryAfter)
   790  			c.rateMu.Unlock()
   791  		}
   792  	}
   793  	return response, err
   794  }
   795  
   796  // Do sends an API request and returns the API response. The API response is
   797  // JSON decoded and stored in the value pointed to by v, or returned as an
   798  // error if an API error has occurred. If v implements the io.Writer interface,
   799  // the raw response body will be written to v, without attempting to first
   800  // decode it. If v is nil, and no error hapens, the response is returned as is.
   801  // If rate limit is exceeded and reset time is in the future, Do returns
   802  // *RateLimitError immediately without making a network API call.
   803  //
   804  // The provided ctx must be non-nil, if it is nil an error is returned. If it
   805  // is canceled or times out, ctx.Err() will be returned.
   806  func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
   807  	resp, err := c.BareDo(ctx, req)
   808  	if err != nil {
   809  		return resp, err
   810  	}
   811  	defer resp.Body.Close()
   812  
   813  	switch v := v.(type) {
   814  	case nil:
   815  	case io.Writer:
   816  		_, err = io.Copy(v, resp.Body)
   817  	default:
   818  		decErr := json.NewDecoder(resp.Body).Decode(v)
   819  		if decErr == io.EOF {
   820  			decErr = nil // ignore EOF errors caused by empty response body
   821  		}
   822  		if decErr != nil {
   823  			err = decErr
   824  		}
   825  	}
   826  	return resp, err
   827  }
   828  
   829  // checkRateLimitBeforeDo does not make any network calls, but uses existing knowledge from
   830  // current client state in order to quickly check if *RateLimitError can be immediately returned
   831  // from Client.Do, and if so, returns it so that Client.Do can skip making a network API call unnecessarily.
   832  // Otherwise it returns nil, and Client.Do should proceed normally.
   833  func (c *Client) checkRateLimitBeforeDo(req *http.Request, rateLimitCategory rateLimitCategory) *RateLimitError {
   834  	c.rateMu.Lock()
   835  	rate := c.rateLimits[rateLimitCategory]
   836  	c.rateMu.Unlock()
   837  	if !rate.Reset.Time.IsZero() && rate.Remaining == 0 && time.Now().Before(rate.Reset.Time) {
   838  		// Create a fake response.
   839  		resp := &http.Response{
   840  			Status:     http.StatusText(http.StatusForbidden),
   841  			StatusCode: http.StatusForbidden,
   842  			Request:    req,
   843  			Header:     make(http.Header),
   844  			Body:       io.NopCloser(strings.NewReader("")),
   845  		}
   846  		return &RateLimitError{
   847  			Rate:     rate,
   848  			Response: resp,
   849  			Message:  fmt.Sprintf("API rate limit of %v still exceeded until %v, not making remote request.", rate.Limit, rate.Reset.Time),
   850  		}
   851  	}
   852  
   853  	return nil
   854  }
   855  
   856  // checkSecondaryRateLimitBeforeDo does not make any network calls, but uses existing knowledge from
   857  // current client state in order to quickly check if *AbuseRateLimitError can be immediately returned
   858  // from Client.Do, and if so, returns it so that Client.Do can skip making a network API call unnecessarily.
   859  // Otherwise it returns nil, and Client.Do should proceed normally.
   860  func (c *Client) checkSecondaryRateLimitBeforeDo(ctx context.Context, req *http.Request) *AbuseRateLimitError {
   861  	c.rateMu.Lock()
   862  	secondary := c.secondaryRateLimitReset
   863  	c.rateMu.Unlock()
   864  	if !secondary.IsZero() && time.Now().Before(secondary) {
   865  		// Create a fake response.
   866  		resp := &http.Response{
   867  			Status:     http.StatusText(http.StatusForbidden),
   868  			StatusCode: http.StatusForbidden,
   869  			Request:    req,
   870  			Header:     make(http.Header),
   871  			Body:       io.NopCloser(strings.NewReader("")),
   872  		}
   873  
   874  		retryAfter := time.Until(secondary)
   875  		return &AbuseRateLimitError{
   876  			Response:   resp,
   877  			Message:    fmt.Sprintf("API secondary rate limit exceeded until %v, not making remote request.", secondary),
   878  			RetryAfter: &retryAfter,
   879  		}
   880  	}
   881  
   882  	return nil
   883  }
   884  
   885  // compareHTTPResponse returns whether two http.Response objects are equal or not.
   886  // Currently, only StatusCode is checked. This function is used when implementing the
   887  // Is(error) bool interface for the custom error types in this package.
   888  func compareHTTPResponse(r1, r2 *http.Response) bool {
   889  	if r1 == nil && r2 == nil {
   890  		return true
   891  	}
   892  
   893  	if r1 != nil && r2 != nil {
   894  		return r1.StatusCode == r2.StatusCode
   895  	}
   896  	return false
   897  }
   898  
   899  /*
   900  An ErrorResponse reports one or more errors caused by an API request.
   901  
   902  GitHub API docs: https://docs.github.com/en/rest/#client-errors
   903  */
   904  type ErrorResponse struct {
   905  	Response *http.Response `json:"-"`       // HTTP response that caused this error
   906  	Message  string         `json:"message"` // error message
   907  	Errors   []Error        `json:"errors"`  // more detail on individual errors
   908  	// Block is only populated on certain types of errors such as code 451.
   909  	Block *ErrorBlock `json:"block,omitempty"`
   910  	// Most errors will also include a documentation_url field pointing
   911  	// to some content that might help you resolve the error, see
   912  	// https://docs.github.com/en/rest/#client-errors
   913  	DocumentationURL string `json:"documentation_url,omitempty"`
   914  }
   915  
   916  // ErrorBlock contains a further explanation for the reason of an error.
   917  // See https://developer.github.com/changes/2016-03-17-the-451-status-code-is-now-supported/
   918  // for more information.
   919  type ErrorBlock struct {
   920  	Reason    string     `json:"reason,omitempty"`
   921  	CreatedAt *Timestamp `json:"created_at,omitempty"`
   922  }
   923  
   924  func (r *ErrorResponse) Error() string {
   925  	return fmt.Sprintf("%v %v: %d %v %+v",
   926  		r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
   927  		r.Response.StatusCode, r.Message, r.Errors)
   928  }
   929  
   930  // Is returns whether the provided error equals this error.
   931  func (r *ErrorResponse) Is(target error) bool {
   932  	v, ok := target.(*ErrorResponse)
   933  	if !ok {
   934  		return false
   935  	}
   936  
   937  	if r.Message != v.Message || (r.DocumentationURL != v.DocumentationURL) ||
   938  		!compareHTTPResponse(r.Response, v.Response) {
   939  		return false
   940  	}
   941  
   942  	// Compare Errors.
   943  	if len(r.Errors) != len(v.Errors) {
   944  		return false
   945  	}
   946  	for idx := range r.Errors {
   947  		if r.Errors[idx] != v.Errors[idx] {
   948  			return false
   949  		}
   950  	}
   951  
   952  	// Compare Block.
   953  	if (r.Block != nil && v.Block == nil) || (r.Block == nil && v.Block != nil) {
   954  		return false
   955  	}
   956  	if r.Block != nil && v.Block != nil {
   957  		if r.Block.Reason != v.Block.Reason {
   958  			return false
   959  		}
   960  		if (r.Block.CreatedAt != nil && v.Block.CreatedAt == nil) || (r.Block.CreatedAt ==
   961  			nil && v.Block.CreatedAt != nil) {
   962  			return false
   963  		}
   964  		if r.Block.CreatedAt != nil && v.Block.CreatedAt != nil {
   965  			if *(r.Block.CreatedAt) != *(v.Block.CreatedAt) {
   966  				return false
   967  			}
   968  		}
   969  	}
   970  
   971  	return true
   972  }
   973  
   974  // TwoFactorAuthError occurs when using HTTP Basic Authentication for a user
   975  // that has two-factor authentication enabled. The request can be reattempted
   976  // by providing a one-time password in the request.
   977  type TwoFactorAuthError ErrorResponse
   978  
   979  func (r *TwoFactorAuthError) Error() string { return (*ErrorResponse)(r).Error() }
   980  
   981  // RateLimitError occurs when GitHub returns 403 Forbidden response with a rate limit
   982  // remaining value of 0.
   983  type RateLimitError struct {
   984  	Rate     Rate           // Rate specifies last known rate limit for the client
   985  	Response *http.Response // HTTP response that caused this error
   986  	Message  string         `json:"message"` // error message
   987  }
   988  
   989  func (r *RateLimitError) Error() string {
   990  	return fmt.Sprintf("%v %v: %d %v %v",
   991  		r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
   992  		r.Response.StatusCode, r.Message, formatRateReset(time.Until(r.Rate.Reset.Time)))
   993  }
   994  
   995  // Is returns whether the provided error equals this error.
   996  func (r *RateLimitError) Is(target error) bool {
   997  	v, ok := target.(*RateLimitError)
   998  	if !ok {
   999  		return false
  1000  	}
  1001  
  1002  	return r.Rate == v.Rate &&
  1003  		r.Message == v.Message &&
  1004  		compareHTTPResponse(r.Response, v.Response)
  1005  }
  1006  
  1007  // AcceptedError occurs when GitHub returns 202 Accepted response with an
  1008  // empty body, which means a job was scheduled on the GitHub side to process
  1009  // the information needed and cache it.
  1010  // Technically, 202 Accepted is not a real error, it's just used to
  1011  // indicate that results are not ready yet, but should be available soon.
  1012  // The request can be repeated after some time.
  1013  type AcceptedError struct {
  1014  	// Raw contains the response body.
  1015  	Raw []byte
  1016  }
  1017  
  1018  func (*AcceptedError) Error() string {
  1019  	return "job scheduled on GitHub side; try again later"
  1020  }
  1021  
  1022  // Is returns whether the provided error equals this error.
  1023  func (ae *AcceptedError) Is(target error) bool {
  1024  	v, ok := target.(*AcceptedError)
  1025  	if !ok {
  1026  		return false
  1027  	}
  1028  	return bytes.Compare(ae.Raw, v.Raw) == 0
  1029  }
  1030  
  1031  // AbuseRateLimitError occurs when GitHub returns 403 Forbidden response with the
  1032  // "documentation_url" field value equal to "https://docs.github.com/en/rest/overview/resources-in-the-rest-api#secondary-rate-limits".
  1033  type AbuseRateLimitError struct {
  1034  	Response *http.Response // HTTP response that caused this error
  1035  	Message  string         `json:"message"` // error message
  1036  
  1037  	// RetryAfter is provided with some abuse rate limit errors. If present,
  1038  	// it is the amount of time that the client should wait before retrying.
  1039  	// Otherwise, the client should try again later (after an unspecified amount of time).
  1040  	RetryAfter *time.Duration
  1041  }
  1042  
  1043  func (r *AbuseRateLimitError) Error() string {
  1044  	return fmt.Sprintf("%v %v: %d %v",
  1045  		r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
  1046  		r.Response.StatusCode, r.Message)
  1047  }
  1048  
  1049  // Is returns whether the provided error equals this error.
  1050  func (r *AbuseRateLimitError) Is(target error) bool {
  1051  	v, ok := target.(*AbuseRateLimitError)
  1052  	if !ok {
  1053  		return false
  1054  	}
  1055  
  1056  	return r.Message == v.Message &&
  1057  		r.RetryAfter == v.RetryAfter &&
  1058  		compareHTTPResponse(r.Response, v.Response)
  1059  }
  1060  
  1061  // sanitizeURL redacts the client_secret parameter from the URL which may be
  1062  // exposed to the user.
  1063  func sanitizeURL(uri *url.URL) *url.URL {
  1064  	if uri == nil {
  1065  		return nil
  1066  	}
  1067  	params := uri.Query()
  1068  	if len(params.Get("client_secret")) > 0 {
  1069  		params.Set("client_secret", "REDACTED")
  1070  		uri.RawQuery = params.Encode()
  1071  	}
  1072  	return uri
  1073  }
  1074  
  1075  /*
  1076  An Error reports more details on an individual error in an ErrorResponse.
  1077  These are the possible validation error codes:
  1078  
  1079  	missing:
  1080  	    resource does not exist
  1081  	missing_field:
  1082  	    a required field on a resource has not been set
  1083  	invalid:
  1084  	    the formatting of a field is invalid
  1085  	already_exists:
  1086  	    another resource has the same valid as this field
  1087  	custom:
  1088  	    some resources return this (e.g. github.User.CreateKey()), additional
  1089  	    information is set in the Message field of the Error
  1090  
  1091  GitHub error responses structure are often undocumented and inconsistent.
  1092  Sometimes error is just a simple string (Issue #540).
  1093  In such cases, Message represents an error message as a workaround.
  1094  
  1095  GitHub API docs: https://docs.github.com/en/rest/#client-errors
  1096  */
  1097  type Error struct {
  1098  	Resource string `json:"resource"` // resource on which the error occurred
  1099  	Field    string `json:"field"`    // field on which the error occurred
  1100  	Code     string `json:"code"`     // validation error code
  1101  	Message  string `json:"message"`  // Message describing the error. Errors with Code == "custom" will always have this set.
  1102  }
  1103  
  1104  func (e *Error) Error() string {
  1105  	return fmt.Sprintf("%v error caused by %v field on %v resource",
  1106  		e.Code, e.Field, e.Resource)
  1107  }
  1108  
  1109  func (e *Error) UnmarshalJSON(data []byte) error {
  1110  	type aliasError Error // avoid infinite recursion by using type alias.
  1111  	if err := json.Unmarshal(data, (*aliasError)(e)); err != nil {
  1112  		return json.Unmarshal(data, &e.Message) // data can be json string.
  1113  	}
  1114  	return nil
  1115  }
  1116  
  1117  // CheckResponse checks the API response for errors, and returns them if
  1118  // present. A response is considered an error if it has a status code outside
  1119  // the 200 range or equal to 202 Accepted.
  1120  // API error responses are expected to have response
  1121  // body, and a JSON response body that maps to ErrorResponse.
  1122  //
  1123  // The error type will be *RateLimitError for rate limit exceeded errors,
  1124  // *AcceptedError for 202 Accepted status codes,
  1125  // and *TwoFactorAuthError for two-factor authentication errors.
  1126  func CheckResponse(r *http.Response) error {
  1127  	if r.StatusCode == http.StatusAccepted {
  1128  		return &AcceptedError{}
  1129  	}
  1130  	if c := r.StatusCode; 200 <= c && c <= 299 {
  1131  		return nil
  1132  	}
  1133  
  1134  	errorResponse := &ErrorResponse{Response: r}
  1135  	data, err := io.ReadAll(r.Body)
  1136  	if err == nil && data != nil {
  1137  		json.Unmarshal(data, errorResponse)
  1138  	}
  1139  	// Re-populate error response body because GitHub error responses are often
  1140  	// undocumented and inconsistent.
  1141  	// Issue #1136, #540.
  1142  	r.Body = io.NopCloser(bytes.NewBuffer(data))
  1143  	switch {
  1144  	case r.StatusCode == http.StatusUnauthorized && strings.HasPrefix(r.Header.Get(headerOTP), "required"):
  1145  		return (*TwoFactorAuthError)(errorResponse)
  1146  	case r.StatusCode == http.StatusForbidden && r.Header.Get(headerRateRemaining) == "0":
  1147  		return &RateLimitError{
  1148  			Rate:     parseRate(r),
  1149  			Response: errorResponse.Response,
  1150  			Message:  errorResponse.Message,
  1151  		}
  1152  	case r.StatusCode == http.StatusForbidden &&
  1153  		(strings.HasSuffix(errorResponse.DocumentationURL, "#abuse-rate-limits") ||
  1154  			strings.HasSuffix(errorResponse.DocumentationURL, "#secondary-rate-limits")):
  1155  		abuseRateLimitError := &AbuseRateLimitError{
  1156  			Response: errorResponse.Response,
  1157  			Message:  errorResponse.Message,
  1158  		}
  1159  		if v := r.Header["Retry-After"]; len(v) > 0 {
  1160  			// According to GitHub support, the "Retry-After" header value will be
  1161  			// an integer which represents the number of seconds that one should
  1162  			// wait before resuming making requests.
  1163  			retryAfterSeconds, _ := strconv.ParseInt(v[0], 10, 64) // Error handling is noop.
  1164  			retryAfter := time.Duration(retryAfterSeconds) * time.Second
  1165  			abuseRateLimitError.RetryAfter = &retryAfter
  1166  		}
  1167  		return abuseRateLimitError
  1168  	default:
  1169  		return errorResponse
  1170  	}
  1171  }
  1172  
  1173  // parseBoolResponse determines the boolean result from a GitHub API response.
  1174  // Several GitHub API methods return boolean responses indicated by the HTTP
  1175  // status code in the response (true indicated by a 204, false indicated by a
  1176  // 404). This helper function will determine that result and hide the 404
  1177  // error if present. Any other error will be returned through as-is.
  1178  func parseBoolResponse(err error) (bool, error) {
  1179  	if err == nil {
  1180  		return true, nil
  1181  	}
  1182  
  1183  	if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode == http.StatusNotFound {
  1184  		// Simply false. In this one case, we do not pass the error through.
  1185  		return false, nil
  1186  	}
  1187  
  1188  	// some other real error occurred
  1189  	return false, err
  1190  }
  1191  
  1192  // Rate represents the rate limit for the current client.
  1193  type Rate struct {
  1194  	// The number of requests per hour the client is currently limited to.
  1195  	Limit int `json:"limit"`
  1196  
  1197  	// The number of remaining requests the client can make this hour.
  1198  	Remaining int `json:"remaining"`
  1199  
  1200  	// The time at which the current rate limit will reset.
  1201  	Reset Timestamp `json:"reset"`
  1202  }
  1203  
  1204  func (r Rate) String() string {
  1205  	return Stringify(r)
  1206  }
  1207  
  1208  // RateLimits represents the rate limits for the current client.
  1209  type RateLimits struct {
  1210  	// The rate limit for non-search API requests. Unauthenticated
  1211  	// requests are limited to 60 per hour. Authenticated requests are
  1212  	// limited to 5,000 per hour.
  1213  	//
  1214  	// GitHub API docs: https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting
  1215  	Core *Rate `json:"core"`
  1216  
  1217  	// The rate limit for search API requests. Unauthenticated requests
  1218  	// are limited to 10 requests per minutes. Authenticated requests are
  1219  	// limited to 30 per minute.
  1220  	//
  1221  	// GitHub API docs: https://docs.github.com/en/rest/search#rate-limit
  1222  	Search *Rate `json:"search"`
  1223  
  1224  	// GitHub API docs: https://docs.github.com/en/graphql/overview/resource-limitations#rate-limit
  1225  	GraphQL *Rate `json:"graphql"`
  1226  
  1227  	// GitHub API dos: https://docs.github.com/en/rest/rate-limit
  1228  	IntegrationManifest *Rate `json:"integration_manifest"`
  1229  
  1230  	SourceImport              *Rate `json:"source_import"`
  1231  	CodeScanningUpload        *Rate `json:"code_scanning_upload"`
  1232  	ActionsRunnerRegistration *Rate `json:"actions_runner_registration"`
  1233  	SCIM                      *Rate `json:"scim"`
  1234  }
  1235  
  1236  func (r RateLimits) String() string {
  1237  	return Stringify(r)
  1238  }
  1239  
  1240  type rateLimitCategory uint8
  1241  
  1242  const (
  1243  	coreCategory rateLimitCategory = iota
  1244  	searchCategory
  1245  	graphqlCategory
  1246  	integrationManifestCategory
  1247  	sourceImportCategory
  1248  	codeScanningUploadCategory
  1249  	actionsRunnerRegistrationCategory
  1250  	scimCategory
  1251  
  1252  	categories // An array of this length will be able to contain all rate limit categories.
  1253  )
  1254  
  1255  // category returns the rate limit category of the endpoint, determined by HTTP method and Request.URL.Path.
  1256  func category(method, path string) rateLimitCategory {
  1257  	switch {
  1258  	// https://docs.github.com/en/rest/rate-limit#about-rate-limits
  1259  	default:
  1260  		// NOTE: coreCategory is returned for actionsRunnerRegistrationCategory too,
  1261  		// because no API found for this category.
  1262  		return coreCategory
  1263  	case strings.HasPrefix(path, "/search/"):
  1264  		return searchCategory
  1265  	case path == "/graphql":
  1266  		return graphqlCategory
  1267  	case strings.HasPrefix(path, "/app-manifests/") &&
  1268  		strings.HasSuffix(path, "/conversions") &&
  1269  		method == http.MethodPost:
  1270  		return integrationManifestCategory
  1271  
  1272  	// https://docs.github.com/en/rest/migrations/source-imports#start-an-import
  1273  	case strings.HasPrefix(path, "/repos/") &&
  1274  		strings.HasSuffix(path, "/import") &&
  1275  		method == http.MethodPut:
  1276  		return sourceImportCategory
  1277  
  1278  	// https://docs.github.com/en/rest/code-scanning#upload-an-analysis-as-sarif-data
  1279  	case strings.HasSuffix(path, "/code-scanning/sarifs"):
  1280  		return codeScanningUploadCategory
  1281  
  1282  	// https://docs.github.com/en/enterprise-cloud@latest/rest/scim
  1283  	case strings.HasPrefix(path, "/scim/"):
  1284  		return scimCategory
  1285  	}
  1286  }
  1287  
  1288  // RateLimits returns the rate limits for the current client.
  1289  func (c *Client) RateLimits(ctx context.Context) (*RateLimits, *Response, error) {
  1290  	req, err := c.NewRequest("GET", "rate_limit", nil)
  1291  	if err != nil {
  1292  		return nil, nil, err
  1293  	}
  1294  
  1295  	response := new(struct {
  1296  		Resources *RateLimits `json:"resources"`
  1297  	})
  1298  
  1299  	// This resource is not subject to rate limits.
  1300  	ctx = context.WithValue(ctx, bypassRateLimitCheck, true)
  1301  	resp, err := c.Do(ctx, req, response)
  1302  	if err != nil {
  1303  		return nil, resp, err
  1304  	}
  1305  
  1306  	if response.Resources != nil {
  1307  		c.rateMu.Lock()
  1308  		if response.Resources.Core != nil {
  1309  			c.rateLimits[coreCategory] = *response.Resources.Core
  1310  		}
  1311  		if response.Resources.Search != nil {
  1312  			c.rateLimits[searchCategory] = *response.Resources.Search
  1313  		}
  1314  		if response.Resources.GraphQL != nil {
  1315  			c.rateLimits[graphqlCategory] = *response.Resources.GraphQL
  1316  		}
  1317  		if response.Resources.IntegrationManifest != nil {
  1318  			c.rateLimits[integrationManifestCategory] = *response.Resources.IntegrationManifest
  1319  		}
  1320  		if response.Resources.SourceImport != nil {
  1321  			c.rateLimits[sourceImportCategory] = *response.Resources.SourceImport
  1322  		}
  1323  		if response.Resources.CodeScanningUpload != nil {
  1324  			c.rateLimits[codeScanningUploadCategory] = *response.Resources.CodeScanningUpload
  1325  		}
  1326  		if response.Resources.ActionsRunnerRegistration != nil {
  1327  			c.rateLimits[actionsRunnerRegistrationCategory] = *response.Resources.ActionsRunnerRegistration
  1328  		}
  1329  		if response.Resources.SCIM != nil {
  1330  			c.rateLimits[scimCategory] = *response.Resources.SCIM
  1331  		}
  1332  		c.rateMu.Unlock()
  1333  	}
  1334  
  1335  	return response.Resources, resp, nil
  1336  }
  1337  
  1338  func setCredentialsAsHeaders(req *http.Request, id, secret string) *http.Request {
  1339  	// To set extra headers, we must make a copy of the Request so
  1340  	// that we don't modify the Request we were given. This is required by the
  1341  	// specification of http.RoundTripper.
  1342  	//
  1343  	// Since we are going to modify only req.Header here, we only need a deep copy
  1344  	// of req.Header.
  1345  	convertedRequest := new(http.Request)
  1346  	*convertedRequest = *req
  1347  	convertedRequest.Header = make(http.Header, len(req.Header))
  1348  
  1349  	for k, s := range req.Header {
  1350  		convertedRequest.Header[k] = append([]string(nil), s...)
  1351  	}
  1352  	convertedRequest.SetBasicAuth(id, secret)
  1353  	return convertedRequest
  1354  }
  1355  
  1356  /*
  1357  UnauthenticatedRateLimitedTransport allows you to make unauthenticated calls
  1358  that need to use a higher rate limit associated with your OAuth application.
  1359  
  1360  	t := &github.UnauthenticatedRateLimitedTransport{
  1361  		ClientID:     "your app's client ID",
  1362  		ClientSecret: "your app's client secret",
  1363  	}
  1364  	client := github.NewClient(t.Client())
  1365  
  1366  This will add the client id and secret as a base64-encoded string in the format
  1367  ClientID:ClientSecret and apply it as an "Authorization": "Basic" header.
  1368  
  1369  See https://docs.github.com/en/rest/#unauthenticated-rate-limited-requests for
  1370  more information.
  1371  */
  1372  type UnauthenticatedRateLimitedTransport struct {
  1373  	// ClientID is the GitHub OAuth client ID of the current application, which
  1374  	// can be found by selecting its entry in the list at
  1375  	// https://github.com/settings/applications.
  1376  	ClientID string
  1377  
  1378  	// ClientSecret is the GitHub OAuth client secret of the current
  1379  	// application.
  1380  	ClientSecret string
  1381  
  1382  	// Transport is the underlying HTTP transport to use when making requests.
  1383  	// It will default to http.DefaultTransport if nil.
  1384  	Transport http.RoundTripper
  1385  }
  1386  
  1387  // RoundTrip implements the RoundTripper interface.
  1388  func (t *UnauthenticatedRateLimitedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
  1389  	if t.ClientID == "" {
  1390  		return nil, errors.New("t.ClientID is empty")
  1391  	}
  1392  	if t.ClientSecret == "" {
  1393  		return nil, errors.New("t.ClientSecret is empty")
  1394  	}
  1395  
  1396  	req2 := setCredentialsAsHeaders(req, t.ClientID, t.ClientSecret)
  1397  	// Make the HTTP request.
  1398  	return t.transport().RoundTrip(req2)
  1399  }
  1400  
  1401  // Client returns an *http.Client that makes requests which are subject to the
  1402  // rate limit of your OAuth application.
  1403  func (t *UnauthenticatedRateLimitedTransport) Client() *http.Client {
  1404  	return &http.Client{Transport: t}
  1405  }
  1406  
  1407  func (t *UnauthenticatedRateLimitedTransport) transport() http.RoundTripper {
  1408  	if t.Transport != nil {
  1409  		return t.Transport
  1410  	}
  1411  	return http.DefaultTransport
  1412  }
  1413  
  1414  // BasicAuthTransport is an http.RoundTripper that authenticates all requests
  1415  // using HTTP Basic Authentication with the provided username and password. It
  1416  // additionally supports users who have two-factor authentication enabled on
  1417  // their GitHub account.
  1418  type BasicAuthTransport struct {
  1419  	Username string // GitHub username
  1420  	Password string // GitHub password
  1421  	OTP      string // one-time password for users with two-factor auth enabled
  1422  
  1423  	// Transport is the underlying HTTP transport to use when making requests.
  1424  	// It will default to http.DefaultTransport if nil.
  1425  	Transport http.RoundTripper
  1426  }
  1427  
  1428  // RoundTrip implements the RoundTripper interface.
  1429  func (t *BasicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
  1430  	req2 := setCredentialsAsHeaders(req, t.Username, t.Password)
  1431  	if t.OTP != "" {
  1432  		req2.Header.Set(headerOTP, t.OTP)
  1433  	}
  1434  	return t.transport().RoundTrip(req2)
  1435  }
  1436  
  1437  // Client returns an *http.Client that makes requests that are authenticated
  1438  // using HTTP Basic Authentication.
  1439  func (t *BasicAuthTransport) Client() *http.Client {
  1440  	return &http.Client{Transport: t}
  1441  }
  1442  
  1443  func (t *BasicAuthTransport) transport() http.RoundTripper {
  1444  	if t.Transport != nil {
  1445  		return t.Transport
  1446  	}
  1447  	return http.DefaultTransport
  1448  }
  1449  
  1450  // formatRateReset formats d to look like "[rate reset in 2s]" or
  1451  // "[rate reset in 87m02s]" for the positive durations. And like "[rate limit was reset 87m02s ago]"
  1452  // for the negative cases.
  1453  func formatRateReset(d time.Duration) string {
  1454  	isNegative := d < 0
  1455  	if isNegative {
  1456  		d *= -1
  1457  	}
  1458  	secondsTotal := int(0.5 + d.Seconds())
  1459  	minutes := secondsTotal / 60
  1460  	seconds := secondsTotal - minutes*60
  1461  
  1462  	var timeString string
  1463  	if minutes > 0 {
  1464  		timeString = fmt.Sprintf("%dm%02ds", minutes, seconds)
  1465  	} else {
  1466  		timeString = fmt.Sprintf("%ds", seconds)
  1467  	}
  1468  
  1469  	if isNegative {
  1470  		return fmt.Sprintf("[rate limit was reset %v ago]", timeString)
  1471  	}
  1472  	return fmt.Sprintf("[rate reset in %v]", timeString)
  1473  }
  1474  
  1475  // When using roundTripWithOptionalFollowRedirect, note that it
  1476  // is the responsibility of the caller to close the response body.
  1477  func (c *Client) roundTripWithOptionalFollowRedirect(ctx context.Context, u string, followRedirects bool, opts ...RequestOption) (*http.Response, error) {
  1478  	req, err := c.NewRequest("GET", u, nil, opts...)
  1479  	if err != nil {
  1480  		return nil, err
  1481  	}
  1482  
  1483  	var resp *http.Response
  1484  	// Use http.DefaultTransport if no custom Transport is configured
  1485  	req = withContext(ctx, req)
  1486  	if c.client.Transport == nil {
  1487  		resp, err = http.DefaultTransport.RoundTrip(req)
  1488  	} else {
  1489  		resp, err = c.client.Transport.RoundTrip(req)
  1490  	}
  1491  	if err != nil {
  1492  		return nil, err
  1493  	}
  1494  
  1495  	// If redirect response is returned, follow it
  1496  	if followRedirects && resp.StatusCode == http.StatusMovedPermanently {
  1497  		resp.Body.Close()
  1498  		u = resp.Header.Get("Location")
  1499  		resp, err = c.roundTripWithOptionalFollowRedirect(ctx, u, false, opts...)
  1500  	}
  1501  	return resp, err
  1502  }
  1503  
  1504  // Bool is a helper routine that allocates a new bool value
  1505  // to store v and returns a pointer to it.
  1506  func Bool(v bool) *bool { return &v }
  1507  
  1508  // Int is a helper routine that allocates a new int value
  1509  // to store v and returns a pointer to it.
  1510  func Int(v int) *int { return &v }
  1511  
  1512  // Int64 is a helper routine that allocates a new int64 value
  1513  // to store v and returns a pointer to it.
  1514  func Int64(v int64) *int64 { return &v }
  1515  
  1516  // String is a helper routine that allocates a new string value
  1517  // to store v and returns a pointer to it.
  1518  func String(v string) *string { return &v }