...

Source file src/github.com/google/go-github/v55/github/github.go

Documentation: github.com/google/go-github/v55/github

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

View as plain text