github.com/hoffie/larasync@v0.0.0-20151025221940-0384d2bddcef/api/common/concat.go (about)

     1  package common
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"net/url"
     9  	"sort"
    10  )
    11  
    12  // concatenatorDelimiter is the character which is put between different
    13  // parts of the request so that these parts cannot be mixed up.
    14  const concatenatorDelimiter = byte(0)
    15  
    16  type concatenator struct {
    17  	req *http.Request
    18  	w   io.Writer
    19  }
    20  
    21  // concatenateTo writes all elementary request parts to the given writer.
    22  // parts are: method, URL, headers, body
    23  func concatenateTo(req *http.Request, w io.Writer) error {
    24  	c := &concatenator{
    25  		req: req,
    26  		w:   w,
    27  	}
    28  	err := c.writeDelimitedString(c.req.Method)
    29  	if err != nil {
    30  		return err
    31  	}
    32  
    33  	err = c.URL()
    34  	if err != nil {
    35  		return err
    36  	}
    37  
    38  	c.Headers()
    39  	if err != nil {
    40  		return err
    41  	}
    42  
    43  	// Body() must be last, as it does not use the length-limited bincontainer;
    44  	// instead, it writes its data verbatim
    45  	c.Body()
    46  	if err != nil {
    47  		return err
    48  	}
    49  	return nil
    50  }
    51  
    52  // URL (re)constructs the URL and appends it
    53  func (c *concatenator) URL() error {
    54  	var url url.URL
    55  	url = *c.req.URL
    56  	// we do not know the scheme for all cases, so ignore it:
    57  	url.Scheme = ""
    58  	if url.Host == "" {
    59  		url.Host = c.req.Host
    60  	}
    61  
    62  	return c.writeDelimitedString(url.String())
    63  }
    64  
    65  // ignoreHeaders is a list of all headers which are not part of the
    66  // resulting output.
    67  var ignoreHeaders = map[string]bool{
    68  	// Authorization is our header; we can't sign our own signature
    69  	"Authorization": true,
    70  	// User-Agent is allowed to vary
    71  	"User-Agent": true,
    72  	// Accept-Encoding will also be mangled by the client
    73  	"Accept-Encoding": true,
    74  	// Content-Length doesn't matter as the content is signed
    75  	"Content-Length": true,
    76  	// Host header does not need to be included as the parsed host header
    77  	// is included as part of the URL (see URL())
    78  	"Host": true,
    79  }
    80  
    81  // Headers concatenate the headers.
    82  func (c *concatenator) Headers() error {
    83  	headers := make([]string, len(c.req.Header))
    84  	i := 0
    85  	for header := range c.req.Header {
    86  		headers[i] = header
    87  		i++
    88  	}
    89  	sort.Strings(headers)
    90  	for _, header := range headers {
    91  		_, isIgnored := ignoreHeaders[header]
    92  		if isIgnored {
    93  			continue
    94  		}
    95  		err := c.writeDelimitedString(header)
    96  		if err != nil {
    97  			return err
    98  		}
    99  		for _, value := range c.req.Header[header] {
   100  			err = c.writeDelimitedString(value)
   101  			if err != nil {
   102  				return err
   103  			}
   104  		}
   105  	}
   106  	return nil
   107  }
   108  
   109  // Body concatenates the body.
   110  func (c *concatenator) Body() error {
   111  	if c.req.Body == nil {
   112  		return c.writeDelimiter()
   113  	}
   114  	bodyCopy := &bytes.Buffer{}
   115  	// we write directly to the writer here and do not use
   116  	// bincontainer.Encoder; the reason for this is that doing
   117  	// that would require buffering all the body content to
   118  	// calculate its length beforehand.
   119  	// for this to be safe, no other data may be written afterwards.
   120  	_, err := io.Copy(io.MultiWriter(bodyCopy, c.w), c.req.Body)
   121  	if err != nil {
   122  		return err
   123  	}
   124  	c.req.Body = ioutil.NopCloser(bodyCopy)
   125  	return c.writeDelimiter()
   126  }
   127  
   128  // writeDelimitedString writes the given string to the writer and
   129  // appends the delimiter.
   130  func (c *concatenator) writeDelimitedString(s string) error {
   131  	_, err := c.w.Write([]byte(s))
   132  	if err != nil {
   133  		return err
   134  	}
   135  	return c.writeDelimiter()
   136  }
   137  
   138  // writeDelimiter outputs the delimiter to the writer.
   139  func (c *concatenator) writeDelimiter() error {
   140  	_, err := c.w.Write([]byte{concatenatorDelimiter})
   141  	return err
   142  }