github.com/google/go-github/v60@v60.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 = "v60.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  	// represting 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  )
   808  
   809  // BareDo sends an API request and lets you handle the api response. If an error
   810  // or API Error occurs, the error will contain more information. Otherwise you
   811  // are supposed to read and close the response's Body. If rate limit is exceeded
   812  // and reset time is in the future, BareDo returns *RateLimitError immediately
   813  // without making a network API call.
   814  //
   815  // The provided ctx must be non-nil, if it is nil an error is returned. If it is
   816  // canceled or times out, ctx.Err() will be returned.
   817  func (c *Client) BareDo(ctx context.Context, req *http.Request) (*Response, error) {
   818  	if ctx == nil {
   819  		return nil, errNonNilContext
   820  	}
   821  
   822  	req = withContext(ctx, req)
   823  
   824  	rateLimitCategory := category(req.Method, req.URL.Path)
   825  
   826  	if bypass := ctx.Value(bypassRateLimitCheck); bypass == nil {
   827  		// If we've hit rate limit, don't make further requests before Reset time.
   828  		if err := c.checkRateLimitBeforeDo(req, rateLimitCategory); err != nil {
   829  			return &Response{
   830  				Response: err.Response,
   831  				Rate:     err.Rate,
   832  			}, err
   833  		}
   834  		// If we've hit a secondary rate limit, don't make further requests before Retry After.
   835  		if err := c.checkSecondaryRateLimitBeforeDo(req); err != nil {
   836  			return &Response{
   837  				Response: err.Response,
   838  			}, err
   839  		}
   840  	}
   841  
   842  	resp, err := c.client.Do(req)
   843  	if err != nil {
   844  		// If we got an error, and the context has been canceled,
   845  		// the context's error is probably more useful.
   846  		select {
   847  		case <-ctx.Done():
   848  			return nil, ctx.Err()
   849  		default:
   850  		}
   851  
   852  		// If the error type is *url.Error, sanitize its URL before returning.
   853  		if e, ok := err.(*url.Error); ok {
   854  			if url, err := url.Parse(e.URL); err == nil {
   855  				e.URL = sanitizeURL(url).String()
   856  				return nil, e
   857  			}
   858  		}
   859  
   860  		return nil, err
   861  	}
   862  
   863  	response := newResponse(resp)
   864  
   865  	// Don't update the rate limits if this was a cached response.
   866  	// X-From-Cache is set by https://github.com/gregjones/httpcache
   867  	if response.Header.Get("X-From-Cache") == "" {
   868  		c.rateMu.Lock()
   869  		c.rateLimits[rateLimitCategory] = response.Rate
   870  		c.rateMu.Unlock()
   871  	}
   872  
   873  	err = CheckResponse(resp)
   874  	if err != nil {
   875  		defer resp.Body.Close()
   876  		// Special case for AcceptedErrors. If an AcceptedError
   877  		// has been encountered, the response's payload will be
   878  		// added to the AcceptedError and returned.
   879  		//
   880  		// Issue #1022
   881  		aerr, ok := err.(*AcceptedError)
   882  		if ok {
   883  			b, readErr := io.ReadAll(resp.Body)
   884  			if readErr != nil {
   885  				return response, readErr
   886  			}
   887  
   888  			aerr.Raw = b
   889  			err = aerr
   890  		}
   891  
   892  		// Update the secondary rate limit if we hit it.
   893  		rerr, ok := err.(*AbuseRateLimitError)
   894  		if ok && rerr.RetryAfter != nil {
   895  			c.rateMu.Lock()
   896  			c.secondaryRateLimitReset = time.Now().Add(*rerr.RetryAfter)
   897  			c.rateMu.Unlock()
   898  		}
   899  	}
   900  	return response, err
   901  }
   902  
   903  // Do sends an API request and returns the API response. The API response is
   904  // JSON decoded and stored in the value pointed to by v, or returned as an
   905  // error if an API error has occurred. If v implements the io.Writer interface,
   906  // the raw response body will be written to v, without attempting to first
   907  // decode it. If v is nil, and no error happens, the response is returned as is.
   908  // If rate limit is exceeded and reset time is in the future, Do returns
   909  // *RateLimitError immediately without making a network API call.
   910  //
   911  // The provided ctx must be non-nil, if it is nil an error is returned. If it
   912  // is canceled or times out, ctx.Err() will be returned.
   913  func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
   914  	resp, err := c.BareDo(ctx, req)
   915  	if err != nil {
   916  		return resp, err
   917  	}
   918  	defer resp.Body.Close()
   919  
   920  	switch v := v.(type) {
   921  	case nil:
   922  	case io.Writer:
   923  		_, err = io.Copy(v, resp.Body)
   924  	default:
   925  		decErr := json.NewDecoder(resp.Body).Decode(v)
   926  		if decErr == io.EOF {
   927  			decErr = nil // ignore EOF errors caused by empty response body
   928  		}
   929  		if decErr != nil {
   930  			err = decErr
   931  		}
   932  	}
   933  	return resp, err
   934  }
   935  
   936  // checkRateLimitBeforeDo does not make any network calls, but uses existing knowledge from
   937  // current client state in order to quickly check if *RateLimitError can be immediately returned
   938  // from Client.Do, and if so, returns it so that Client.Do can skip making a network API call unnecessarily.
   939  // Otherwise it returns nil, and Client.Do should proceed normally.
   940  func (c *Client) checkRateLimitBeforeDo(req *http.Request, rateLimitCategory rateLimitCategory) *RateLimitError {
   941  	c.rateMu.Lock()
   942  	rate := c.rateLimits[rateLimitCategory]
   943  	c.rateMu.Unlock()
   944  	if !rate.Reset.Time.IsZero() && rate.Remaining == 0 && time.Now().Before(rate.Reset.Time) {
   945  		// Create a fake response.
   946  		resp := &http.Response{
   947  			Status:     http.StatusText(http.StatusForbidden),
   948  			StatusCode: http.StatusForbidden,
   949  			Request:    req,
   950  			Header:     make(http.Header),
   951  			Body:       io.NopCloser(strings.NewReader("")),
   952  		}
   953  		return &RateLimitError{
   954  			Rate:     rate,
   955  			Response: resp,
   956  			Message:  fmt.Sprintf("API rate limit of %v still exceeded until %v, not making remote request.", rate.Limit, rate.Reset.Time),
   957  		}
   958  	}
   959  
   960  	return nil
   961  }
   962  
   963  // checkSecondaryRateLimitBeforeDo does not make any network calls, but uses existing knowledge from
   964  // current client state in order to quickly check if *AbuseRateLimitError can be immediately returned
   965  // from Client.Do, and if so, returns it so that Client.Do can skip making a network API call unnecessarily.
   966  // Otherwise it returns nil, and Client.Do should proceed normally.
   967  func (c *Client) checkSecondaryRateLimitBeforeDo(req *http.Request) *AbuseRateLimitError {
   968  	c.rateMu.Lock()
   969  	secondary := c.secondaryRateLimitReset
   970  	c.rateMu.Unlock()
   971  	if !secondary.IsZero() && time.Now().Before(secondary) {
   972  		// Create a fake response.
   973  		resp := &http.Response{
   974  			Status:     http.StatusText(http.StatusForbidden),
   975  			StatusCode: http.StatusForbidden,
   976  			Request:    req,
   977  			Header:     make(http.Header),
   978  			Body:       io.NopCloser(strings.NewReader("")),
   979  		}
   980  
   981  		retryAfter := time.Until(secondary)
   982  		return &AbuseRateLimitError{
   983  			Response:   resp,
   984  			Message:    fmt.Sprintf("API secondary rate limit exceeded until %v, not making remote request.", secondary),
   985  			RetryAfter: &retryAfter,
   986  		}
   987  	}
   988  
   989  	return nil
   990  }
   991  
   992  // compareHTTPResponse returns whether two http.Response objects are equal or not.
   993  // Currently, only StatusCode is checked. This function is used when implementing the
   994  // Is(error) bool interface for the custom error types in this package.
   995  func compareHTTPResponse(r1, r2 *http.Response) bool {
   996  	if r1 == nil && r2 == nil {
   997  		return true
   998  	}
   999  
  1000  	if r1 != nil && r2 != nil {
  1001  		return r1.StatusCode == r2.StatusCode
  1002  	}
  1003  	return false
  1004  }
  1005  
  1006  /*
  1007  An ErrorResponse reports one or more errors caused by an API request.
  1008  
  1009  GitHub API docs: https://docs.github.com/rest/#client-errors
  1010  */
  1011  type ErrorResponse struct {
  1012  	Response *http.Response `json:"-"`       // HTTP response that caused this error
  1013  	Message  string         `json:"message"` // error message
  1014  	Errors   []Error        `json:"errors"`  // more detail on individual errors
  1015  	// Block is only populated on certain types of errors such as code 451.
  1016  	Block *ErrorBlock `json:"block,omitempty"`
  1017  	// Most errors will also include a documentation_url field pointing
  1018  	// to some content that might help you resolve the error, see
  1019  	// https://docs.github.com/rest/#client-errors
  1020  	DocumentationURL string `json:"documentation_url,omitempty"`
  1021  }
  1022  
  1023  // ErrorBlock contains a further explanation for the reason of an error.
  1024  // See https://developer.github.com/changes/2016-03-17-the-451-status-code-is-now-supported/
  1025  // for more information.
  1026  type ErrorBlock struct {
  1027  	Reason    string     `json:"reason,omitempty"`
  1028  	CreatedAt *Timestamp `json:"created_at,omitempty"`
  1029  }
  1030  
  1031  func (r *ErrorResponse) Error() string {
  1032  	if r.Response != nil && r.Response.Request != nil {
  1033  		return fmt.Sprintf("%v %v: %d %v %+v",
  1034  			r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
  1035  			r.Response.StatusCode, r.Message, r.Errors)
  1036  	}
  1037  
  1038  	if r.Response != nil {
  1039  		return fmt.Sprintf("%d %v %+v", r.Response.StatusCode, r.Message, r.Errors)
  1040  	}
  1041  
  1042  	return fmt.Sprintf("%v %+v", r.Message, r.Errors)
  1043  }
  1044  
  1045  // Is returns whether the provided error equals this error.
  1046  func (r *ErrorResponse) Is(target error) bool {
  1047  	v, ok := target.(*ErrorResponse)
  1048  	if !ok {
  1049  		return false
  1050  	}
  1051  
  1052  	if r.Message != v.Message || (r.DocumentationURL != v.DocumentationURL) ||
  1053  		!compareHTTPResponse(r.Response, v.Response) {
  1054  		return false
  1055  	}
  1056  
  1057  	// Compare Errors.
  1058  	if len(r.Errors) != len(v.Errors) {
  1059  		return false
  1060  	}
  1061  	for idx := range r.Errors {
  1062  		if r.Errors[idx] != v.Errors[idx] {
  1063  			return false
  1064  		}
  1065  	}
  1066  
  1067  	// Compare Block.
  1068  	if (r.Block != nil && v.Block == nil) || (r.Block == nil && v.Block != nil) {
  1069  		return false
  1070  	}
  1071  	if r.Block != nil && v.Block != nil {
  1072  		if r.Block.Reason != v.Block.Reason {
  1073  			return false
  1074  		}
  1075  		if (r.Block.CreatedAt != nil && v.Block.CreatedAt == nil) || (r.Block.CreatedAt ==
  1076  			nil && v.Block.CreatedAt != nil) {
  1077  			return false
  1078  		}
  1079  		if r.Block.CreatedAt != nil && v.Block.CreatedAt != nil {
  1080  			if *(r.Block.CreatedAt) != *(v.Block.CreatedAt) {
  1081  				return false
  1082  			}
  1083  		}
  1084  	}
  1085  
  1086  	return true
  1087  }
  1088  
  1089  // TwoFactorAuthError occurs when using HTTP Basic Authentication for a user
  1090  // that has two-factor authentication enabled. The request can be reattempted
  1091  // by providing a one-time password in the request.
  1092  type TwoFactorAuthError ErrorResponse
  1093  
  1094  func (r *TwoFactorAuthError) Error() string { return (*ErrorResponse)(r).Error() }
  1095  
  1096  // RateLimitError occurs when GitHub returns 403 Forbidden response with a rate limit
  1097  // remaining value of 0.
  1098  type RateLimitError struct {
  1099  	Rate     Rate           // Rate specifies last known rate limit for the client
  1100  	Response *http.Response // HTTP response that caused this error
  1101  	Message  string         `json:"message"` // error message
  1102  }
  1103  
  1104  func (r *RateLimitError) Error() string {
  1105  	return fmt.Sprintf("%v %v: %d %v %v",
  1106  		r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
  1107  		r.Response.StatusCode, r.Message, formatRateReset(time.Until(r.Rate.Reset.Time)))
  1108  }
  1109  
  1110  // Is returns whether the provided error equals this error.
  1111  func (r *RateLimitError) Is(target error) bool {
  1112  	v, ok := target.(*RateLimitError)
  1113  	if !ok {
  1114  		return false
  1115  	}
  1116  
  1117  	return r.Rate == v.Rate &&
  1118  		r.Message == v.Message &&
  1119  		compareHTTPResponse(r.Response, v.Response)
  1120  }
  1121  
  1122  // AcceptedError occurs when GitHub returns 202 Accepted response with an
  1123  // empty body, which means a job was scheduled on the GitHub side to process
  1124  // the information needed and cache it.
  1125  // Technically, 202 Accepted is not a real error, it's just used to
  1126  // indicate that results are not ready yet, but should be available soon.
  1127  // The request can be repeated after some time.
  1128  type AcceptedError struct {
  1129  	// Raw contains the response body.
  1130  	Raw []byte
  1131  }
  1132  
  1133  func (*AcceptedError) Error() string {
  1134  	return "job scheduled on GitHub side; try again later"
  1135  }
  1136  
  1137  // Is returns whether the provided error equals this error.
  1138  func (ae *AcceptedError) Is(target error) bool {
  1139  	v, ok := target.(*AcceptedError)
  1140  	if !ok {
  1141  		return false
  1142  	}
  1143  	return bytes.Equal(ae.Raw, v.Raw)
  1144  }
  1145  
  1146  // AbuseRateLimitError occurs when GitHub returns 403 Forbidden response with the
  1147  // "documentation_url" field value equal to "https://docs.github.com/rest/overview/rate-limits-for-the-rest-api#about-secondary-rate-limits".
  1148  type AbuseRateLimitError struct {
  1149  	Response *http.Response // HTTP response that caused this error
  1150  	Message  string         `json:"message"` // error message
  1151  
  1152  	// RetryAfter is provided with some abuse rate limit errors. If present,
  1153  	// it is the amount of time that the client should wait before retrying.
  1154  	// Otherwise, the client should try again later (after an unspecified amount of time).
  1155  	RetryAfter *time.Duration
  1156  }
  1157  
  1158  func (r *AbuseRateLimitError) Error() string {
  1159  	return fmt.Sprintf("%v %v: %d %v",
  1160  		r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
  1161  		r.Response.StatusCode, r.Message)
  1162  }
  1163  
  1164  // Is returns whether the provided error equals this error.
  1165  func (r *AbuseRateLimitError) Is(target error) bool {
  1166  	v, ok := target.(*AbuseRateLimitError)
  1167  	if !ok {
  1168  		return false
  1169  	}
  1170  
  1171  	return r.Message == v.Message &&
  1172  		r.RetryAfter == v.RetryAfter &&
  1173  		compareHTTPResponse(r.Response, v.Response)
  1174  }
  1175  
  1176  // sanitizeURL redacts the client_secret parameter from the URL which may be
  1177  // exposed to the user.
  1178  func sanitizeURL(uri *url.URL) *url.URL {
  1179  	if uri == nil {
  1180  		return nil
  1181  	}
  1182  	params := uri.Query()
  1183  	if len(params.Get("client_secret")) > 0 {
  1184  		params.Set("client_secret", "REDACTED")
  1185  		uri.RawQuery = params.Encode()
  1186  	}
  1187  	return uri
  1188  }
  1189  
  1190  /*
  1191  An Error reports more details on an individual error in an ErrorResponse.
  1192  These are the possible validation error codes:
  1193  
  1194  	missing:
  1195  	    resource does not exist
  1196  	missing_field:
  1197  	    a required field on a resource has not been set
  1198  	invalid:
  1199  	    the formatting of a field is invalid
  1200  	already_exists:
  1201  	    another resource has the same valid as this field
  1202  	custom:
  1203  	    some resources return this (e.g. github.User.CreateKey()), additional
  1204  	    information is set in the Message field of the Error
  1205  
  1206  GitHub error responses structure are often undocumented and inconsistent.
  1207  Sometimes error is just a simple string (Issue #540).
  1208  In such cases, Message represents an error message as a workaround.
  1209  
  1210  GitHub API docs: https://docs.github.com/rest/#client-errors
  1211  */
  1212  type Error struct {
  1213  	Resource string `json:"resource"` // resource on which the error occurred
  1214  	Field    string `json:"field"`    // field on which the error occurred
  1215  	Code     string `json:"code"`     // validation error code
  1216  	Message  string `json:"message"`  // Message describing the error. Errors with Code == "custom" will always have this set.
  1217  }
  1218  
  1219  func (e *Error) Error() string {
  1220  	return fmt.Sprintf("%v error caused by %v field on %v resource",
  1221  		e.Code, e.Field, e.Resource)
  1222  }
  1223  
  1224  func (e *Error) UnmarshalJSON(data []byte) error {
  1225  	type aliasError Error // avoid infinite recursion by using type alias.
  1226  	if err := json.Unmarshal(data, (*aliasError)(e)); err != nil {
  1227  		return json.Unmarshal(data, &e.Message) // data can be json string.
  1228  	}
  1229  	return nil
  1230  }
  1231  
  1232  // CheckResponse checks the API response for errors, and returns them if
  1233  // present. A response is considered an error if it has a status code outside
  1234  // the 200 range or equal to 202 Accepted.
  1235  // API error responses are expected to have response
  1236  // body, and a JSON response body that maps to ErrorResponse.
  1237  //
  1238  // The error type will be *RateLimitError for rate limit exceeded errors,
  1239  // *AcceptedError for 202 Accepted status codes,
  1240  // and *TwoFactorAuthError for two-factor authentication errors.
  1241  func CheckResponse(r *http.Response) error {
  1242  	if r.StatusCode == http.StatusAccepted {
  1243  		return &AcceptedError{}
  1244  	}
  1245  	if c := r.StatusCode; 200 <= c && c <= 299 {
  1246  		return nil
  1247  	}
  1248  
  1249  	errorResponse := &ErrorResponse{Response: r}
  1250  	data, err := io.ReadAll(r.Body)
  1251  	if err == nil && data != nil {
  1252  		err = json.Unmarshal(data, errorResponse)
  1253  		if err != nil {
  1254  			// reset the response as if this never happened
  1255  			errorResponse = &ErrorResponse{Response: r}
  1256  		}
  1257  	}
  1258  	// Re-populate error response body because GitHub error responses are often
  1259  	// undocumented and inconsistent.
  1260  	// Issue #1136, #540.
  1261  	r.Body = io.NopCloser(bytes.NewBuffer(data))
  1262  	switch {
  1263  	case r.StatusCode == http.StatusUnauthorized && strings.HasPrefix(r.Header.Get(headerOTP), "required"):
  1264  		return (*TwoFactorAuthError)(errorResponse)
  1265  	case r.StatusCode == http.StatusForbidden && r.Header.Get(headerRateRemaining) == "0":
  1266  		return &RateLimitError{
  1267  			Rate:     parseRate(r),
  1268  			Response: errorResponse.Response,
  1269  			Message:  errorResponse.Message,
  1270  		}
  1271  	case r.StatusCode == http.StatusForbidden &&
  1272  		(strings.HasSuffix(errorResponse.DocumentationURL, "#abuse-rate-limits") ||
  1273  			strings.HasSuffix(errorResponse.DocumentationURL, "secondary-rate-limits")):
  1274  		abuseRateLimitError := &AbuseRateLimitError{
  1275  			Response: errorResponse.Response,
  1276  			Message:  errorResponse.Message,
  1277  		}
  1278  		if retryAfter := parseSecondaryRate(r); retryAfter != nil {
  1279  			abuseRateLimitError.RetryAfter = retryAfter
  1280  		}
  1281  		return abuseRateLimitError
  1282  	default:
  1283  		return errorResponse
  1284  	}
  1285  }
  1286  
  1287  // parseBoolResponse determines the boolean result from a GitHub API response.
  1288  // Several GitHub API methods return boolean responses indicated by the HTTP
  1289  // status code in the response (true indicated by a 204, false indicated by a
  1290  // 404). This helper function will determine that result and hide the 404
  1291  // error if present. Any other error will be returned through as-is.
  1292  func parseBoolResponse(err error) (bool, error) {
  1293  	if err == nil {
  1294  		return true, nil
  1295  	}
  1296  
  1297  	if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode == http.StatusNotFound {
  1298  		// Simply false. In this one case, we do not pass the error through.
  1299  		return false, nil
  1300  	}
  1301  
  1302  	// some other real error occurred
  1303  	return false, err
  1304  }
  1305  
  1306  type rateLimitCategory uint8
  1307  
  1308  const (
  1309  	coreCategory rateLimitCategory = iota
  1310  	searchCategory
  1311  	graphqlCategory
  1312  	integrationManifestCategory
  1313  	sourceImportCategory
  1314  	codeScanningUploadCategory
  1315  	actionsRunnerRegistrationCategory
  1316  	scimCategory
  1317  	dependencySnapshotsCategory
  1318  	codeSearchCategory
  1319  
  1320  	categories // An array of this length will be able to contain all rate limit categories.
  1321  )
  1322  
  1323  // category returns the rate limit category of the endpoint, determined by HTTP method and Request.URL.Path.
  1324  func category(method, path string) rateLimitCategory {
  1325  	switch {
  1326  	// https://docs.github.com/rest/rate-limit#about-rate-limits
  1327  	default:
  1328  		// NOTE: coreCategory is returned for actionsRunnerRegistrationCategory too,
  1329  		// because no API found for this category.
  1330  		return coreCategory
  1331  
  1332  	// https://docs.github.com/en/rest/search/search#search-code
  1333  	case strings.HasPrefix(path, "/search/code") &&
  1334  		method == http.MethodGet:
  1335  		return codeSearchCategory
  1336  
  1337  	case strings.HasPrefix(path, "/search/"):
  1338  		return searchCategory
  1339  	case path == "/graphql":
  1340  		return graphqlCategory
  1341  	case strings.HasPrefix(path, "/app-manifests/") &&
  1342  		strings.HasSuffix(path, "/conversions") &&
  1343  		method == http.MethodPost:
  1344  		return integrationManifestCategory
  1345  
  1346  	// https://docs.github.com/rest/migrations/source-imports#start-an-import
  1347  	case strings.HasPrefix(path, "/repos/") &&
  1348  		strings.HasSuffix(path, "/import") &&
  1349  		method == http.MethodPut:
  1350  		return sourceImportCategory
  1351  
  1352  	// https://docs.github.com/rest/code-scanning#upload-an-analysis-as-sarif-data
  1353  	case strings.HasSuffix(path, "/code-scanning/sarifs"):
  1354  		return codeScanningUploadCategory
  1355  
  1356  	// https://docs.github.com/enterprise-cloud@latest/rest/scim
  1357  	case strings.HasPrefix(path, "/scim/"):
  1358  		return scimCategory
  1359  
  1360  	// https://docs.github.com/en/rest/dependency-graph/dependency-submission#create-a-snapshot-of-dependencies-for-a-repository
  1361  	case strings.HasPrefix(path, "/repos/") &&
  1362  		strings.HasSuffix(path, "/dependency-graph/snapshots") &&
  1363  		method == http.MethodPost:
  1364  		return dependencySnapshotsCategory
  1365  	}
  1366  }
  1367  
  1368  // RateLimits returns the rate limits for the current client.
  1369  //
  1370  // Deprecated: Use RateLimitService.Get instead.
  1371  func (c *Client) RateLimits(ctx context.Context) (*RateLimits, *Response, error) {
  1372  	return c.RateLimit.Get(ctx)
  1373  }
  1374  
  1375  func setCredentialsAsHeaders(req *http.Request, id, secret string) *http.Request {
  1376  	// To set extra headers, we must make a copy of the Request so
  1377  	// that we don't modify the Request we were given. This is required by the
  1378  	// specification of http.RoundTripper.
  1379  	//
  1380  	// Since we are going to modify only req.Header here, we only need a deep copy
  1381  	// of req.Header.
  1382  	convertedRequest := new(http.Request)
  1383  	*convertedRequest = *req
  1384  	convertedRequest.Header = make(http.Header, len(req.Header))
  1385  
  1386  	for k, s := range req.Header {
  1387  		convertedRequest.Header[k] = append([]string(nil), s...)
  1388  	}
  1389  	convertedRequest.SetBasicAuth(id, secret)
  1390  	return convertedRequest
  1391  }
  1392  
  1393  /*
  1394  UnauthenticatedRateLimitedTransport allows you to make unauthenticated calls
  1395  that need to use a higher rate limit associated with your OAuth application.
  1396  
  1397  	t := &github.UnauthenticatedRateLimitedTransport{
  1398  		ClientID:     "your app's client ID",
  1399  		ClientSecret: "your app's client secret",
  1400  	}
  1401  	client := github.NewClient(t.Client())
  1402  
  1403  This will add the client id and secret as a base64-encoded string in the format
  1404  ClientID:ClientSecret and apply it as an "Authorization": "Basic" header.
  1405  
  1406  See https://docs.github.com/rest/#unauthenticated-rate-limited-requests for
  1407  more information.
  1408  */
  1409  type UnauthenticatedRateLimitedTransport struct {
  1410  	// ClientID is the GitHub OAuth client ID of the current application, which
  1411  	// can be found by selecting its entry in the list at
  1412  	// https://github.com/settings/applications.
  1413  	ClientID string
  1414  
  1415  	// ClientSecret is the GitHub OAuth client secret of the current
  1416  	// application.
  1417  	ClientSecret string
  1418  
  1419  	// Transport is the underlying HTTP transport to use when making requests.
  1420  	// It will default to http.DefaultTransport if nil.
  1421  	Transport http.RoundTripper
  1422  }
  1423  
  1424  // RoundTrip implements the RoundTripper interface.
  1425  func (t *UnauthenticatedRateLimitedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
  1426  	if t.ClientID == "" {
  1427  		return nil, errors.New("t.ClientID is empty")
  1428  	}
  1429  	if t.ClientSecret == "" {
  1430  		return nil, errors.New("t.ClientSecret is empty")
  1431  	}
  1432  
  1433  	req2 := setCredentialsAsHeaders(req, t.ClientID, t.ClientSecret)
  1434  	// Make the HTTP request.
  1435  	return t.transport().RoundTrip(req2)
  1436  }
  1437  
  1438  // Client returns an *http.Client that makes requests which are subject to the
  1439  // rate limit of your OAuth application.
  1440  func (t *UnauthenticatedRateLimitedTransport) Client() *http.Client {
  1441  	return &http.Client{Transport: t}
  1442  }
  1443  
  1444  func (t *UnauthenticatedRateLimitedTransport) transport() http.RoundTripper {
  1445  	if t.Transport != nil {
  1446  		return t.Transport
  1447  	}
  1448  	return http.DefaultTransport
  1449  }
  1450  
  1451  // BasicAuthTransport is an http.RoundTripper that authenticates all requests
  1452  // using HTTP Basic Authentication with the provided username and password. It
  1453  // additionally supports users who have two-factor authentication enabled on
  1454  // their GitHub account.
  1455  type BasicAuthTransport struct {
  1456  	Username string // GitHub username
  1457  	Password string // GitHub password
  1458  	OTP      string // one-time password for users with two-factor auth enabled
  1459  
  1460  	// Transport is the underlying HTTP transport to use when making requests.
  1461  	// It will default to http.DefaultTransport if nil.
  1462  	Transport http.RoundTripper
  1463  }
  1464  
  1465  // RoundTrip implements the RoundTripper interface.
  1466  func (t *BasicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
  1467  	req2 := setCredentialsAsHeaders(req, t.Username, t.Password)
  1468  	if t.OTP != "" {
  1469  		req2.Header.Set(headerOTP, t.OTP)
  1470  	}
  1471  	return t.transport().RoundTrip(req2)
  1472  }
  1473  
  1474  // Client returns an *http.Client that makes requests that are authenticated
  1475  // using HTTP Basic Authentication.
  1476  func (t *BasicAuthTransport) Client() *http.Client {
  1477  	return &http.Client{Transport: t}
  1478  }
  1479  
  1480  func (t *BasicAuthTransport) transport() http.RoundTripper {
  1481  	if t.Transport != nil {
  1482  		return t.Transport
  1483  	}
  1484  	return http.DefaultTransport
  1485  }
  1486  
  1487  // formatRateReset formats d to look like "[rate reset in 2s]" or
  1488  // "[rate reset in 87m02s]" for the positive durations. And like "[rate limit was reset 87m02s ago]"
  1489  // for the negative cases.
  1490  func formatRateReset(d time.Duration) string {
  1491  	isNegative := d < 0
  1492  	if isNegative {
  1493  		d *= -1
  1494  	}
  1495  	secondsTotal := int(0.5 + d.Seconds())
  1496  	minutes := secondsTotal / 60
  1497  	seconds := secondsTotal - minutes*60
  1498  
  1499  	var timeString string
  1500  	if minutes > 0 {
  1501  		timeString = fmt.Sprintf("%dm%02ds", minutes, seconds)
  1502  	} else {
  1503  		timeString = fmt.Sprintf("%ds", seconds)
  1504  	}
  1505  
  1506  	if isNegative {
  1507  		return fmt.Sprintf("[rate limit was reset %v ago]", timeString)
  1508  	}
  1509  	return fmt.Sprintf("[rate reset in %v]", timeString)
  1510  }
  1511  
  1512  // When using roundTripWithOptionalFollowRedirect, note that it
  1513  // is the responsibility of the caller to close the response body.
  1514  func (c *Client) roundTripWithOptionalFollowRedirect(ctx context.Context, u string, maxRedirects int, opts ...RequestOption) (*http.Response, error) {
  1515  	req, err := c.NewRequest("GET", u, nil, opts...)
  1516  	if err != nil {
  1517  		return nil, err
  1518  	}
  1519  
  1520  	var resp *http.Response
  1521  	// Use http.DefaultTransport if no custom Transport is configured
  1522  	req = withContext(ctx, req)
  1523  	if c.client.Transport == nil {
  1524  		resp, err = http.DefaultTransport.RoundTrip(req)
  1525  	} else {
  1526  		resp, err = c.client.Transport.RoundTrip(req)
  1527  	}
  1528  	if err != nil {
  1529  		return nil, err
  1530  	}
  1531  
  1532  	// If redirect response is returned, follow it
  1533  	if maxRedirects > 0 && resp.StatusCode == http.StatusMovedPermanently {
  1534  		_ = resp.Body.Close()
  1535  		u = resp.Header.Get("Location")
  1536  		resp, err = c.roundTripWithOptionalFollowRedirect(ctx, u, maxRedirects-1, opts...)
  1537  	}
  1538  	return resp, err
  1539  }
  1540  
  1541  // Bool is a helper routine that allocates a new bool value
  1542  // to store v and returns a pointer to it.
  1543  func Bool(v bool) *bool { return &v }
  1544  
  1545  // Int is a helper routine that allocates a new int value
  1546  // to store v and returns a pointer to it.
  1547  func Int(v int) *int { return &v }
  1548  
  1549  // Int64 is a helper routine that allocates a new int64 value
  1550  // to store v and returns a pointer to it.
  1551  func Int64(v int64) *int64 { return &v }
  1552  
  1553  // String is a helper routine that allocates a new string value
  1554  // to store v and returns a pointer to it.
  1555  func String(v string) *string { return &v }
  1556  
  1557  // roundTripperFunc creates a RoundTripper (transport)
  1558  type roundTripperFunc func(*http.Request) (*http.Response, error)
  1559  
  1560  func (fn roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
  1561  	return fn(r)
  1562  }