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
8package github
9
10import (
11	"bytes"
12	"context"
13	"encoding/json"
14	"errors"
15	"fmt"
16	"io"
17	"io/ioutil"
18	"net/http"
19	"net/url"
20	"reflect"
21	"strconv"
22	"strings"
23	"sync"
24	"time"
25
26	"github.com/google/go-querystring/query"
27)
28
29const (
30	libraryVersion = "8"
31	defaultBaseURL = "https://api.github.com/"
32	uploadBaseURL  = "https://uploads.github.com/"
33	userAgent      = "go-github/" + libraryVersion
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
47	// Media Type values to access preview APIs
48
49	// https://developer.github.com/changes/2015-03-09-licenses-api/
50	mediaTypeLicensesPreview = "application/vnd.github.drax-preview+json"
51
52	// https://developer.github.com/changes/2014-12-09-new-attributes-for-stars-api/
53	mediaTypeStarringPreview = "application/vnd.github.v3.star+json"
54
55	// https://developer.github.com/changes/2015-11-11-protected-branches-api/
56	mediaTypeProtectedBranchesPreview = "application/vnd.github.loki-preview+json"
57
58	// https://help.github.com/enterprise/2.4/admin/guides/migrations/exporting-the-github-com-organization-s-repositories/
59	mediaTypeMigrationsPreview = "application/vnd.github.wyandotte-preview+json"
60
61	// https://developer.github.com/changes/2016-04-06-deployment-and-deployment-status-enhancements/
62	mediaTypeDeploymentStatusPreview = "application/vnd.github.ant-man-preview+json"
63
64	// https://developer.github.com/changes/2016-02-19-source-import-preview-api/
65	mediaTypeImportPreview = "application/vnd.github.barred-rock-preview"
66
67	// https://developer.github.com/changes/2016-05-12-reactions-api-preview/
68	mediaTypeReactionsPreview = "application/vnd.github.squirrel-girl-preview"
69
70	// https://developer.github.com/changes/2016-04-01-squash-api-preview/
71	// https://developer.github.com/changes/2016-09-26-pull-request-merge-api-update/
72	mediaTypeSquashPreview = "application/vnd.github.polaris-preview+json"
73
74	// https://developer.github.com/changes/2016-04-04-git-signing-api-preview/
75	mediaTypeGitSigningPreview = "application/vnd.github.cryptographer-preview+json"
76
77	// https://developer.github.com/changes/2016-05-23-timeline-preview-api/
78	mediaTypeTimelinePreview = "application/vnd.github.mockingbird-preview+json"
79
80	// https://developer.github.com/changes/2016-06-14-repository-invitations/
81	mediaTypeRepositoryInvitationsPreview = "application/vnd.github.swamp-thing-preview+json"
82
83	// https://developer.github.com/changes/2016-07-06-github-pages-preiew-api/
84	mediaTypePagesPreview = "application/vnd.github.mister-fantastic-preview+json"
85
86	// https://developer.github.com/changes/2016-09-14-projects-api/
87	mediaTypeProjectsPreview = "application/vnd.github.inertia-preview+json"
88
89	// https://developer.github.com/changes/2016-09-14-Integrations-Early-Access/
90	mediaTypeIntegrationPreview = "application/vnd.github.machine-man-preview+json"
91
92	// https://developer.github.com/changes/2017-01-05-commit-search-api/
93	mediaTypeCommitSearchPreview = "application/vnd.github.cloak-preview+json"
94
95	// https://developer.github.com/changes/2017-02-28-user-blocking-apis-and-webhook/
96	mediaTypeBlockUsersPreview = "application/vnd.github.giant-sentry-fist-preview+json"
97
98	// https://developer.github.com/changes/2017-02-09-community-health/
99	mediaTypeRepositoryCommunityHealthMetricsPreview = "application/vnd.github.black-panther-preview+json"
100
101	// https://developer.github.com/changes/2017-05-23-coc-api/
102	mediaTypeCodesOfConductPreview = "application/vnd.github.scarlet-witch-preview+json"
103)
104
105// A Client manages communication with the GitHub API.
106type Client struct {
107	clientMu sync.Mutex   // clientMu protects the client during calls that modify the CheckRedirect func.
108	client   *http.Client // HTTP client used to communicate with the API.
109
110	// Base URL for API requests. Defaults to the public GitHub API, but can be
111	// set to a domain endpoint to use with GitHub Enterprise. BaseURL should
112	// always be specified with a trailing slash.
113	BaseURL *url.URL
114
115	// Base URL for uploading files.
116	UploadURL *url.URL
117
118	// User agent used when communicating with the GitHub API.
119	UserAgent string
120
121	rateMu     sync.Mutex
122	rateLimits [categories]Rate // Rate limits for the client as determined by the most recent API calls.
123
124	common service // Reuse a single struct instead of allocating one for each service on the heap.
125
126	// Services used for talking to different parts of the GitHub API.
127	Activity       *ActivityService
128	Admin          *AdminService
129	Apps           *AppsService
130	Authorizations *AuthorizationsService
131	Gists          *GistsService
132	Git            *GitService
133	Gitignores     *GitignoresService
134	Issues         *IssuesService
135	Organizations  *OrganizationsService
136	Projects       *ProjectsService
137	PullRequests   *PullRequestsService
138	Repositories   *RepositoriesService
139	Search         *SearchService
140	Users          *UsersService
141	Licenses       *LicensesService
142	Migrations     *MigrationService
143	Reactions      *ReactionsService
144}
145
146type service struct {
147	client *Client
148}
149
150// ListOptions specifies the optional parameters to various List methods that
151// support pagination.
152type ListOptions struct {
153	// For paginated result sets, page of results to retrieve.
154	Page int `url:"page,omitempty"`
155
156	// For paginated result sets, the number of results to include per page.
157	PerPage int `url:"per_page,omitempty"`
158}
159
160// UploadOptions specifies the parameters to methods that support uploads.
161type UploadOptions struct {
162	Name string `url:"name,omitempty"`
163}
164
165// RawType represents type of raw format of a request instead of JSON.
166type RawType uint8
167
168const (
169	// Diff format.
170	Diff RawType = 1 + iota
171	// Patch format.
172	Patch
173)
174
175// RawOptions specifies parameters when user wants to get raw format of
176// a response instead of JSON.
177type RawOptions struct {
178	Type RawType
179}
180
181// addOptions adds the parameters in opt as URL query parameters to s. opt
182// must be a struct whose fields may contain "url" tags.
183func addOptions(s string, opt interface{}) (string, error) {
184	v := reflect.ValueOf(opt)
185	if v.Kind() == reflect.Ptr && v.IsNil() {
186		return s, nil
187	}
188
189	u, err := url.Parse(s)
190	if err != nil {
191		return s, err
192	}
193
194	qs, err := query.Values(opt)
195	if err != nil {
196		return s, err
197	}
198
199	u.RawQuery = qs.Encode()
200	return u.String(), nil
201}
202
203// NewClient returns a new GitHub API client. If a nil httpClient is
204// provided, http.DefaultClient will be used. To use API methods which require
205// authentication, provide an http.Client that will perform the authentication
206// for you (such as that provided by the golang.org/x/oauth2 library).
207func NewClient(httpClient *http.Client) *Client {
208	if httpClient == nil {
209		httpClient = http.DefaultClient
210	}
211	baseURL, _ := url.Parse(defaultBaseURL)
212	uploadURL, _ := url.Parse(uploadBaseURL)
213
214	c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: userAgent, UploadURL: uploadURL}
215	c.common.client = c
216	c.Activity = (*ActivityService)(&c.common)
217	c.Admin = (*AdminService)(&c.common)
218	c.Apps = (*AppsService)(&c.common)
219	c.Authorizations = (*AuthorizationsService)(&c.common)
220	c.Gists = (*GistsService)(&c.common)
221	c.Git = (*GitService)(&c.common)
222	c.Gitignores = (*GitignoresService)(&c.common)
223	c.Issues = (*IssuesService)(&c.common)
224	c.Licenses = (*LicensesService)(&c.common)
225	c.Migrations = (*MigrationService)(&c.common)
226	c.Organizations = (*OrganizationsService)(&c.common)
227	c.Projects = (*ProjectsService)(&c.common)
228	c.PullRequests = (*PullRequestsService)(&c.common)
229	c.Reactions = (*ReactionsService)(&c.common)
230	c.Repositories = (*RepositoriesService)(&c.common)
231	c.Search = (*SearchService)(&c.common)
232	c.Users = (*UsersService)(&c.common)
233	return c
234}
235
236// NewRequest creates an API request. A relative URL can be provided in urlStr,
237// in which case it is resolved relative to the BaseURL of the Client.
238// Relative URLs should always be specified without a preceding slash. If
239// specified, the value pointed to by body is JSON encoded and included as the
240// request body.
241func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
242	rel, err := url.Parse(urlStr)
243	if err != nil {
244		return nil, err
245	}
246
247	u := c.BaseURL.ResolveReference(rel)
248
249	var buf io.ReadWriter
250	if body != nil {
251		buf = new(bytes.Buffer)
252		err := json.NewEncoder(buf).Encode(body)
253		if err != nil {
254			return nil, err
255		}
256	}
257
258	req, err := http.NewRequest(method, u.String(), buf)
259	if err != nil {
260		return nil, err
261	}
262
263	if body != nil {
264		req.Header.Set("Content-Type", "application/json")
265	}
266	req.Header.Set("Accept", mediaTypeV3)
267	if c.UserAgent != "" {
268		req.Header.Set("User-Agent", c.UserAgent)
269	}
270	return req, nil
271}
272
273// NewUploadRequest creates an upload request. A relative URL can be provided in
274// urlStr, in which case it is resolved relative to the UploadURL of the Client.
275// Relative URLs should always be specified without a preceding slash.
276func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, size int64, mediaType string) (*http.Request, error) {
277	rel, err := url.Parse(urlStr)
278	if err != nil {
279		return nil, err
280	}
281
282	u := c.UploadURL.ResolveReference(rel)
283	req, err := http.NewRequest("POST", u.String(), reader)
284	if err != nil {
285		return nil, err
286	}
287	req.ContentLength = size
288
289	if mediaType == "" {
290		mediaType = defaultMediaType
291	}
292	req.Header.Set("Content-Type", mediaType)
293	req.Header.Set("Accept", mediaTypeV3)
294	req.Header.Set("User-Agent", c.UserAgent)
295	return req, nil
296}
297
298// Response is a GitHub API response. This wraps the standard http.Response
299// returned from GitHub and provides convenient access to things like
300// pagination links.
301type Response struct {
302	*http.Response
303
304	// These fields provide the page values for paginating through a set of
305	// results. Any or all of these may be set to the zero value for
306	// responses that are not part of a paginated set, or for which there
307	// are no additional pages.
308
309	NextPage  int
310	PrevPage  int
311	FirstPage int
312	LastPage  int
313
314	Rate
315}
316
317// newResponse creates a new Response for the provided http.Response.
318func newResponse(r *http.Response) *Response {
319	response := &Response{Response: r}
320	response.populatePageValues()
321	response.Rate = parseRate(r)
322	return response
323}
324
325// populatePageValues parses the HTTP Link response headers and populates the
326// various pagination link values in the Response.
327func (r *Response) populatePageValues() {
328	if links, ok := r.Response.Header["Link"]; ok && len(links) > 0 {
329		for _, link := range strings.Split(links[0], ",") {
330			segments := strings.Split(strings.TrimSpace(link), ";")
331
332			// link must at least have href and rel
333			if len(segments) < 2 {
334				continue
335			}
336
337			// ensure href is properly formatted
338			if !strings.HasPrefix(segments[0], "<") || !strings.HasSuffix(segments[0], ">") {
339				continue
340			}
341
342			// try to pull out page parameter
343			url, err := url.Parse(segments[0][1 : len(segments[0])-1])
344			if err != nil {
345				continue
346			}
347			page := url.Query().Get("page")
348			if page == "" {
349				continue
350			}
351
352			for _, segment := range segments[1:] {
353				switch strings.TrimSpace(segment) {
354				case `rel="next"`:
355					r.NextPage, _ = strconv.Atoi(page)
356				case `rel="prev"`:
357					r.PrevPage, _ = strconv.Atoi(page)
358				case `rel="first"`:
359					r.FirstPage, _ = strconv.Atoi(page)
360				case `rel="last"`:
361					r.LastPage, _ = strconv.Atoi(page)
362				}
363
364			}
365		}
366	}
367}
368
369// parseRate parses the rate related headers.
370func parseRate(r *http.Response) Rate {
371	var rate Rate
372	if limit := r.Header.Get(headerRateLimit); limit != "" {
373		rate.Limit, _ = strconv.Atoi(limit)
374	}
375	if remaining := r.Header.Get(headerRateRemaining); remaining != "" {
376		rate.Remaining, _ = strconv.Atoi(remaining)
377	}
378	if reset := r.Header.Get(headerRateReset); reset != "" {
379		if v, _ := strconv.ParseInt(reset, 10, 64); v != 0 {
380			rate.Reset = Timestamp{time.Unix(v, 0)}
381		}
382	}
383	return rate
384}
385
386// Do sends an API request and returns the API response. The API response is
387// JSON decoded and stored in the value pointed to by v, or returned as an
388// error if an API error has occurred. If v implements the io.Writer
389// interface, the raw response body will be written to v, without attempting to
390// first decode it. If rate limit is exceeded and reset time is in the future,
391// Do returns *RateLimitError immediately without making a network API call.
392//
393// The provided ctx must be non-nil. If it is canceled or times out,
394// ctx.Err() will be returned.
395func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
396	ctx, req = withContext(ctx, req)
397
398	rateLimitCategory := category(req.URL.Path)
399
400	// If we've hit rate limit, don't make further requests before Reset time.
401	if err := c.checkRateLimitBeforeDo(req, rateLimitCategory); err != nil {
402		return &Response{
403			Response: err.Response,
404			Rate:     err.Rate,
405		}, err
406	}
407
408	resp, err := c.client.Do(req)
409	if err != nil {
410		// If we got an error, and the context has been canceled,
411		// the context's error is probably more useful.
412		select {
413		case <-ctx.Done():
414			return nil, ctx.Err()
415		default:
416		}
417
418		// If the error type is *url.Error, sanitize its URL before returning.
419		if e, ok := err.(*url.Error); ok {
420			if url, err := url.Parse(e.URL); err == nil {
421				e.URL = sanitizeURL(url).String()
422				return nil, e
423			}
424		}
425
426		return nil, err
427	}
428
429	defer func() {
430		// Drain up to 512 bytes and close the body to let the Transport reuse the connection
431		io.CopyN(ioutil.Discard, resp.Body, 512)
432		resp.Body.Close()
433	}()
434
435	response := newResponse(resp)
436
437	c.rateMu.Lock()
438	c.rateLimits[rateLimitCategory] = response.Rate
439	c.rateMu.Unlock()
440
441	err = CheckResponse(resp)
442	if err != nil {
443		// even though there was an error, we still return the response
444		// in case the caller wants to inspect it further
445		return response, err
446	}
447
448	if v != nil {
449		if w, ok := v.(io.Writer); ok {
450			io.Copy(w, resp.Body)
451		} else {
452			err = json.NewDecoder(resp.Body).Decode(v)
453			if err == io.EOF {
454				err = nil // ignore EOF errors caused by empty response body
455			}
456		}
457	}
458
459	return response, err
460}
461
462// checkRateLimitBeforeDo does not make any network calls, but uses existing knowledge from
463// current client state in order to quickly check if *RateLimitError can be immediately returned
464// from Client.Do, and if so, returns it so that Client.Do can skip making a network API call unnecessarily.
465// Otherwise it returns nil, and Client.Do should proceed normally.
466func (c *Client) checkRateLimitBeforeDo(req *http.Request, rateLimitCategory rateLimitCategory) *RateLimitError {
467	c.rateMu.Lock()
468	rate := c.rateLimits[rateLimitCategory]
469	c.rateMu.Unlock()
470	if !rate.Reset.Time.IsZero() && rate.Remaining == 0 && time.Now().Before(rate.Reset.Time) {
471		// Create a fake response.
472		resp := &http.Response{
473			Status:     http.StatusText(http.StatusForbidden),
474			StatusCode: http.StatusForbidden,
475			Request:    req,
476			Header:     make(http.Header),
477			Body:       ioutil.NopCloser(strings.NewReader("")),
478		}
479		return &RateLimitError{
480			Rate:     rate,
481			Response: resp,
482			Message:  fmt.Sprintf("API rate limit of %v still exceeded until %v, not making remote request.", rate.Limit, rate.Reset.Time),
483		}
484	}
485
486	return nil
487}
488
489/*
490An ErrorResponse reports one or more errors caused by an API request.
491
492GitHub API docs: https://developer.github.com/v3/#client-errors
493*/
494type ErrorResponse struct {
495	Response *http.Response // HTTP response that caused this error
496	Message  string         `json:"message"` // error message
497	Errors   []Error        `json:"errors"`  // more detail on individual errors
498	// Block is only populated on certain types of errors such as code 451.
499	// See https://developer.github.com/changes/2016-03-17-the-451-status-code-is-now-supported/
500	// for more information.
501	Block *struct {
502		Reason    string     `json:"reason,omitempty"`
503		CreatedAt *Timestamp `json:"created_at,omitempty"`
504	} `json:"block,omitempty"`
505	// Most errors will also include a documentation_url field pointing
506	// to some content that might help you resolve the error, see
507	// https://developer.github.com/v3/#client-errors
508	DocumentationURL string `json:"documentation_url,omitempty"`
509}
510
511func (r *ErrorResponse) Error() string {
512	return fmt.Sprintf("%v %v: %d %v %+v",
513		r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
514		r.Response.StatusCode, r.Message, r.Errors)
515}
516
517// TwoFactorAuthError occurs when using HTTP Basic Authentication for a user
518// that has two-factor authentication enabled. The request can be reattempted
519// by providing a one-time password in the request.
520type TwoFactorAuthError ErrorResponse
521
522func (r *TwoFactorAuthError) Error() string { return (*ErrorResponse)(r).Error() }
523
524// RateLimitError occurs when GitHub returns 403 Forbidden response with a rate limit
525// remaining value of 0, and error message starts with "API rate limit exceeded for ".
526type RateLimitError struct {
527	Rate     Rate           // Rate specifies last known rate limit for the client
528	Response *http.Response // HTTP response that caused this error
529	Message  string         `json:"message"` // error message
530}
531
532func (r *RateLimitError) Error() string {
533	return fmt.Sprintf("%v %v: %d %v; rate reset in %v",
534		r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
535		r.Response.StatusCode, r.Message, r.Rate.Reset.Time.Sub(time.Now()))
536}
537
538// AcceptedError occurs when GitHub returns 202 Accepted response with an
539// empty body, which means a job was scheduled on the GitHub side to process
540// the information needed and cache it.
541// Technically, 202 Accepted is not a real error, it's just used to
542// indicate that results are not ready yet, but should be available soon.
543// The request can be repeated after some time.
544type AcceptedError struct{}
545
546func (*AcceptedError) Error() string {
547	return "job scheduled on GitHub side; try again later"
548}
549
550// AbuseRateLimitError occurs when GitHub returns 403 Forbidden response with the
551// "documentation_url" field value equal to "https://developer.github.com/v3#abuse-rate-limits".
552type AbuseRateLimitError struct {
553	Response *http.Response // HTTP response that caused this error
554	Message  string         `json:"message"` // error message
555
556	// RetryAfter is provided with some abuse rate limit errors. If present,
557	// it is the amount of time that the client should wait before retrying.
558	// Otherwise, the client should try again later (after an unspecified amount of time).
559	RetryAfter *time.Duration
560}
561
562func (r *AbuseRateLimitError) Error() string {
563	return fmt.Sprintf("%v %v: %d %v",
564		r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
565		r.Response.StatusCode, r.Message)
566}
567
568// sanitizeURL redacts the client_secret parameter from the URL which may be
569// exposed to the user.
570func sanitizeURL(uri *url.URL) *url.URL {
571	if uri == nil {
572		return nil
573	}
574	params := uri.Query()
575	if len(params.Get("client_secret")) > 0 {
576		params.Set("client_secret", "REDACTED")
577		uri.RawQuery = params.Encode()
578	}
579	return uri
580}
581
582/*
583An Error reports more details on an individual error in an ErrorResponse.
584These are the possible validation error codes:
585
586    missing:
587        resource does not exist
588    missing_field:
589        a required field on a resource has not been set
590    invalid:
591        the formatting of a field is invalid
592    already_exists:
593        another resource has the same valid as this field
594    custom:
595        some resources return this (e.g. github.User.CreateKey()), additional
596        information is set in the Message field of the Error
597
598GitHub API docs: https://developer.github.com/v3/#client-errors
599*/
600type Error struct {
601	Resource string `json:"resource"` // resource on which the error occurred
602	Field    string `json:"field"`    // field on which the error occurred
603	Code     string `json:"code"`     // validation error code
604	Message  string `json:"message"`  // Message describing the error. Errors with Code == "custom" will always have this set.
605}
606
607func (e *Error) Error() string {
608	return fmt.Sprintf("%v error caused by %v field on %v resource",
609		e.Code, e.Field, e.Resource)
610}
611
612// CheckResponse checks the API response for errors, and returns them if
613// present. A response is considered an error if it has a status code outside
614// the 200 range or equal to 202 Accepted.
615// API error responses are expected to have either no response
616// body, or a JSON response body that maps to ErrorResponse. Any other
617// response body will be silently ignored.
618//
619// The error type will be *RateLimitError for rate limit exceeded errors,
620// *AcceptedError for 202 Accepted status codes,
621// and *TwoFactorAuthError for two-factor authentication errors.
622func CheckResponse(r *http.Response) error {
623	if r.StatusCode == http.StatusAccepted {
624		return &AcceptedError{}
625	}
626	if c := r.StatusCode; 200 <= c && c <= 299 {
627		return nil
628	}
629	errorResponse := &ErrorResponse{Response: r}
630	data, err := ioutil.ReadAll(r.Body)
631	if err == nil && data != nil {
632		json.Unmarshal(data, errorResponse)
633	}
634	switch {
635	case r.StatusCode == http.StatusUnauthorized && strings.HasPrefix(r.Header.Get(headerOTP), "required"):
636		return (*TwoFactorAuthError)(errorResponse)
637	case r.StatusCode == http.StatusForbidden && r.Header.Get(headerRateRemaining) == "0" && strings.HasPrefix(errorResponse.Message, "API rate limit exceeded for "):
638		return &RateLimitError{
639			Rate:     parseRate(r),
640			Response: errorResponse.Response,
641			Message:  errorResponse.Message,
642		}
643	case r.StatusCode == http.StatusForbidden && errorResponse.DocumentationURL == "https://developer.github.com/v3#abuse-rate-limits":
644		abuseRateLimitError := &AbuseRateLimitError{
645			Response: errorResponse.Response,
646			Message:  errorResponse.Message,
647		}
648		if v := r.Header["Retry-After"]; len(v) > 0 {
649			// According to GitHub support, the "Retry-After" header value will be
650			// an integer which represents the number of seconds that one should
651			// wait before resuming making requests.
652			retryAfterSeconds, _ := strconv.ParseInt(v[0], 10, 64) // Error handling is noop.
653			retryAfter := time.Duration(retryAfterSeconds) * time.Second
654			abuseRateLimitError.RetryAfter = &retryAfter
655		}
656		return abuseRateLimitError
657	default:
658		return errorResponse
659	}
660}
661
662// parseBoolResponse determines the boolean result from a GitHub API response.
663// Several GitHub API methods return boolean responses indicated by the HTTP
664// status code in the response (true indicated by a 204, false indicated by a
665// 404). This helper function will determine that result and hide the 404
666// error if present. Any other error will be returned through as-is.
667func parseBoolResponse(err error) (bool, error) {
668	if err == nil {
669		return true, nil
670	}
671
672	if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode == http.StatusNotFound {
673		// Simply false. In this one case, we do not pass the error through.
674		return false, nil
675	}
676
677	// some other real error occurred
678	return false, err
679}
680
681// Rate represents the rate limit for the current client.
682type Rate struct {
683	// The number of requests per hour the client is currently limited to.
684	Limit int `json:"limit"`
685
686	// The number of remaining requests the client can make this hour.
687	Remaining int `json:"remaining"`
688
689	// The time at which the current rate limit will reset.
690	Reset Timestamp `json:"reset"`
691}
692
693func (r Rate) String() string {
694	return Stringify(r)
695}
696
697// RateLimits represents the rate limits for the current client.
698type RateLimits struct {
699	// The rate limit for non-search API requests. Unauthenticated
700	// requests are limited to 60 per hour. Authenticated requests are
701	// limited to 5,000 per hour.
702	//
703	// GitHub API docs: https://developer.github.com/v3/#rate-limiting
704	Core *Rate `json:"core"`
705
706	// The rate limit for search API requests. Unauthenticated requests
707	// are limited to 10 requests per minutes. Authenticated requests are
708	// limited to 30 per minute.
709	//
710	// GitHub API docs: https://developer.github.com/v3/search/#rate-limit
711	Search *Rate `json:"search"`
712}
713
714func (r RateLimits) String() string {
715	return Stringify(r)
716}
717
718type rateLimitCategory uint8
719
720const (
721	coreCategory rateLimitCategory = iota
722	searchCategory
723
724	categories // An array of this length will be able to contain all rate limit categories.
725)
726
727// category returns the rate limit category of the endpoint, determined by Request.URL.Path.
728func category(path string) rateLimitCategory {
729	switch {
730	default:
731		return coreCategory
732	case strings.HasPrefix(path, "/search/"):
733		return searchCategory
734	}
735}
736
737// RateLimits returns the rate limits for the current client.
738func (c *Client) RateLimits(ctx context.Context) (*RateLimits, *Response, error) {
739	req, err := c.NewRequest("GET", "rate_limit", nil)
740	if err != nil {
741		return nil, nil, err
742	}
743
744	response := new(struct {
745		Resources *RateLimits `json:"resources"`
746	})
747	resp, err := c.Do(ctx, req, response)
748	if err != nil {
749		return nil, nil, err
750	}
751
752	if response.Resources != nil {
753		c.rateMu.Lock()
754		if response.Resources.Core != nil {
755			c.rateLimits[coreCategory] = *response.Resources.Core
756		}
757		if response.Resources.Search != nil {
758			c.rateLimits[searchCategory] = *response.Resources.Search
759		}
760		c.rateMu.Unlock()
761	}
762
763	return response.Resources, resp, nil
764}
765
766/*
767UnauthenticatedRateLimitedTransport allows you to make unauthenticated calls
768that need to use a higher rate limit associated with your OAuth application.
769
770	t := &github.UnauthenticatedRateLimitedTransport{
771		ClientID:     "your app's client ID",
772		ClientSecret: "your app's client secret",
773	}
774	client := github.NewClient(t.Client())
775
776This will append the querystring params client_id=xxx&client_secret=yyy to all
777requests.
778
779See https://developer.github.com/v3/#unauthenticated-rate-limited-requests for
780more information.
781*/
782type UnauthenticatedRateLimitedTransport struct {
783	// ClientID is the GitHub OAuth client ID of the current application, which
784	// can be found by selecting its entry in the list at
785	// https://github.com/settings/applications.
786	ClientID string
787
788	// ClientSecret is the GitHub OAuth client secret of the current
789	// application.
790	ClientSecret string
791
792	// Transport is the underlying HTTP transport to use when making requests.
793	// It will default to http.DefaultTransport if nil.
794	Transport http.RoundTripper
795}
796
797// RoundTrip implements the RoundTripper interface.
798func (t *UnauthenticatedRateLimitedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
799	if t.ClientID == "" {
800		return nil, errors.New("t.ClientID is empty")
801	}
802	if t.ClientSecret == "" {
803		return nil, errors.New("t.ClientSecret is empty")
804	}
805
806	// To set extra querystring params, we must make a copy of the Request so
807	// that we don't modify the Request we were given. This is required by the
808	// specification of http.RoundTripper.
809	req = cloneRequest(req)
810	q := req.URL.Query()
811	q.Set("client_id", t.ClientID)
812	q.Set("client_secret", t.ClientSecret)
813	req.URL.RawQuery = q.Encode()
814
815	// Make the HTTP request.
816	return t.transport().RoundTrip(req)
817}
818
819// Client returns an *http.Client that makes requests which are subject to the
820// rate limit of your OAuth application.
821func (t *UnauthenticatedRateLimitedTransport) Client() *http.Client {
822	return &http.Client{Transport: t}
823}
824
825func (t *UnauthenticatedRateLimitedTransport) transport() http.RoundTripper {
826	if t.Transport != nil {
827		return t.Transport
828	}
829	return http.DefaultTransport
830}
831
832// BasicAuthTransport is an http.RoundTripper that authenticates all requests
833// using HTTP Basic Authentication with the provided username and password. It
834// additionally supports users who have two-factor authentication enabled on
835// their GitHub account.
836type BasicAuthTransport struct {
837	Username string // GitHub username
838	Password string // GitHub password
839	OTP      string // one-time password for users with two-factor auth enabled
840
841	// Transport is the underlying HTTP transport to use when making requests.
842	// It will default to http.DefaultTransport if nil.
843	Transport http.RoundTripper
844}
845
846// RoundTrip implements the RoundTripper interface.
847func (t *BasicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
848	req = cloneRequest(req) // per RoundTrip contract
849	req.SetBasicAuth(t.Username, t.Password)
850	if t.OTP != "" {
851		req.Header.Set(headerOTP, t.OTP)
852	}
853	return t.transport().RoundTrip(req)
854}
855
856// Client returns an *http.Client that makes requests that are authenticated
857// using HTTP Basic Authentication.
858func (t *BasicAuthTransport) Client() *http.Client {
859	return &http.Client{Transport: t}
860}
861
862func (t *BasicAuthTransport) transport() http.RoundTripper {
863	if t.Transport != nil {
864		return t.Transport
865	}
866	return http.DefaultTransport
867}
868
869// cloneRequest returns a clone of the provided *http.Request. The clone is a
870// shallow copy of the struct and its Header map.
871func cloneRequest(r *http.Request) *http.Request {
872	// shallow copy of the struct
873	r2 := new(http.Request)
874	*r2 = *r
875	// deep copy of the Header
876	r2.Header = make(http.Header, len(r.Header))
877	for k, s := range r.Header {
878		r2.Header[k] = append([]string(nil), s...)
879	}
880	return r2
881}
882
883// Bool is a helper routine that allocates a new bool value
884// to store v and returns a pointer to it.
885func Bool(v bool) *bool { return &v }
886
887// Int is a helper routine that allocates a new int value
888// to store v and returns a pointer to it.
889func Int(v int) *int { return &v }
890
891// String is a helper routine that allocates a new string value
892// to store v and returns a pointer to it.
893func String(v string) *string { return &v }
894