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