github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/lib/rest/rest.go (about)

     1  // Package rest implements a simple REST wrapper
     2  //
     3  // All methods are safe for concurrent calling.
     4  package rest
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"encoding/xml"
    10  	"io"
    11  	"io/ioutil"
    12  	"mime/multipart"
    13  	"net/http"
    14  	"net/url"
    15  	"sync"
    16  
    17  	"github.com/ncw/rclone/fs"
    18  	"github.com/pkg/errors"
    19  )
    20  
    21  // Client contains the info to sustain the API
    22  type Client struct {
    23  	mu           sync.RWMutex
    24  	c            *http.Client
    25  	rootURL      string
    26  	errorHandler func(resp *http.Response) error
    27  	headers      map[string]string
    28  	signer       SignerFn
    29  }
    30  
    31  // NewClient takes an oauth http.Client and makes a new api instance
    32  func NewClient(c *http.Client) *Client {
    33  	api := &Client{
    34  		c:            c,
    35  		errorHandler: defaultErrorHandler,
    36  		headers:      make(map[string]string),
    37  	}
    38  	return api
    39  }
    40  
    41  // ReadBody reads resp.Body into result, closing the body
    42  func ReadBody(resp *http.Response) (result []byte, err error) {
    43  	defer fs.CheckClose(resp.Body, &err)
    44  	return ioutil.ReadAll(resp.Body)
    45  }
    46  
    47  // defaultErrorHandler doesn't attempt to parse the http body, just
    48  // returns it in the error message
    49  func defaultErrorHandler(resp *http.Response) (err error) {
    50  	body, err := ReadBody(resp)
    51  	if err != nil {
    52  		return errors.Wrap(err, "error reading error out of body")
    53  	}
    54  	return errors.Errorf("HTTP error %v (%v) returned body: %q", resp.StatusCode, resp.Status, body)
    55  }
    56  
    57  // SetErrorHandler sets the handler to decode an error response when
    58  // the HTTP status code is not 2xx.  The handler should close resp.Body.
    59  func (api *Client) SetErrorHandler(fn func(resp *http.Response) error) *Client {
    60  	api.mu.Lock()
    61  	defer api.mu.Unlock()
    62  	api.errorHandler = fn
    63  	return api
    64  }
    65  
    66  // SetRoot sets the default RootURL.  You can override this on a per
    67  // call basis using the RootURL field in Opts.
    68  func (api *Client) SetRoot(RootURL string) *Client {
    69  	api.mu.Lock()
    70  	defer api.mu.Unlock()
    71  	api.rootURL = RootURL
    72  	return api
    73  }
    74  
    75  // SetHeader sets a header for all requests
    76  func (api *Client) SetHeader(key, value string) *Client {
    77  	api.mu.Lock()
    78  	defer api.mu.Unlock()
    79  	api.headers[key] = value
    80  	return api
    81  }
    82  
    83  // RemoveHeader unsets a header for all requests
    84  func (api *Client) RemoveHeader(key string) *Client {
    85  	api.mu.Lock()
    86  	defer api.mu.Unlock()
    87  	delete(api.headers, key)
    88  	return api
    89  }
    90  
    91  // SignerFn is used to sign an outgoing request
    92  type SignerFn func(*http.Request) error
    93  
    94  // SetSigner sets a signer for all requests
    95  func (api *Client) SetSigner(signer SignerFn) *Client {
    96  	api.mu.Lock()
    97  	defer api.mu.Unlock()
    98  	api.signer = signer
    99  	return api
   100  }
   101  
   102  // SetUserPass creates an Authorization header for all requests with
   103  // the UserName and Password passed in
   104  func (api *Client) SetUserPass(UserName, Password string) *Client {
   105  	req, _ := http.NewRequest("GET", "http://example.com", nil)
   106  	req.SetBasicAuth(UserName, Password)
   107  	api.SetHeader("Authorization", req.Header.Get("Authorization"))
   108  	return api
   109  }
   110  
   111  // SetCookie creates an Cookies Header for all requests with the supplied
   112  // cookies passed in.
   113  // All cookies have to be supplied at once, all cookies will be overwritten
   114  // on a new call to the method
   115  func (api *Client) SetCookie(cks ...*http.Cookie) *Client {
   116  	req, _ := http.NewRequest("GET", "http://example.com", nil)
   117  	for _, ck := range cks {
   118  		req.AddCookie(ck)
   119  	}
   120  	api.SetHeader("Cookie", req.Header.Get("Cookie"))
   121  	return api
   122  }
   123  
   124  // Opts contains parameters for Call, CallJSON etc
   125  type Opts struct {
   126  	Method                string // GET, POST etc
   127  	Path                  string // relative to RootURL
   128  	RootURL               string // override RootURL passed into SetRoot()
   129  	Body                  io.Reader
   130  	NoResponse            bool // set to close Body
   131  	ContentType           string
   132  	ContentLength         *int64
   133  	ContentRange          string
   134  	ExtraHeaders          map[string]string
   135  	UserName              string // username for Basic Auth
   136  	Password              string // password for Basic Auth
   137  	Options               []fs.OpenOption
   138  	IgnoreStatus          bool       // if set then we don't check error status or parse error body
   139  	MultipartParams       url.Values // if set do multipart form upload with attached file
   140  	MultipartMetadataName string     // ..this is used for the name of the metadata form part if set
   141  	MultipartContentName  string     // ..name of the parameter which is the attached file
   142  	MultipartFileName     string     // ..name of the file for the attached file
   143  	Parameters            url.Values // any parameters for the final URL
   144  	TransferEncoding      []string   // transfer encoding, set to "identity" to disable chunked encoding
   145  	Close                 bool       // set to close the connection after this transaction
   146  	NoRedirect            bool       // if this is set then the client won't follow redirects
   147  }
   148  
   149  // Copy creates a copy of the options
   150  func (o *Opts) Copy() *Opts {
   151  	newOpts := *o
   152  	return &newOpts
   153  }
   154  
   155  // DecodeJSON decodes resp.Body into result
   156  func DecodeJSON(resp *http.Response, result interface{}) (err error) {
   157  	defer fs.CheckClose(resp.Body, &err)
   158  	decoder := json.NewDecoder(resp.Body)
   159  	return decoder.Decode(result)
   160  }
   161  
   162  // DecodeXML decodes resp.Body into result
   163  func DecodeXML(resp *http.Response, result interface{}) (err error) {
   164  	defer fs.CheckClose(resp.Body, &err)
   165  	decoder := xml.NewDecoder(resp.Body)
   166  	return decoder.Decode(result)
   167  }
   168  
   169  // ClientWithNoRedirects makes a new http client which won't follow redirects
   170  func ClientWithNoRedirects(c *http.Client) *http.Client {
   171  	clientCopy := *c
   172  	clientCopy.CheckRedirect = func(req *http.Request, via []*http.Request) error {
   173  		return http.ErrUseLastResponse
   174  	}
   175  	return &clientCopy
   176  }
   177  
   178  // Call makes the call and returns the http.Response
   179  //
   180  // if err != nil then resp.Body will need to be closed unless
   181  // opt.NoResponse is set
   182  //
   183  // it will return resp if at all possible, even if err is set
   184  func (api *Client) Call(opts *Opts) (resp *http.Response, err error) {
   185  	api.mu.RLock()
   186  	defer api.mu.RUnlock()
   187  	if opts == nil {
   188  		return nil, errors.New("call() called with nil opts")
   189  	}
   190  	url := api.rootURL
   191  	if opts.RootURL != "" {
   192  		url = opts.RootURL
   193  	}
   194  	if url == "" {
   195  		return nil, errors.New("RootURL not set")
   196  	}
   197  	url += opts.Path
   198  	if opts.Parameters != nil && len(opts.Parameters) > 0 {
   199  		url += "?" + opts.Parameters.Encode()
   200  	}
   201  	body := opts.Body
   202  	// If length is set and zero then nil out the body to stop use
   203  	// use of chunked encoding and insert a "Content-Length: 0"
   204  	// header.
   205  	//
   206  	// If we don't do this we get "Content-Length" headers for all
   207  	// files except 0 length files.
   208  	if opts.ContentLength != nil && *opts.ContentLength == 0 {
   209  		body = nil
   210  	}
   211  	req, err := http.NewRequest(opts.Method, url, body)
   212  	if err != nil {
   213  		return
   214  	}
   215  	headers := make(map[string]string)
   216  	// Set default headers
   217  	for k, v := range api.headers {
   218  		headers[k] = v
   219  	}
   220  	if opts.ContentType != "" {
   221  		headers["Content-Type"] = opts.ContentType
   222  	}
   223  	if opts.ContentLength != nil {
   224  		req.ContentLength = *opts.ContentLength
   225  	}
   226  	if opts.ContentRange != "" {
   227  		headers["Content-Range"] = opts.ContentRange
   228  	}
   229  	if len(opts.TransferEncoding) != 0 {
   230  		req.TransferEncoding = opts.TransferEncoding
   231  	}
   232  	if opts.Close {
   233  		req.Close = true
   234  	}
   235  	// Set any extra headers
   236  	if opts.ExtraHeaders != nil {
   237  		for k, v := range opts.ExtraHeaders {
   238  			headers[k] = v
   239  		}
   240  	}
   241  	// add any options to the headers
   242  	fs.OpenOptionAddHeaders(opts.Options, headers)
   243  	// Now set the headers
   244  	for k, v := range headers {
   245  		if v != "" {
   246  			req.Header.Add(k, v)
   247  		}
   248  	}
   249  	if opts.UserName != "" || opts.Password != "" {
   250  		req.SetBasicAuth(opts.UserName, opts.Password)
   251  	}
   252  	var c *http.Client
   253  	if opts.NoRedirect {
   254  		c = ClientWithNoRedirects(api.c)
   255  	} else {
   256  		c = api.c
   257  	}
   258  	if api.signer != nil {
   259  		err = api.signer(req)
   260  		if err != nil {
   261  			return nil, errors.Wrap(err, "signer failed")
   262  		}
   263  	}
   264  	api.mu.RUnlock()
   265  	resp, err = c.Do(req)
   266  	api.mu.RLock()
   267  	if err != nil {
   268  		return nil, err
   269  	}
   270  	if !opts.IgnoreStatus {
   271  		if resp.StatusCode < 200 || resp.StatusCode > 299 {
   272  			err = api.errorHandler(resp)
   273  			if err.Error() == "" {
   274  				// replace empty errors with something
   275  				err = errors.Errorf("http error %d: %v", resp.StatusCode, resp.Status)
   276  			}
   277  			return resp, err
   278  		}
   279  	}
   280  	if opts.NoResponse {
   281  		return resp, resp.Body.Close()
   282  	}
   283  	return resp, nil
   284  }
   285  
   286  // MultipartUpload creates an io.Reader which produces an encoded a
   287  // multipart form upload from the params passed in and the  passed in
   288  //
   289  // in - the body of the file (may be nil)
   290  // params - the form parameters
   291  // fileName - is the name of the attached file
   292  // contentName - the name of the parameter for the file
   293  //
   294  // the int64 returned is the overhead in addition to the file contents, in case Content-Length is required
   295  //
   296  // NB This doesn't allow setting the content type of the attachment
   297  func MultipartUpload(in io.Reader, params url.Values, contentName, fileName string) (io.ReadCloser, string, int64, error) {
   298  	bodyReader, bodyWriter := io.Pipe()
   299  	writer := multipart.NewWriter(bodyWriter)
   300  	contentType := writer.FormDataContentType()
   301  
   302  	// Create a Multipart Writer as base for calculating the Content-Length
   303  	buf := &bytes.Buffer{}
   304  	dummyMultipartWriter := multipart.NewWriter(buf)
   305  	err := dummyMultipartWriter.SetBoundary(writer.Boundary())
   306  	if err != nil {
   307  		return nil, "", 0, err
   308  	}
   309  
   310  	for key, vals := range params {
   311  		for _, val := range vals {
   312  			err := dummyMultipartWriter.WriteField(key, val)
   313  			if err != nil {
   314  				return nil, "", 0, err
   315  			}
   316  		}
   317  	}
   318  	if in != nil {
   319  		_, err = dummyMultipartWriter.CreateFormFile(contentName, fileName)
   320  		if err != nil {
   321  			return nil, "", 0, err
   322  		}
   323  	}
   324  
   325  	err = dummyMultipartWriter.Close()
   326  	if err != nil {
   327  		return nil, "", 0, err
   328  	}
   329  
   330  	multipartLength := int64(buf.Len())
   331  
   332  	// Pump the data in the background
   333  	go func() {
   334  		var err error
   335  
   336  		for key, vals := range params {
   337  			for _, val := range vals {
   338  				err = writer.WriteField(key, val)
   339  				if err != nil {
   340  					_ = bodyWriter.CloseWithError(errors.Wrap(err, "create metadata part"))
   341  					return
   342  				}
   343  			}
   344  		}
   345  
   346  		if in != nil {
   347  			part, err := writer.CreateFormFile(contentName, fileName)
   348  			if err != nil {
   349  				_ = bodyWriter.CloseWithError(errors.Wrap(err, "failed to create form file"))
   350  				return
   351  			}
   352  
   353  			_, err = io.Copy(part, in)
   354  			if err != nil {
   355  				_ = bodyWriter.CloseWithError(errors.Wrap(err, "failed to copy data"))
   356  				return
   357  			}
   358  		}
   359  
   360  		err = writer.Close()
   361  		if err != nil {
   362  			_ = bodyWriter.CloseWithError(errors.Wrap(err, "failed to close form"))
   363  			return
   364  		}
   365  
   366  		_ = bodyWriter.Close()
   367  	}()
   368  
   369  	return bodyReader, contentType, multipartLength, nil
   370  }
   371  
   372  // CallJSON runs Call and decodes the body as a JSON object into response (if not nil)
   373  //
   374  // If request is not nil then it will be JSON encoded as the body of the request
   375  //
   376  // If response is not nil then the response will be JSON decoded into
   377  // it and resp.Body will be closed.
   378  //
   379  // If response is nil then the resp.Body will be closed only if
   380  // opts.NoResponse is set.
   381  //
   382  // If (opts.MultipartParams or opts.MultipartContentName) and
   383  // opts.Body are set then CallJSON will do a multipart upload with a
   384  // file attached.  opts.MultipartContentName is the name of the
   385  // parameter and opts.MultipartFileName is the name of the file.  If
   386  // MultpartContentName is set, and request != nil is supplied, then
   387  // the request will be marshalled into JSON and added to the form with
   388  // parameter name MultipartMetadataName.
   389  //
   390  // It will return resp if at all possible, even if err is set
   391  func (api *Client) CallJSON(opts *Opts, request interface{}, response interface{}) (resp *http.Response, err error) {
   392  	return api.callCodec(opts, request, response, json.Marshal, DecodeJSON, "application/json")
   393  }
   394  
   395  // CallXML runs Call and decodes the body as a XML object into response (if not nil)
   396  //
   397  // If request is not nil then it will be XML encoded as the body of the request
   398  //
   399  // If response is not nil then the response will be XML decoded into
   400  // it and resp.Body will be closed.
   401  //
   402  // If response is nil then the resp.Body will be closed only if
   403  // opts.NoResponse is set.
   404  //
   405  // See CallJSON for a description of MultipartParams and related opts
   406  //
   407  // It will return resp if at all possible, even if err is set
   408  func (api *Client) CallXML(opts *Opts, request interface{}, response interface{}) (resp *http.Response, err error) {
   409  	return api.callCodec(opts, request, response, xml.Marshal, DecodeXML, "application/xml")
   410  }
   411  
   412  type marshalFn func(v interface{}) ([]byte, error)
   413  type decodeFn func(resp *http.Response, result interface{}) (err error)
   414  
   415  func (api *Client) callCodec(opts *Opts, request interface{}, response interface{}, marshal marshalFn, decode decodeFn, contentType string) (resp *http.Response, err error) {
   416  	var requestBody []byte
   417  	// Marshal the request if given
   418  	if request != nil {
   419  		requestBody, err = marshal(request)
   420  		if err != nil {
   421  			return nil, err
   422  		}
   423  		// Set the body up as a marshalled object if no body passed in
   424  		if opts.Body == nil {
   425  			opts = opts.Copy()
   426  			opts.ContentType = contentType
   427  			opts.Body = bytes.NewBuffer(requestBody)
   428  		}
   429  	}
   430  	if opts.MultipartParams != nil || opts.MultipartContentName != "" {
   431  		params := opts.MultipartParams
   432  		if params == nil {
   433  			params = url.Values{}
   434  		}
   435  		if opts.MultipartMetadataName != "" {
   436  			params.Add(opts.MultipartMetadataName, string(requestBody))
   437  		}
   438  		opts = opts.Copy()
   439  
   440  		var overhead int64
   441  		opts.Body, opts.ContentType, overhead, err = MultipartUpload(opts.Body, params, opts.MultipartContentName, opts.MultipartFileName)
   442  		if opts.ContentLength != nil {
   443  			*opts.ContentLength += overhead
   444  		}
   445  	}
   446  	resp, err = api.Call(opts)
   447  	if err != nil {
   448  		return resp, err
   449  	}
   450  	// if opts.NoResponse is set, resp.Body will have been closed by Call()
   451  	if response == nil || opts.NoResponse {
   452  		return resp, nil
   453  	}
   454  	err = decode(resp, response)
   455  	return resp, err
   456  }