• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2016 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package gensupport
6
7import (
8	"fmt"
9	"io"
10	"io/ioutil"
11	"mime/multipart"
12	"net/http"
13	"net/textproto"
14
15	"google.golang.org/api/googleapi"
16)
17
18const sniffBuffSize = 512
19
20func newContentSniffer(r io.Reader) *contentSniffer {
21	return &contentSniffer{r: r}
22}
23
24// contentSniffer wraps a Reader, and reports the content type determined by sniffing up to 512 bytes from the Reader.
25type contentSniffer struct {
26	r     io.Reader
27	start []byte // buffer for the sniffed bytes.
28	err   error  // set to any error encountered while reading bytes to be sniffed.
29
30	ctype   string // set on first sniff.
31	sniffed bool   // set to true on first sniff.
32}
33
34func (cs *contentSniffer) Read(p []byte) (n int, err error) {
35	// Ensure that the content type is sniffed before any data is consumed from Reader.
36	_, _ = cs.ContentType()
37
38	if len(cs.start) > 0 {
39		n := copy(p, cs.start)
40		cs.start = cs.start[n:]
41		return n, nil
42	}
43
44	// We may have read some bytes into start while sniffing, even if the read ended in an error.
45	// We should first return those bytes, then the error.
46	if cs.err != nil {
47		return 0, cs.err
48	}
49
50	// Now we have handled all bytes that were buffered while sniffing.  Now just delegate to the underlying reader.
51	return cs.r.Read(p)
52}
53
54// ContentType returns the sniffed content type, and whether the content type was succesfully sniffed.
55func (cs *contentSniffer) ContentType() (string, bool) {
56	if cs.sniffed {
57		return cs.ctype, cs.ctype != ""
58	}
59	cs.sniffed = true
60	// If ReadAll hits EOF, it returns err==nil.
61	cs.start, cs.err = ioutil.ReadAll(io.LimitReader(cs.r, sniffBuffSize))
62
63	// Don't try to detect the content type based on possibly incomplete data.
64	if cs.err != nil {
65		return "", false
66	}
67
68	cs.ctype = http.DetectContentType(cs.start)
69	return cs.ctype, true
70}
71
72// DetermineContentType determines the content type of the supplied reader.
73// If the content type is already known, it can be specified via ctype.
74// Otherwise, the content of media will be sniffed to determine the content type.
75// If media implements googleapi.ContentTyper (deprecated), this will be used
76// instead of sniffing the content.
77// After calling DetectContentType the caller must not perform further reads on
78// media, but rather read from the Reader that is returned.
79func DetermineContentType(media io.Reader, ctype string) (io.Reader, string) {
80	// Note: callers could avoid calling DetectContentType if ctype != "",
81	// but doing the check inside this function reduces the amount of
82	// generated code.
83	if ctype != "" {
84		return media, ctype
85	}
86
87	// For backwards compatability, allow clients to set content
88	// type by providing a ContentTyper for media.
89	if typer, ok := media.(googleapi.ContentTyper); ok {
90		return media, typer.ContentType()
91	}
92
93	sniffer := newContentSniffer(media)
94	if ctype, ok := sniffer.ContentType(); ok {
95		return sniffer, ctype
96	}
97	// If content type could not be sniffed, reads from sniffer will eventually fail with an error.
98	return sniffer, ""
99}
100
101type typeReader struct {
102	io.Reader
103	typ string
104}
105
106// multipartReader combines the contents of multiple readers to creat a multipart/related HTTP body.
107// Close must be called if reads from the multipartReader are abandoned before reaching EOF.
108type multipartReader struct {
109	pr       *io.PipeReader
110	pipeOpen bool
111	ctype    string
112}
113
114func newMultipartReader(parts []typeReader) *multipartReader {
115	mp := &multipartReader{pipeOpen: true}
116	var pw *io.PipeWriter
117	mp.pr, pw = io.Pipe()
118	mpw := multipart.NewWriter(pw)
119	mp.ctype = "multipart/related; boundary=" + mpw.Boundary()
120	go func() {
121		for _, part := range parts {
122			w, err := mpw.CreatePart(typeHeader(part.typ))
123			if err != nil {
124				mpw.Close()
125				pw.CloseWithError(fmt.Errorf("googleapi: CreatePart failed: %v", err))
126				return
127			}
128			_, err = io.Copy(w, part.Reader)
129			if err != nil {
130				mpw.Close()
131				pw.CloseWithError(fmt.Errorf("googleapi: Copy failed: %v", err))
132				return
133			}
134		}
135
136		mpw.Close()
137		pw.Close()
138	}()
139	return mp
140}
141
142func (mp *multipartReader) Read(data []byte) (n int, err error) {
143	return mp.pr.Read(data)
144}
145
146func (mp *multipartReader) Close() error {
147	if !mp.pipeOpen {
148		return nil
149	}
150	mp.pipeOpen = false
151	return mp.pr.Close()
152}
153
154// CombineBodyMedia combines a json body with media content to create a multipart/related HTTP body.
155// It returns a ReadCloser containing the combined body, and the overall "multipart/related" content type, with random boundary.
156//
157// The caller must call Close on the returned ReadCloser if reads are abandoned before reaching EOF.
158func CombineBodyMedia(body io.Reader, bodyContentType string, media io.Reader, mediaContentType string) (io.ReadCloser, string) {
159	mp := newMultipartReader([]typeReader{
160		{body, bodyContentType},
161		{media, mediaContentType},
162	})
163	return mp, mp.ctype
164}
165
166func typeHeader(contentType string) textproto.MIMEHeader {
167	h := make(textproto.MIMEHeader)
168	if contentType != "" {
169		h.Set("Content-Type", contentType)
170	}
171	return h
172}
173
174// PrepareUpload determines whether the data in the supplied reader should be
175// uploaded in a single request, or in sequential chunks.
176// chunkSize is the size of the chunk that media should be split into.
177// If chunkSize is non-zero and the contents of media do not fit in a single
178// chunk (or there is an error reading media), then media will be returned as a
179// MediaBuffer.  Otherwise, media will be returned as a Reader.
180//
181// After PrepareUpload has been called, media should no longer be used: the
182// media content should be accessed via one of the return values.
183func PrepareUpload(media io.Reader, chunkSize int) (io.Reader, *MediaBuffer) {
184	if chunkSize == 0 { // do not chunk
185		return media, nil
186	}
187
188	mb := NewMediaBuffer(media, chunkSize)
189	rdr, _, _, err := mb.Chunk()
190
191	if err == io.EOF { // we can upload this in a single request
192		return rdr, nil
193	}
194	// err might be a non-EOF error. If it is, the next call to mb.Chunk will
195	// return the same error. Returning a MediaBuffer ensures that this error
196	// will be handled at some point.
197
198	return nil, mb
199}
200