github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/release/github/api.go (about)

     1  // Modified from https://github.com/aktau/github-release/blob/master/api.go
     2  
     3  package github
     4  
     5  import (
     6  	"bytes"
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"net/http"
    12  	"net/url"
    13  	"os"
    14  )
    15  
    16  const (
    17  	githubAPIURL = "https://api.github.com"
    18  )
    19  
    20  func githubURL(host string) (u *url.URL, err error) {
    21  	u, err = url.Parse(host)
    22  	if err != nil {
    23  		return
    24  	}
    25  	data := url.Values{}
    26  	u.RawQuery = data.Encode()
    27  	return
    28  }
    29  
    30  // materializeFile takes a physical file or stream (named pipe, user input,
    31  // ...) and returns an io.Reader and the number of bytes that can be read
    32  // from it.
    33  func materializeFile(f *os.File) (io.Reader, int64, error) {
    34  	fi, err := f.Stat()
    35  	if err != nil {
    36  		return nil, 0, err
    37  	}
    38  
    39  	// If the file is actually a char device (like user typed input)
    40  	// or a named pipe (like a streamed in file), buffer it up.
    41  	//
    42  	// When uploading a file, you need to either explicitly set the
    43  	// Content-Length header or send a chunked request. Since the
    44  	// github upload server doesn't accept chunked encoding, we have
    45  	// to set the size of the file manually. Since a stream doesn't have a
    46  	// predefined length, it's read entirely into a byte buffer.
    47  	if fi.Mode()&(os.ModeCharDevice|os.ModeNamedPipe) == 1 {
    48  		var buf bytes.Buffer
    49  		n, readErr := buf.ReadFrom(f)
    50  		if readErr != nil {
    51  			return nil, 0, errors.New("req: could not buffer up input stream: " + readErr.Error())
    52  		}
    53  		return &buf, n, readErr
    54  	}
    55  
    56  	// We know the os.File is most likely an actual file now.
    57  	n, err := getFileSize(f)
    58  	return f, n, err
    59  }
    60  
    61  // NewAuthRequest creates a new request that sends the auth token
    62  func NewAuthRequest(method, url, bodyType, token string, headers map[string]string, body io.Reader) (*http.Request, error) {
    63  	var n int64 // content length
    64  	var err error
    65  	if f, ok := body.(*os.File); ok {
    66  		// Retrieve the content-length and buffer up if necessary.
    67  		body, n, err = materializeFile(f)
    68  		if err != nil {
    69  			return nil, err
    70  		}
    71  	}
    72  
    73  	req, err := http.NewRequest(method, url, body)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	if n != 0 {
    79  		req.ContentLength = n
    80  	}
    81  
    82  	if bodyType != "" {
    83  		req.Header.Set("Content-Type", bodyType)
    84  	}
    85  	req.Header.Set("Authorization", fmt.Sprintf("token %s", token))
    86  
    87  	for k, v := range headers {
    88  		req.Header.Set(k, v)
    89  	}
    90  
    91  	return req, nil
    92  }
    93  
    94  // DoAuthRequest does an authenticated request to Github
    95  func DoAuthRequest(method, url, bodyType, token string, headers map[string]string, body io.Reader) (*http.Response, error) {
    96  	req, err := NewAuthRequest(method, url, bodyType, token, headers, body)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	resp, err := http.DefaultClient.Do(req)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  
   106  	return resp, nil
   107  }
   108  
   109  // Get does a GET request to the Github API
   110  func Get(token string, url string, v interface{}) error {
   111  	resp, err := DoAuthRequest("GET", url, "", token, nil, nil)
   112  	if resp != nil {
   113  		defer func() { _ = resp.Body.Close() }()
   114  	}
   115  	if err != nil {
   116  		return fmt.Errorf("Error in http Get %v", err)
   117  	}
   118  	return get(resp, url, v)
   119  }
   120  
   121  func get(resp *http.Response, url, v interface{}) error {
   122  	if resp.StatusCode != http.StatusOK {
   123  		return fmt.Errorf("%s responded with %v", url, resp.Status)
   124  	}
   125  
   126  	var r io.Reader = resp.Body
   127  	if err := json.NewDecoder(r).Decode(v); err != nil {
   128  		return fmt.Errorf("could not unmarshall JSON into Release struct, %v", err)
   129  	}
   130  	return nil
   131  }