github.com/andrewhsu/cli/v2@v2.0.1-0.20210910131313-d4b4061f5b89/pkg/cmd/factory/http.go (about)

     1  package factory
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"os"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/andrewhsu/cli/v2/api"
    11  	"github.com/andrewhsu/cli/v2/internal/ghinstance"
    12  	"github.com/andrewhsu/cli/v2/internal/httpunix"
    13  	"github.com/andrewhsu/cli/v2/pkg/iostreams"
    14  )
    15  
    16  var timezoneNames = map[int]string{
    17  	-39600: "Pacific/Niue",
    18  	-36000: "Pacific/Honolulu",
    19  	-34200: "Pacific/Marquesas",
    20  	-32400: "America/Anchorage",
    21  	-28800: "America/Los_Angeles",
    22  	-25200: "America/Chihuahua",
    23  	-21600: "America/Chicago",
    24  	-18000: "America/Bogota",
    25  	-14400: "America/Caracas",
    26  	-12600: "America/St_Johns",
    27  	-10800: "America/Argentina/Buenos_Aires",
    28  	-7200:  "Atlantic/South_Georgia",
    29  	-3600:  "Atlantic/Cape_Verde",
    30  	0:      "Europe/London",
    31  	3600:   "Europe/Amsterdam",
    32  	7200:   "Europe/Athens",
    33  	10800:  "Europe/Istanbul",
    34  	12600:  "Asia/Tehran",
    35  	14400:  "Asia/Dubai",
    36  	16200:  "Asia/Kabul",
    37  	18000:  "Asia/Tashkent",
    38  	19800:  "Asia/Kolkata",
    39  	20700:  "Asia/Kathmandu",
    40  	21600:  "Asia/Dhaka",
    41  	23400:  "Asia/Rangoon",
    42  	25200:  "Asia/Bangkok",
    43  	28800:  "Asia/Manila",
    44  	31500:  "Australia/Eucla",
    45  	32400:  "Asia/Tokyo",
    46  	34200:  "Australia/Darwin",
    47  	36000:  "Australia/Brisbane",
    48  	37800:  "Australia/Adelaide",
    49  	39600:  "Pacific/Guadalcanal",
    50  	43200:  "Pacific/Nauru",
    51  	46800:  "Pacific/Auckland",
    52  	49500:  "Pacific/Chatham",
    53  	50400:  "Pacific/Kiritimati",
    54  }
    55  
    56  type configGetter interface {
    57  	Get(string, string) (string, error)
    58  }
    59  
    60  // generic authenticated HTTP client for commands
    61  func NewHTTPClient(io *iostreams.IOStreams, cfg configGetter, appVersion string, setAccept bool) (*http.Client, error) {
    62  	var opts []api.ClientOption
    63  
    64  	// We need to check and potentially add the unix socket roundtripper option
    65  	// before adding any other options, since if we are going to use the unix
    66  	// socket transport, it needs to form the base of the transport chain
    67  	// represented by invocations of opts...
    68  	//
    69  	// Another approach might be to change the signature of api.NewHTTPClient to
    70  	// take an explicit base http.RoundTripper as its first parameter (it
    71  	// currently defaults internally to http.DefaultTransport), or add another
    72  	// variant like api.NewHTTPClientWithBaseRoundTripper. But, the only caller
    73  	// which would use that non-default behavior is right here, and it doesn't
    74  	// seem worth the cognitive overhead everywhere else just to serve this one
    75  	// use case.
    76  	unixSocket, err := cfg.Get("", "http_unix_socket")
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	if unixSocket != "" {
    81  		opts = append(opts, api.ClientOption(func(http.RoundTripper) http.RoundTripper {
    82  			return httpunix.NewRoundTripper(unixSocket)
    83  		}))
    84  	}
    85  
    86  	if verbose := os.Getenv("DEBUG"); verbose != "" {
    87  		logTraffic := strings.Contains(verbose, "api")
    88  		opts = append(opts, api.VerboseLog(io.ErrOut, logTraffic, io.IsStderrTTY()))
    89  	}
    90  
    91  	opts = append(opts,
    92  		api.AddHeader("User-Agent", fmt.Sprintf("GitHub CLI %s", appVersion)),
    93  		api.AddHeaderFunc("Authorization", func(req *http.Request) (string, error) {
    94  			hostname := ghinstance.NormalizeHostname(getHost(req))
    95  			if token, err := cfg.Get(hostname, "oauth_token"); err == nil && token != "" {
    96  				return fmt.Sprintf("token %s", token), nil
    97  			}
    98  			return "", nil
    99  		}),
   100  		api.AddHeaderFunc("Time-Zone", func(req *http.Request) (string, error) {
   101  			if req.Method != "GET" && req.Method != "HEAD" {
   102  				if time.Local.String() != "Local" {
   103  					return time.Local.String(), nil
   104  				}
   105  				_, offset := time.Now().Zone()
   106  				return timezoneNames[offset], nil
   107  			}
   108  			return "", nil
   109  		}),
   110  	)
   111  
   112  	if setAccept {
   113  		opts = append(opts,
   114  			api.AddHeaderFunc("Accept", func(req *http.Request) (string, error) {
   115  				accept := "application/vnd.github.merge-info-preview+json" // PullRequest.mergeStateStatus
   116  				accept += ", application/vnd.github.nebula-preview"        // visibility when RESTing repos into an org
   117  				if ghinstance.IsEnterprise(getHost(req)) {
   118  					accept += ", application/vnd.github.antiope-preview"    // Commit.statusCheckRollup
   119  					accept += ", application/vnd.github.shadow-cat-preview" // PullRequest.isDraft
   120  				}
   121  				return accept, nil
   122  			}),
   123  		)
   124  	}
   125  
   126  	return api.NewHTTPClient(opts...), nil
   127  }
   128  
   129  func getHost(r *http.Request) string {
   130  	if r.Host != "" {
   131  		return r.Host
   132  	}
   133  	return r.URL.Hostname()
   134  }