cli/pkg/cmd/factory/http.go
lylecantcode 56fda0f8c6
Support GH_DEBUG to control verbosity, deprecate DEBUG (#5306)
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>
2022-03-29 18:05:35 +02:00

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()
}