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

     1  // Copyright 2013 The go-github AUTHORS. All rights reserved.
     2  //
     3  // Use of this source code is governed by a BSD-style
     4  // license that can be found in the LICENSE file.
     5  
     6  //go:generate go run gen-accessors.go
     7  //go:generate go run gen-stringify-test.go
     8  
     9  package github
    10  
    11  import (
    12  	"bytes"
    13  	"context"
    14  	"encoding/json"
    15  	"errors"
    16  	"fmt"
    17  	"io"
    18  	"io/ioutil"
    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  	defaultBaseURL = "https://api.github.com/"
    32  	uploadBaseURL  = "https://uploads.github.com/"
    33  	userAgent      = "go-github"
    34  
    35  	headerRateLimit     = "X-RateLimit-Limit"
    36  	headerRateRemaining = "X-RateLimit-Remaining"
    37  	headerRateReset     = "X-RateLimit-Reset"
    38  	headerOTP           = "X-GitHub-OTP"
    39  
    40  	mediaTypeV3                = "application/vnd.github.v3+json"
    41  	defaultMediaType           = "application/octet-stream"
    42  	mediaTypeV3SHA             = "application/vnd.github.v3.sha"
    43  	mediaTypeV3Diff            = "application/vnd.github.v3.diff"
    44  	mediaTypeV3Patch           = "application/vnd.github.v3.patch"
    45  	mediaTypeOrgPermissionRepo = "application/vnd.github.v3.repository+json"
    46  	mediaTypeIssueImportAPI    = "application/vnd.github.golden-comet-preview+json"
    47  
    48  	// Media Type values to access preview APIs
    49  
    50  	// https://developer.github.com/changes/2014-12-09-new-attributes-for-stars-api/
    51  	mediaTypeStarringPreview = "application/vnd.github.v3.star+json"
    52  
    53  	// https://help.github.com/enterprise/2.4/admin/guides/migrations/exporting-the-github-com-organization-s-repositories/
    54  	mediaTypeMigrationsPreview = "application/vnd.github.wyandotte-preview+json"
    55  
    56  	// https://developer.github.com/changes/2016-04-06-deployment-and-deployment-status-enhancements/
    57  	mediaTypeDeploymentStatusPreview = "application/vnd.github.ant-man-preview+json"
    58  
    59  	// https://developer.github.com/changes/2018-10-16-deployments-environments-states-and-auto-inactive-updates/
    60  	mediaTypeExpandDeploymentStatusPreview = "application/vnd.github.flash-preview+json"
    61  
    62  	// https://developer.github.com/changes/2016-05-12-reactions-api-preview/
    63  	mediaTypeReactionsPreview = "application/vnd.github.squirrel-girl-preview"
    64  
    65  	// https://developer.github.com/changes/2016-05-23-timeline-preview-api/
    66  	mediaTypeTimelinePreview = "application/vnd.github.mockingbird-preview+json"
    67  
    68  	// https://developer.github.com/changes/2016-09-14-projects-api/
    69  	mediaTypeProjectsPreview = "application/vnd.github.inertia-preview+json"
    70  
    71  	// https://developer.github.com/changes/2017-01-05-commit-search-api/
    72  	mediaTypeCommitSearchPreview = "application/vnd.github.cloak-preview+json"
    73  
    74  	// https://developer.github.com/changes/2017-02-28-user-blocking-apis-and-webhook/
    75  	mediaTypeBlockUsersPreview = "application/vnd.github.giant-sentry-fist-preview+json"
    76  
    77  	// https://developer.github.com/changes/2017-02-09-community-health/
    78  	mediaTypeRepositoryCommunityHealthMetricsPreview = "application/vnd.github.black-panther-preview+json"
    79  
    80  	// https://developer.github.com/changes/2017-05-23-coc-api/
    81  	mediaTypeCodesOfConductPreview = "application/vnd.github.scarlet-witch-preview+json"
    82  
    83  	// https://developer.github.com/changes/2017-07-17-update-topics-on-repositories/
    84  	mediaTypeTopicsPreview = "application/vnd.github.mercy-preview+json"
    85  
    86  	// https://developer.github.com/changes/2018-03-16-protected-branches-required-approving-reviews/
    87  	mediaTypeRequiredApprovingReviewsPreview = "application/vnd.github.luke-cage-preview+json"
    88  
    89  	// https://developer.github.com/enterprise/2.13/v3/repos/pre_receive_hooks/
    90  	mediaTypePreReceiveHooksPreview = "application/vnd.github.eye-scream-preview"
    91  
    92  	// https://developer.github.com/changes/2018-02-22-protected-branches-required-signatures/
    93  	mediaTypeSignaturePreview = "application/vnd.github.zzzax-preview+json"
    94  
    95  	// https://developer.github.com/changes/2018-09-05-project-card-events/
    96  	mediaTypeProjectCardDetailsPreview = "application/vnd.github.starfox-preview+json"
    97  
    98  	// https://developer.github.com/changes/2018-12-18-interactions-preview/
    99  	mediaTypeInteractionRestrictionsPreview = "application/vnd.github.sombra-preview+json"
   100  
   101  	// https://developer.github.com/changes/2019-03-14-enabling-disabling-pages/
   102  	mediaTypeEnablePagesAPIPreview = "application/vnd.github.switcheroo-preview+json"
   103  
   104  	// https://developer.github.com/changes/2019-04-24-vulnerability-alerts/
   105  	mediaTypeRequiredVulnerabilityAlertsPreview = "application/vnd.github.dorian-preview+json"
   106  
   107  	// https://developer.github.com/changes/2019-06-04-automated-security-fixes/
   108  	mediaTypeRequiredAutomatedSecurityFixesPreview = "application/vnd.github.london-preview+json"
   109  
   110  	// https://developer.github.com/changes/2019-05-29-update-branch-api/
   111  	mediaTypeUpdatePullRequestBranchPreview = "application/vnd.github.lydian-preview+json"
   112  
   113  	// https://developer.github.com/changes/2019-04-11-pulls-branches-for-commit/
   114  	mediaTypeListPullsOrBranchesForCommitPreview = "application/vnd.github.groot-preview+json"
   115  
   116  	// https://docs.github.com/en/free-pro-team@latest/rest/reference/previews/#repository-creation-permissions
   117  	mediaTypeMemberAllowedRepoCreationTypePreview = "application/vnd.github.surtur-preview+json"
   118  
   119  	// https://docs.github.com/en/free-pro-team@latest/rest/reference/previews/#create-and-use-repository-templates
   120  	mediaTypeRepositoryTemplatePreview = "application/vnd.github.baptiste-preview+json"
   121  
   122  	// https://developer.github.com/changes/2019-10-03-multi-line-comments/
   123  	mediaTypeMultiLineCommentsPreview = "application/vnd.github.comfort-fade-preview+json"
   124  
   125  	// https://developer.github.com/changes/2019-11-05-deprecated-passwords-and-authorizations-api/
   126  	mediaTypeOAuthAppPreview = "application/vnd.github.doctor-strange-preview+json"
   127  
   128  	// https://developer.github.com/changes/2019-12-03-internal-visibility-changes/
   129  	mediaTypeRepositoryVisibilityPreview = "application/vnd.github.nebula-preview+json"
   130  
   131  	// https://developer.github.com/changes/2018-12-10-content-attachments-api/
   132  	mediaTypeContentAttachmentsPreview = "application/vnd.github.corsair-preview+json"
   133  )
   134  
   135  // A Client manages communication with the GitHub API.
   136  type Client struct {
   137  	clientMu sync.Mutex   // clientMu protects the client during calls that modify the CheckRedirect func.
   138  	client   *http.Client // HTTP client used to communicate with the API.
   139  
   140  	// Base URL for API requests. Defaults to the public GitHub API, but can be
   141  	// set to a domain endpoint to use with GitHub Enterprise. BaseURL should
   142  	// always be specified with a trailing slash.
   143  	BaseURL *url.URL
   144  
   145  	// Base URL for uploading files.
   146  	UploadURL *url.URL
   147  
   148  	// User agent used when communicating with the GitHub API.
   149  	UserAgent string
   150  
   151  	rateMu     sync.Mutex
   152  	rateLimits [categories]Rate // Rate limits for the client as determined by the most recent API calls.
   153  
   154  	common service // Reuse a single struct instead of allocating one for each service on the heap.
   155  
   156  	// Services used for talking to different parts of the GitHub API.
   157  	Actions        *ActionsService
   158  	Activity       *ActivityService
   159  	Admin          *AdminService
   160  	Apps           *AppsService
   161  	Authorizations *AuthorizationsService
   162  	Checks         *ChecksService
   163  	CodeScanning   *CodeScanningService
   164  	Enterprise     *EnterpriseService
   165  	Gists          *GistsService
   166  	Git            *GitService
   167  	Gitignores     *GitignoresService
   168  	Interactions   *InteractionsService
   169  	IssueImport    *IssueImportService
   170  	Issues         *IssuesService
   171  	Licenses       *LicensesService
   172  	Marketplace    *MarketplaceService
   173  	Migrations     *MigrationService
   174  	Organizations  *OrganizationsService
   175  	Projects       *ProjectsService
   176  	PullRequests   *PullRequestsService
   177  	Reactions      *ReactionsService
   178  	Repositories   *RepositoriesService
   179  	Search         *SearchService
   180  	Teams          *TeamsService
   181  	Users          *UsersService
   182  }
   183  
   184  type service struct {
   185  	client *Client
   186  }
   187  
   188  // ListOptions specifies the optional parameters to various List methods that
   189  // support offset pagination.
   190  type ListOptions struct {
   191  	// For paginated result sets, page of results to retrieve.
   192  	Page int `url:"page,omitempty"`
   193  
   194  	// For paginated result sets, the number of results to include per page.
   195  	PerPage int `url:"per_page,omitempty"`
   196  }
   197  
   198  // ListCursorOptions specifies the optional parameters to various List methods that
   199  // support cursor pagination.
   200  type ListCursorOptions struct {
   201  	// For paginated result sets, page of results to retrieve.
   202  	Page string `url:"page,omitempty"`
   203  
   204  	// For paginated result sets, the number of results to include per page.
   205  	PerPage int `url:"per_page,omitempty"`
   206  }
   207  
   208  // UploadOptions specifies the parameters to methods that support uploads.
   209  type UploadOptions struct {
   210  	Name      string `url:"name,omitempty"`
   211  	Label     string `url:"label,omitempty"`
   212  	MediaType string `url:"-"`
   213  }
   214  
   215  // RawType represents type of raw format of a request instead of JSON.
   216  type RawType uint8
   217  
   218  const (
   219  	// Diff format.
   220  	Diff RawType = 1 + iota
   221  	// Patch format.
   222  	Patch
   223  )
   224  
   225  // RawOptions specifies parameters when user wants to get raw format of
   226  // a response instead of JSON.
   227  type RawOptions struct {
   228  	Type RawType
   229  }
   230  
   231  // addOptions adds the parameters in opts as URL query parameters to s. opts
   232  // must be a struct whose fields may contain "url" tags.
   233  func addOptions(s string, opts interface{}) (string, error) {
   234  	v := reflect.ValueOf(opts)
   235  	if v.Kind() == reflect.Ptr && v.IsNil() {
   236  		return s, nil
   237  	}
   238  
   239  	u, err := url.Parse(s)
   240  	if err != nil {
   241  		return s, err
   242  	}
   243  
   244  	qs, err := query.Values(opts)
   245  	if err != nil {
   246  		return s, err
   247  	}
   248  
   249  	u.RawQuery = qs.Encode()
   250  	return u.String(), nil
   251  }
   252  
   253  // NewClient returns a new GitHub API client. If a nil httpClient is
   254  // provided, a new http.Client will be used. To use API methods which require
   255  // authentication, provide an http.Client that will perform the authentication
   256  // for you (such as that provided by the golang.org/x/oauth2 library).
   257  func NewClient(httpClient *http.Client) *Client {
   258  	if httpClient == nil {
   259  		httpClient = &http.Client{}
   260  	}
   261  	baseURL, _ := url.Parse(defaultBaseURL)
   262  	uploadURL, _ := url.Parse(uploadBaseURL)
   263  
   264  	c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: userAgent, UploadURL: uploadURL}
   265  	c.common.client = c
   266  	c.Actions = (*ActionsService)(&c.common)
   267  	c.Activity = (*ActivityService)(&c.common)
   268  	c.Admin = (*AdminService)(&c.common)
   269  	c.Apps = (*AppsService)(&c.common)
   270  	c.Authorizations = (*AuthorizationsService)(&c.common)
   271  	c.Checks = (*ChecksService)(&c.common)
   272  	c.CodeScanning = (*CodeScanningService)(&c.common)
   273  	c.Enterprise = (*EnterpriseService)(&c.common)
   274  	c.Gists = (*GistsService)(&c.common)
   275  	c.Git = (*GitService)(&c.common)
   276  	c.Gitignores = (*GitignoresService)(&c.common)
   277  	c.Interactions = (*InteractionsService)(&c.common)
   278  	c.IssueImport = (*IssueImportService)(&c.common)
   279  	c.Issues = (*IssuesService)(&c.common)
   280  	c.Licenses = (*LicensesService)(&c.common)
   281  	c.Marketplace = &MarketplaceService{client: c}
   282  	c.Migrations = (*MigrationService)(&c.common)
   283  	c.Organizations = (*OrganizationsService)(&c.common)
   284  	c.Projects = (*ProjectsService)(&c.common)
   285  	c.PullRequests = (*PullRequestsService)(&c.common)
   286  	c.Reactions = (*ReactionsService)(&c.common)
   287  	c.Repositories = (*RepositoriesService)(&c.common)
   288  	c.Search = (*SearchService)(&c.common)
   289  	c.Teams = (*TeamsService)(&c.common)
   290  	c.Users = (*UsersService)(&c.common)
   291  	return c
   292  }
   293  
   294  // NewEnterpriseClient returns a new GitHub API client with provided
   295  // base URL and upload URL (often is your GitHub Enterprise hostname).
   296  // If the base URL does not have the suffix "/api/v3/", it will be added automatically.
   297  // If the upload URL does not have the suffix "/api/uploads", it will be added automatically.
   298  // If a nil httpClient is provided, a new http.Client will be used.
   299  //
   300  // Note that NewEnterpriseClient is a convenience helper only;
   301  // its behavior is equivalent to using NewClient, followed by setting
   302  // the BaseURL and UploadURL fields.
   303  //
   304  // Another important thing is that by default, the GitHub Enterprise URL format
   305  // should be http(s)://[hostname]/api/v3/ or you will always receive the 406 status code.
   306  // The upload URL format should be http(s)://[hostname]/api/uploads/.
   307  func NewEnterpriseClient(baseURL, uploadURL string, httpClient *http.Client) (*Client, error) {
   308  	baseEndpoint, err := url.Parse(baseURL)
   309  	if err != nil {
   310  		return nil, err
   311  	}
   312  	if !strings.HasSuffix(baseEndpoint.Path, "/") {
   313  		baseEndpoint.Path += "/"
   314  	}
   315  	if !strings.HasSuffix(baseEndpoint.Path, "/api/v3/") &&
   316  		!strings.HasPrefix(baseEndpoint.Host, "api.") &&
   317  		!strings.Contains(baseEndpoint.Host, ".api.") {
   318  		baseEndpoint.Path += "api/v3/"
   319  	}
   320  
   321  	uploadEndpoint, err := url.Parse(uploadURL)
   322  	if err != nil {
   323  		return nil, err
   324  	}
   325  	if !strings.HasSuffix(uploadEndpoint.Path, "/") {
   326  		uploadEndpoint.Path += "/"
   327  	}
   328  	if !strings.HasSuffix(uploadEndpoint.Path, "/api/uploads/") &&
   329  		!strings.HasPrefix(uploadEndpoint.Host, "api.") &&
   330  		!strings.Contains(uploadEndpoint.Host, ".api.") {
   331  		uploadEndpoint.Path += "api/uploads/"
   332  	}
   333  
   334  	c := NewClient(httpClient)
   335  	c.BaseURL = baseEndpoint
   336  	c.UploadURL = uploadEndpoint
   337  	return c, nil
   338  }
   339  
   340  // NewRequest creates an API request. A relative URL can be provided in urlStr,
   341  // in which case it is resolved relative to the BaseURL of the Client.
   342  // Relative URLs should always be specified without a preceding slash. If
   343  // specified, the value pointed to by body is JSON encoded and included as the
   344  // request body.
   345  func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
   346  	if !strings.HasSuffix(c.BaseURL.Path, "/") {
   347  		return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL)
   348  	}
   349  	u, err := c.BaseURL.Parse(urlStr)
   350  	if err != nil {
   351  		return nil, err
   352  	}
   353  
   354  	var buf io.ReadWriter
   355  	if body != nil {
   356  		buf = &bytes.Buffer{}
   357  		enc := json.NewEncoder(buf)
   358  		enc.SetEscapeHTML(false)
   359  		err := enc.Encode(body)
   360  		if err != nil {
   361  			return nil, err
   362  		}
   363  	}
   364  
   365  	req, err := http.NewRequest(method, u.String(), buf)
   366  	if err != nil {
   367  		return nil, err
   368  	}
   369  
   370  	if body != nil {
   371  		req.Header.Set("Content-Type", "application/json")
   372  	}
   373  	req.Header.Set("Accept", mediaTypeV3)
   374  	if c.UserAgent != "" {
   375  		req.Header.Set("User-Agent", c.UserAgent)
   376  	}
   377  	return req, nil
   378  }
   379  
   380  // NewUploadRequest creates an upload request. A relative URL can be provided in
   381  // urlStr, in which case it is resolved relative to the UploadURL of the Client.
   382  // Relative URLs should always be specified without a preceding slash.
   383  func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, size int64, mediaType string) (*http.Request, error) {
   384  	if !strings.HasSuffix(c.UploadURL.Path, "/") {
   385  		return nil, fmt.Errorf("UploadURL must have a trailing slash, but %q does not", c.UploadURL)
   386  	}
   387  	u, err := c.UploadURL.Parse(urlStr)
   388  	if err != nil {
   389  		return nil, err
   390  	}
   391  
   392  	req, err := http.NewRequest("POST", u.String(), reader)
   393  	if err != nil {
   394  		return nil, err
   395  	}
   396  	req.ContentLength = size
   397  
   398  	if mediaType == "" {
   399  		mediaType = defaultMediaType
   400  	}
   401  	req.Header.Set("Content-Type", mediaType)
   402  	req.Header.Set("Accept", mediaTypeV3)
   403  	req.Header.Set("User-Agent", c.UserAgent)
   404  	return req, nil
   405  }
   406  
   407  // Response is a GitHub API response. This wraps the standard http.Response
   408  // returned from GitHub and provides convenient access to things like
   409  // pagination links.
   410  type Response struct {
   411  	*http.Response
   412  
   413  	// These fields provide the page values for paginating through a set of
   414  	// results. Any or all of these may be set to the zero value for
   415  	// responses that are not part of a paginated set, or for which there
   416  	// are no additional pages.
   417  	//
   418  	// These fields support what is called "offset pagination" and should
   419  	// be used with the ListOptions struct.
   420  	NextPage  int
   421  	PrevPage  int
   422  	FirstPage int
   423  	LastPage  int
   424  
   425  	// Additionally, some APIs support "cursor pagination" instead of offset.
   426  	// This means that a token points directly to the next record which
   427  	// can lead to O(1) performance compared to O(n) performance provided
   428  	// by offset pagination.
   429  	//
   430  	// For APIs that support cursor pagination (such as
   431  	// TeamsService.ListIDPGroupsInOrganization), the following field
   432  	// will be populated to point to the next page.
   433  	//
   434  	// To use this token, set ListCursorOptions.Page to this value before
   435  	// calling the endpoint again.
   436  	NextPageToken string
   437  
   438  	// Explicitly specify the Rate type so Rate's String() receiver doesn't
   439  	// propagate to Response.
   440  	Rate Rate
   441  }
   442  
   443  // newResponse creates a new Response for the provided http.Response.
   444  // r must not be nil.
   445  func newResponse(r *http.Response) *Response {
   446  	response := &Response{Response: r}
   447  	response.populatePageValues()
   448  	response.Rate = parseRate(r)
   449  	return response
   450  }
   451  
   452  // populatePageValues parses the HTTP Link response headers and populates the
   453  // various pagination link values in the Response.
   454  func (r *Response) populatePageValues() {
   455  	if links, ok := r.Response.Header["Link"]; ok && len(links) > 0 {
   456  		for _, link := range strings.Split(links[0], ",") {
   457  			segments := strings.Split(strings.TrimSpace(link), ";")
   458  
   459  			// link must at least have href and rel
   460  			if len(segments) < 2 {
   461  				continue
   462  			}
   463  
   464  			// ensure href is properly formatted
   465  			if !strings.HasPrefix(segments[0], "<") || !strings.HasSuffix(segments[0], ">") {
   466  				continue
   467  			}
   468  
   469  			// try to pull out page parameter
   470  			url, err := url.Parse(segments[0][1 : len(segments[0])-1])
   471  			if err != nil {
   472  				continue
   473  			}
   474  			page := url.Query().Get("page")
   475  			if page == "" {
   476  				continue
   477  			}
   478  
   479  			for _, segment := range segments[1:] {
   480  				switch strings.TrimSpace(segment) {
   481  				case `rel="next"`:
   482  					if r.NextPage, err = strconv.Atoi(page); err != nil {
   483  						r.NextPageToken = page
   484  					}
   485  				case `rel="prev"`:
   486  					r.PrevPage, _ = strconv.Atoi(page)
   487  				case `rel="first"`:
   488  					r.FirstPage, _ = strconv.Atoi(page)
   489  				case `rel="last"`:
   490  					r.LastPage, _ = strconv.Atoi(page)
   491  				}
   492  
   493  			}
   494  		}
   495  	}
   496  }
   497  
   498  // parseRate parses the rate related headers.
   499  func parseRate(r *http.Response) Rate {
   500  	var rate Rate
   501  	if limit := r.Header.Get(headerRateLimit); limit != "" {
   502  		rate.Limit, _ = strconv.Atoi(limit)
   503  	}
   504  	if remaining := r.Header.Get(headerRateRemaining); remaining != "" {
   505  		rate.Remaining, _ = strconv.Atoi(remaining)
   506  	}
   507  	if reset := r.Header.Get(headerRateReset); reset != "" {
   508  		if v, _ := strconv.ParseInt(reset, 10, 64); v != 0 {
   509  			rate.Reset = Timestamp{time.Unix(v, 0)}
   510  		}
   511  	}
   512  	return rate
   513  }
   514  
   515  // Do sends an API request and returns the API response. The API response is
   516  // JSON decoded and stored in the value pointed to by v, or returned as an
   517  // error if an API error has occurred. If v implements the io.Writer
   518  // interface, the raw response body will be written to v, without attempting to
   519  // first decode it. If rate limit is exceeded and reset time is in the future,
   520  // Do returns *RateLimitError immediately without making a network API call.
   521  //
   522  // The provided ctx must be non-nil, if it is nil an error is returned. If it is canceled or times out,
   523  // ctx.Err() will be returned.
   524  func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
   525  	if ctx == nil {
   526  		return nil, errors.New("context must be non-nil")
   527  	}
   528  	req = withContext(ctx, req)
   529  
   530  	rateLimitCategory := category(req.URL.Path)
   531  
   532  	// If we've hit rate limit, don't make further requests before Reset time.
   533  	if err := c.checkRateLimitBeforeDo(req, rateLimitCategory); err != nil {
   534  		return &Response{
   535  			Response: err.Response,
   536  			Rate:     err.Rate,
   537  		}, err
   538  	}
   539  
   540  	resp, err := c.client.Do(req)
   541  	if err != nil {
   542  		// If we got an error, and the context has been canceled,
   543  		// the context's error is probably more useful.
   544  		select {
   545  		case <-ctx.Done():
   546  			return nil, ctx.Err()
   547  		default:
   548  		}
   549  
   550  		// If the error type is *url.Error, sanitize its URL before returning.
   551  		if e, ok := err.(*url.Error); ok {
   552  			if url, err := url.Parse(e.URL); err == nil {
   553  				e.URL = sanitizeURL(url).String()
   554  				return nil, e
   555  			}
   556  		}
   557  
   558  		return nil, err
   559  	}
   560  
   561  	defer resp.Body.Close()
   562  
   563  	response := newResponse(resp)
   564  
   565  	c.rateMu.Lock()
   566  	c.rateLimits[rateLimitCategory] = response.Rate
   567  	c.rateMu.Unlock()
   568  
   569  	err = CheckResponse(resp)
   570  	if err != nil {
   571  		// Special case for AcceptedErrors. If an AcceptedError
   572  		// has been encountered, the response's payload will be
   573  		// added to the AcceptedError and returned.
   574  		//
   575  		// Issue #1022
   576  		aerr, ok := err.(*AcceptedError)
   577  		if ok {
   578  			b, readErr := ioutil.ReadAll(resp.Body)
   579  			if readErr != nil {
   580  				return response, readErr
   581  			}
   582  
   583  			aerr.Raw = b
   584  			return response, aerr
   585  		}
   586  
   587  		return response, err
   588  	}
   589  
   590  	if v != nil {
   591  		if w, ok := v.(io.Writer); ok {
   592  			io.Copy(w, resp.Body)
   593  		} else {
   594  			decErr := json.NewDecoder(resp.Body).Decode(v)
   595  			if decErr == io.EOF {
   596  				decErr = nil // ignore EOF errors caused by empty response body
   597  			}
   598  			if decErr != nil {
   599  				err = decErr
   600  			}
   601  		}
   602  	}
   603  
   604  	return response, err
   605  }
   606  
   607  // checkRateLimitBeforeDo does not make any network calls, but uses existing knowledge from
   608  // current client state in order to quickly check if *RateLimitError can be immediately returned
   609  // from Client.Do, and if so, returns it so that Client.Do can skip making a network API call unnecessarily.
   610  // Otherwise it returns nil, and Client.Do should proceed normally.
   611  func (c *Client) checkRateLimitBeforeDo(req *http.Request, rateLimitCategory rateLimitCategory) *RateLimitError {
   612  	c.rateMu.Lock()
   613  	rate := c.rateLimits[rateLimitCategory]
   614  	c.rateMu.Unlock()
   615  	if !rate.Reset.Time.IsZero() && rate.Remaining == 0 && time.Now().Before(rate.Reset.Time) {
   616  		// Create a fake response.
   617  		resp := &http.Response{
   618  			Status:     http.StatusText(http.StatusForbidden),
   619  			StatusCode: http.StatusForbidden,
   620  			Request:    req,
   621  			Header:     make(http.Header),
   622  			Body:       ioutil.NopCloser(strings.NewReader("")),
   623  		}
   624  		return &RateLimitError{
   625  			Rate:     rate,
   626  			Response: resp,
   627  			Message:  fmt.Sprintf("API rate limit of %v still exceeded until %v, not making remote request.", rate.Limit, rate.Reset.Time),
   628  		}
   629  	}
   630  
   631  	return nil
   632  }
   633  
   634  /*
   635  An ErrorResponse reports one or more errors caused by an API request.
   636  
   637  GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/#client-errors
   638  */
   639  type ErrorResponse struct {
   640  	Response *http.Response // HTTP response that caused this error
   641  	Message  string         `json:"message"` // error message
   642  	Errors   []Error        `json:"errors"`  // more detail on individual errors
   643  	// Block is only populated on certain types of errors such as code 451.
   644  	// See https://developer.github.com/changes/2016-03-17-the-451-status-code-is-now-supported/
   645  	// for more information.
   646  	Block *struct {
   647  		Reason    string     `json:"reason,omitempty"`
   648  		CreatedAt *Timestamp `json:"created_at,omitempty"`
   649  	} `json:"block,omitempty"`
   650  	// Most errors will also include a documentation_url field pointing
   651  	// to some content that might help you resolve the error, see
   652  	// https://docs.github.com/en/free-pro-team@latest/rest/reference/#client-errors
   653  	DocumentationURL string `json:"documentation_url,omitempty"`
   654  }
   655  
   656  func (r *ErrorResponse) Error() string {
   657  	return fmt.Sprintf("%v %v: %d %v %+v",
   658  		r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
   659  		r.Response.StatusCode, r.Message, r.Errors)
   660  }
   661  
   662  // TwoFactorAuthError occurs when using HTTP Basic Authentication for a user
   663  // that has two-factor authentication enabled. The request can be reattempted
   664  // by providing a one-time password in the request.
   665  type TwoFactorAuthError ErrorResponse
   666  
   667  func (r *TwoFactorAuthError) Error() string { return (*ErrorResponse)(r).Error() }
   668  
   669  // RateLimitError occurs when GitHub returns 403 Forbidden response with a rate limit
   670  // remaining value of 0.
   671  type RateLimitError struct {
   672  	Rate     Rate           // Rate specifies last known rate limit for the client
   673  	Response *http.Response // HTTP response that caused this error
   674  	Message  string         `json:"message"` // error message
   675  }
   676  
   677  func (r *RateLimitError) Error() string {
   678  	return fmt.Sprintf("%v %v: %d %v %v",
   679  		r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
   680  		r.Response.StatusCode, r.Message, formatRateReset(time.Until(r.Rate.Reset.Time)))
   681  }
   682  
   683  // AcceptedError occurs when GitHub returns 202 Accepted response with an
   684  // empty body, which means a job was scheduled on the GitHub side to process
   685  // the information needed and cache it.
   686  // Technically, 202 Accepted is not a real error, it's just used to
   687  // indicate that results are not ready yet, but should be available soon.
   688  // The request can be repeated after some time.
   689  type AcceptedError struct {
   690  	// Raw contains the response body.
   691  	Raw []byte
   692  }
   693  
   694  func (*AcceptedError) Error() string {
   695  	return "job scheduled on GitHub side; try again later"
   696  }
   697  
   698  // AbuseRateLimitError occurs when GitHub returns 403 Forbidden response with the
   699  // "documentation_url" field value equal to "https://docs.github.com/en/free-pro-team@latest/rest/reference/#abuse-rate-limits".
   700  type AbuseRateLimitError struct {
   701  	Response *http.Response // HTTP response that caused this error
   702  	Message  string         `json:"message"` // error message
   703  
   704  	// RetryAfter is provided with some abuse rate limit errors. If present,
   705  	// it is the amount of time that the client should wait before retrying.
   706  	// Otherwise, the client should try again later (after an unspecified amount of time).
   707  	RetryAfter *time.Duration
   708  }
   709  
   710  func (r *AbuseRateLimitError) Error() string {
   711  	return fmt.Sprintf("%v %v: %d %v",
   712  		r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
   713  		r.Response.StatusCode, r.Message)
   714  }
   715  
   716  // sanitizeURL redacts the client_secret parameter from the URL which may be
   717  // exposed to the user.
   718  func sanitizeURL(uri *url.URL) *url.URL {
   719  	if uri == nil {
   720  		return nil
   721  	}
   722  	params := uri.Query()
   723  	if len(params.Get("client_secret")) > 0 {
   724  		params.Set("client_secret", "REDACTED")
   725  		uri.RawQuery = params.Encode()
   726  	}
   727  	return uri
   728  }
   729  
   730  /*
   731  An Error reports more details on an individual error in an ErrorResponse.
   732  These are the possible validation error codes:
   733  
   734      missing:
   735          resource does not exist
   736      missing_field:
   737          a required field on a resource has not been set
   738      invalid:
   739          the formatting of a field is invalid
   740      already_exists:
   741          another resource has the same valid as this field
   742      custom:
   743          some resources return this (e.g. github.User.CreateKey()), additional
   744          information is set in the Message field of the Error
   745  
   746  GitHub error responses structure are often undocumented and inconsistent.
   747  Sometimes error is just a simple string (Issue #540).
   748  In such cases, Message represents an error message as a workaround.
   749  
   750  GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/#client-errors
   751  */
   752  type Error struct {
   753  	Resource string `json:"resource"` // resource on which the error occurred
   754  	Field    string `json:"field"`    // field on which the error occurred
   755  	Code     string `json:"code"`     // validation error code
   756  	Message  string `json:"message"`  // Message describing the error. Errors with Code == "custom" will always have this set.
   757  }
   758  
   759  func (e *Error) Error() string {
   760  	return fmt.Sprintf("%v error caused by %v field on %v resource",
   761  		e.Code, e.Field, e.Resource)
   762  }
   763  
   764  func (e *Error) UnmarshalJSON(data []byte) error {
   765  	type aliasError Error // avoid infinite recursion by using type alias.
   766  	if err := json.Unmarshal(data, (*aliasError)(e)); err != nil {
   767  		return json.Unmarshal(data, &e.Message) // data can be json string.
   768  	}
   769  	return nil
   770  }
   771  
   772  // CheckResponse checks the API response for errors, and returns them if
   773  // present. A response is considered an error if it has a status code outside
   774  // the 200 range or equal to 202 Accepted.
   775  // API error responses are expected to have response
   776  // body, and a JSON response body that maps to ErrorResponse.
   777  //
   778  // The error type will be *RateLimitError for rate limit exceeded errors,
   779  // *AcceptedError for 202 Accepted status codes,
   780  // and *TwoFactorAuthError for two-factor authentication errors.
   781  func CheckResponse(r *http.Response) error {
   782  	if r.StatusCode == http.StatusAccepted {
   783  		return &AcceptedError{}
   784  	}
   785  	if c := r.StatusCode; 200 <= c && c <= 299 {
   786  		return nil
   787  	}
   788  	errorResponse := &ErrorResponse{Response: r}
   789  	data, err := ioutil.ReadAll(r.Body)
   790  	if err == nil && data != nil {
   791  		json.Unmarshal(data, errorResponse)
   792  	}
   793  	// Re-populate error response body because GitHub error responses are often
   794  	// undocumented and inconsistent.
   795  	// Issue #1136, #540.
   796  	r.Body = ioutil.NopCloser(bytes.NewBuffer(data))
   797  	switch {
   798  	case r.StatusCode == http.StatusUnauthorized && strings.HasPrefix(r.Header.Get(headerOTP), "required"):
   799  		return (*TwoFactorAuthError)(errorResponse)
   800  	case r.StatusCode == http.StatusForbidden && r.Header.Get(headerRateRemaining) == "0":
   801  		return &RateLimitError{
   802  			Rate:     parseRate(r),
   803  			Response: errorResponse.Response,
   804  			Message:  errorResponse.Message,
   805  		}
   806  	case r.StatusCode == http.StatusForbidden && strings.HasSuffix(errorResponse.DocumentationURL, "#abuse-rate-limits"):
   807  		abuseRateLimitError := &AbuseRateLimitError{
   808  			Response: errorResponse.Response,
   809  			Message:  errorResponse.Message,
   810  		}
   811  		if v := r.Header["Retry-After"]; len(v) > 0 {
   812  			// According to GitHub support, the "Retry-After" header value will be
   813  			// an integer which represents the number of seconds that one should
   814  			// wait before resuming making requests.
   815  			retryAfterSeconds, _ := strconv.ParseInt(v[0], 10, 64) // Error handling is noop.
   816  			retryAfter := time.Duration(retryAfterSeconds) * time.Second
   817  			abuseRateLimitError.RetryAfter = &retryAfter
   818  		}
   819  		return abuseRateLimitError
   820  	default:
   821  		return errorResponse
   822  	}
   823  }
   824  
   825  // parseBoolResponse determines the boolean result from a GitHub API response.
   826  // Several GitHub API methods return boolean responses indicated by the HTTP
   827  // status code in the response (true indicated by a 204, false indicated by a
   828  // 404). This helper function will determine that result and hide the 404
   829  // error if present. Any other error will be returned through as-is.
   830  func parseBoolResponse(err error) (bool, error) {
   831  	if err == nil {
   832  		return true, nil
   833  	}
   834  
   835  	if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode == http.StatusNotFound {
   836  		// Simply false. In this one case, we do not pass the error through.
   837  		return false, nil
   838  	}
   839  
   840  	// some other real error occurred
   841  	return false, err
   842  }
   843  
   844  // Rate represents the rate limit for the current client.
   845  type Rate struct {
   846  	// The number of requests per hour the client is currently limited to.
   847  	Limit int `json:"limit"`
   848  
   849  	// The number of remaining requests the client can make this hour.
   850  	Remaining int `json:"remaining"`
   851  
   852  	// The time at which the current rate limit will reset.
   853  	Reset Timestamp `json:"reset"`
   854  }
   855  
   856  func (r Rate) String() string {
   857  	return Stringify(r)
   858  }
   859  
   860  // RateLimits represents the rate limits for the current client.
   861  type RateLimits struct {
   862  	// The rate limit for non-search API requests. Unauthenticated
   863  	// requests are limited to 60 per hour. Authenticated requests are
   864  	// limited to 5,000 per hour.
   865  	//
   866  	// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/#rate-limiting
   867  	Core *Rate `json:"core"`
   868  
   869  	// The rate limit for search API requests. Unauthenticated requests
   870  	// are limited to 10 requests per minutes. Authenticated requests are
   871  	// limited to 30 per minute.
   872  	//
   873  	// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/search/#rate-limit
   874  	Search *Rate `json:"search"`
   875  }
   876  
   877  func (r RateLimits) String() string {
   878  	return Stringify(r)
   879  }
   880  
   881  type rateLimitCategory uint8
   882  
   883  const (
   884  	coreCategory rateLimitCategory = iota
   885  	searchCategory
   886  
   887  	categories // An array of this length will be able to contain all rate limit categories.
   888  )
   889  
   890  // category returns the rate limit category of the endpoint, determined by Request.URL.Path.
   891  func category(path string) rateLimitCategory {
   892  	switch {
   893  	default:
   894  		return coreCategory
   895  	case strings.HasPrefix(path, "/search/"):
   896  		return searchCategory
   897  	}
   898  }
   899  
   900  // RateLimits returns the rate limits for the current client.
   901  func (c *Client) RateLimits(ctx context.Context) (*RateLimits, *Response, error) {
   902  	req, err := c.NewRequest("GET", "rate_limit", nil)
   903  	if err != nil {
   904  		return nil, nil, err
   905  	}
   906  
   907  	response := new(struct {
   908  		Resources *RateLimits `json:"resources"`
   909  	})
   910  	resp, err := c.Do(ctx, req, response)
   911  	if err != nil {
   912  		return nil, nil, err
   913  	}
   914  
   915  	if response.Resources != nil {
   916  		c.rateMu.Lock()
   917  		if response.Resources.Core != nil {
   918  			c.rateLimits[coreCategory] = *response.Resources.Core
   919  		}
   920  		if response.Resources.Search != nil {
   921  			c.rateLimits[searchCategory] = *response.Resources.Search
   922  		}
   923  		c.rateMu.Unlock()
   924  	}
   925  
   926  	return response.Resources, resp, nil
   927  }
   928  
   929  func setCredentialsAsHeaders(req *http.Request, id, secret string) *http.Request {
   930  	// To set extra headers, we must make a copy of the Request so
   931  	// that we don't modify the Request we were given. This is required by the
   932  	// specification of http.RoundTripper.
   933  	//
   934  	// Since we are going to modify only req.Header here, we only need a deep copy
   935  	// of req.Header.
   936  	convertedRequest := new(http.Request)
   937  	*convertedRequest = *req
   938  	convertedRequest.Header = make(http.Header, len(req.Header))
   939  
   940  	for k, s := range req.Header {
   941  		convertedRequest.Header[k] = append([]string(nil), s...)
   942  	}
   943  	convertedRequest.SetBasicAuth(id, secret)
   944  	return convertedRequest
   945  }
   946  
   947  /*
   948  UnauthenticatedRateLimitedTransport allows you to make unauthenticated calls
   949  that need to use a higher rate limit associated with your OAuth application.
   950  
   951  	t := &github.UnauthenticatedRateLimitedTransport{
   952  		ClientID:     "your app's client ID",
   953  		ClientSecret: "your app's client secret",
   954  	}
   955  	client := github.NewClient(t.Client())
   956  
   957  This will add the client id and secret as a base64-encoded string in the format
   958  ClientID:ClientSecret and apply it as an "Authorization": "Basic" header.
   959  
   960  See https://docs.github.com/en/free-pro-team@latest/rest/reference/#unauthenticated-rate-limited-requests for
   961  more information.
   962  */
   963  type UnauthenticatedRateLimitedTransport struct {
   964  	// ClientID is the GitHub OAuth client ID of the current application, which
   965  	// can be found by selecting its entry in the list at
   966  	// https://github.com/settings/applications.
   967  	ClientID string
   968  
   969  	// ClientSecret is the GitHub OAuth client secret of the current
   970  	// application.
   971  	ClientSecret string
   972  
   973  	// Transport is the underlying HTTP transport to use when making requests.
   974  	// It will default to http.DefaultTransport if nil.
   975  	Transport http.RoundTripper
   976  }
   977  
   978  // RoundTrip implements the RoundTripper interface.
   979  func (t *UnauthenticatedRateLimitedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
   980  	if t.ClientID == "" {
   981  		return nil, errors.New("t.ClientID is empty")
   982  	}
   983  	if t.ClientSecret == "" {
   984  		return nil, errors.New("t.ClientSecret is empty")
   985  	}
   986  
   987  	req2 := setCredentialsAsHeaders(req, t.ClientID, t.ClientSecret)
   988  	// Make the HTTP request.
   989  	return t.transport().RoundTrip(req2)
   990  }
   991  
   992  // Client returns an *http.Client that makes requests which are subject to the
   993  // rate limit of your OAuth application.
   994  func (t *UnauthenticatedRateLimitedTransport) Client() *http.Client {
   995  	return &http.Client{Transport: t}
   996  }
   997  
   998  func (t *UnauthenticatedRateLimitedTransport) transport() http.RoundTripper {
   999  	if t.Transport != nil {
  1000  		return t.Transport
  1001  	}
  1002  	return http.DefaultTransport
  1003  }
  1004  
  1005  // BasicAuthTransport is an http.RoundTripper that authenticates all requests
  1006  // using HTTP Basic Authentication with the provided username and password. It
  1007  // additionally supports users who have two-factor authentication enabled on
  1008  // their GitHub account.
  1009  type BasicAuthTransport struct {
  1010  	Username string // GitHub username
  1011  	Password string // GitHub password
  1012  	OTP      string // one-time password for users with two-factor auth enabled
  1013  
  1014  	// Transport is the underlying HTTP transport to use when making requests.
  1015  	// It will default to http.DefaultTransport if nil.
  1016  	Transport http.RoundTripper
  1017  }
  1018  
  1019  // RoundTrip implements the RoundTripper interface.
  1020  func (t *BasicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
  1021  	req2 := setCredentialsAsHeaders(req, t.Username, t.Password)
  1022  	if t.OTP != "" {
  1023  		req2.Header.Set(headerOTP, t.OTP)
  1024  	}
  1025  	return t.transport().RoundTrip(req2)
  1026  }
  1027  
  1028  // Client returns an *http.Client that makes requests that are authenticated
  1029  // using HTTP Basic Authentication.
  1030  func (t *BasicAuthTransport) Client() *http.Client {
  1031  	return &http.Client{Transport: t}
  1032  }
  1033  
  1034  func (t *BasicAuthTransport) transport() http.RoundTripper {
  1035  	if t.Transport != nil {
  1036  		return t.Transport
  1037  	}
  1038  	return http.DefaultTransport
  1039  }
  1040  
  1041  // formatRateReset formats d to look like "[rate reset in 2s]" or
  1042  // "[rate reset in 87m02s]" for the positive durations. And like "[rate limit was reset 87m02s ago]"
  1043  // for the negative cases.
  1044  func formatRateReset(d time.Duration) string {
  1045  	isNegative := d < 0
  1046  	if isNegative {
  1047  		d *= -1
  1048  	}
  1049  	secondsTotal := int(0.5 + d.Seconds())
  1050  	minutes := secondsTotal / 60
  1051  	seconds := secondsTotal - minutes*60
  1052  
  1053  	var timeString string
  1054  	if minutes > 0 {
  1055  		timeString = fmt.Sprintf("%dm%02ds", minutes, seconds)
  1056  	} else {
  1057  		timeString = fmt.Sprintf("%ds", seconds)
  1058  	}
  1059  
  1060  	if isNegative {
  1061  		return fmt.Sprintf("[rate limit was reset %v ago]", timeString)
  1062  	}
  1063  	return fmt.Sprintf("[rate reset in %v]", timeString)
  1064  }
  1065  
  1066  // Bool is a helper routine that allocates a new bool value
  1067  // to store v and returns a pointer to it.
  1068  func Bool(v bool) *bool { return &v }
  1069  
  1070  // Int is a helper routine that allocates a new int value
  1071  // to store v and returns a pointer to it.
  1072  func Int(v int) *int { return &v }
  1073  
  1074  // Int64 is a helper routine that allocates a new int64 value
  1075  // to store v and returns a pointer to it.
  1076  func Int64(v int64) *int64 { return &v }
  1077  
  1078  // String is a helper routine that allocates a new string value
  1079  // to store v and returns a pointer to it.
  1080  func String(v string) *string { return &v }