The GH_DEBUG environment variable is a new gh-specific verbosity control. For backwards-compatibility, DEBUG will still be respected if it has values "1", "true", "yes", and "api", but any other values will be ignored. Finally, support for "oauth" debug value has been dropped in favor of "api". The "oauth" value only had limited, internal use. Co-authored-by: Mislav Marohnić <mislav@github.com>
152 lines
4.6 KiB
Go
152 lines
4.6 KiB
Go
package factory
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/cli/cli/v2/api"
|
|
"github.com/cli/cli/v2/internal/ghinstance"
|
|
"github.com/cli/cli/v2/internal/httpunix"
|
|
"github.com/cli/cli/v2/pkg/iostreams"
|
|
"github.com/cli/cli/v2/utils"
|
|
)
|
|
|
|
var timezoneNames = map[int]string{
|
|
-39600: "Pacific/Niue",
|
|
-36000: "Pacific/Honolulu",
|
|
-34200: "Pacific/Marquesas",
|
|
-32400: "America/Anchorage",
|
|
-28800: "America/Los_Angeles",
|
|
-25200: "America/Chihuahua",
|
|
-21600: "America/Chicago",
|
|
-18000: "America/Bogota",
|
|
-14400: "America/Caracas",
|
|
-12600: "America/St_Johns",
|
|
-10800: "America/Argentina/Buenos_Aires",
|
|
-7200: "Atlantic/South_Georgia",
|
|
-3600: "Atlantic/Cape_Verde",
|
|
0: "Europe/London",
|
|
3600: "Europe/Amsterdam",
|
|
7200: "Europe/Athens",
|
|
10800: "Europe/Istanbul",
|
|
12600: "Asia/Tehran",
|
|
14400: "Asia/Dubai",
|
|
16200: "Asia/Kabul",
|
|
18000: "Asia/Tashkent",
|
|
19800: "Asia/Kolkata",
|
|
20700: "Asia/Kathmandu",
|
|
21600: "Asia/Dhaka",
|
|
23400: "Asia/Rangoon",
|
|
25200: "Asia/Bangkok",
|
|
28800: "Asia/Manila",
|
|
31500: "Australia/Eucla",
|
|
32400: "Asia/Tokyo",
|
|
34200: "Australia/Darwin",
|
|
36000: "Australia/Brisbane",
|
|
37800: "Australia/Adelaide",
|
|
39600: "Pacific/Guadalcanal",
|
|
43200: "Pacific/Nauru",
|
|
46800: "Pacific/Auckland",
|
|
49500: "Pacific/Chatham",
|
|
50400: "Pacific/Kiritimati",
|
|
}
|
|
|
|
type configGetter interface {
|
|
Get(string, string) (string, error)
|
|
}
|
|
|
|
// generic authenticated HTTP client for commands
|
|
func NewHTTPClient(io *iostreams.IOStreams, cfg configGetter, appVersion string, setAccept bool) (*http.Client, error) {
|
|
var opts []api.ClientOption
|
|
|
|
// We need to check and potentially add the unix socket roundtripper option
|
|
// before adding any other options, since if we are going to use the unix
|
|
// socket transport, it needs to form the base of the transport chain
|
|
// represented by invocations of opts...
|
|
//
|
|
// Another approach might be to change the signature of api.NewHTTPClient to
|
|
// take an explicit base http.RoundTripper as its first parameter (it
|
|
// currently defaults internally to http.DefaultTransport), or add another
|
|
// variant like api.NewHTTPClientWithBaseRoundTripper. But, the only caller
|
|
// which would use that non-default behavior is right here, and it doesn't
|
|
// seem worth the cognitive overhead everywhere else just to serve this one
|
|
// use case.
|
|
unixSocket, err := cfg.Get("", "http_unix_socket")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if unixSocket != "" {
|
|
opts = append(opts, api.ClientOption(func(http.RoundTripper) http.RoundTripper {
|
|
return httpunix.NewRoundTripper(unixSocket)
|
|
}))
|
|
}
|
|
|
|
if isVerbose, debugValue := utils.IsDebugEnabled(); isVerbose {
|
|
logTraffic := strings.Contains(debugValue, "api")
|
|
opts = append(opts, api.VerboseLog(io.ErrOut, logTraffic, io.IsStderrTTY()))
|
|
}
|
|
|
|
opts = append(opts,
|
|
api.AddHeader("User-Agent", fmt.Sprintf("GitHub CLI %s", appVersion)),
|
|
api.AddHeaderFunc("Authorization", func(req *http.Request) (string, error) {
|
|
hostname := ghinstance.NormalizeHostname(getHost(req))
|
|
if token, err := cfg.Get(hostname, "oauth_token"); err == nil && token != "" {
|
|
return fmt.Sprintf("token %s", token), nil
|
|
}
|
|
return "", nil
|
|
}),
|
|
api.AddHeaderFunc("Time-Zone", func(req *http.Request) (string, error) {
|
|
if req.Method != "GET" && req.Method != "HEAD" {
|
|
if time.Local.String() != "Local" {
|
|
return time.Local.String(), nil
|
|
}
|
|
_, offset := time.Now().Zone()
|
|
return timezoneNames[offset], nil
|
|
}
|
|
return "", nil
|
|
}),
|
|
api.ExtractHeader("X-GitHub-SSO", &ssoHeader),
|
|
)
|
|
|
|
if setAccept {
|
|
opts = append(opts,
|
|
api.AddHeaderFunc("Accept", func(req *http.Request) (string, error) {
|
|
accept := "application/vnd.github.merge-info-preview+json" // PullRequest.mergeStateStatus
|
|
accept += ", application/vnd.github.nebula-preview" // visibility when RESTing repos into an org
|
|
if ghinstance.IsEnterprise(getHost(req)) {
|
|
accept += ", application/vnd.github.antiope-preview" // Commit.statusCheckRollup
|
|
accept += ", application/vnd.github.shadow-cat-preview" // PullRequest.isDraft
|
|
}
|
|
return accept, nil
|
|
}),
|
|
)
|
|
}
|
|
|
|
return api.NewHTTPClient(opts...), nil
|
|
}
|
|
|
|
var ssoHeader string
|
|
var ssoURLRE = regexp.MustCompile(`\burl=([^;]+)`)
|
|
|
|
// SSOURL returns the URL of a SAML SSO challenge received by the server for clients that use ExtractHeader
|
|
// to extract the value of the "X-GitHub-SSO" response header.
|
|
func SSOURL() string {
|
|
if ssoHeader == "" {
|
|
return ""
|
|
}
|
|
m := ssoURLRE.FindStringSubmatch(ssoHeader)
|
|
if m == nil {
|
|
return ""
|
|
}
|
|
return m[1]
|
|
}
|
|
|
|
func getHost(r *http.Request) string {
|
|
if r.Host != "" {
|
|
return r.Host
|
|
}
|
|
return r.URL.Hostname()
|
|
}
|