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