| // Copyright 2016 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package gensupport |
| |
| import ( |
| "fmt" |
| "io" |
| "io/ioutil" |
| "mime/multipart" |
| "net/http" |
| "net/textproto" |
| |
| "google.golang.org/api/googleapi" |
| ) |
| |
| const sniffBuffSize = 512 |
| |
| func newContentSniffer(r io.Reader) *contentSniffer { |
| return &contentSniffer{r: r} |
| } |
| |
| // contentSniffer wraps a Reader, and reports the content type determined by sniffing up to 512 bytes from the Reader. |
| type contentSniffer struct { |
| r io.Reader |
| start []byte // buffer for the sniffed bytes. |
| err error // set to any error encountered while reading bytes to be sniffed. |
| |
| ctype string // set on first sniff. |
| sniffed bool // set to true on first sniff. |
| } |
| |
| func (cs *contentSniffer) Read(p []byte) (n int, err error) { |
| // Ensure that the content type is sniffed before any data is consumed from Reader. |
| _, _ = cs.ContentType() |
| |
| if len(cs.start) > 0 { |
| n := copy(p, cs.start) |
| cs.start = cs.start[n:] |
| return n, nil |
| } |
| |
| // We may have read some bytes into start while sniffing, even if the read ended in an error. |
| // We should first return those bytes, then the error. |
| if cs.err != nil { |
| return 0, cs.err |
| } |
| |
| // Now we have handled all bytes that were buffered while sniffing. Now just delegate to the underlying reader. |
| return cs.r.Read(p) |
| } |
| |
| // ContentType returns the sniffed content type, and whether the content type was succesfully sniffed. |
| func (cs *contentSniffer) ContentType() (string, bool) { |
| if cs.sniffed { |
| return cs.ctype, cs.ctype != "" |
| } |
| cs.sniffed = true |
| // If ReadAll hits EOF, it returns err==nil. |
| cs.start, cs.err = ioutil.ReadAll(io.LimitReader(cs.r, sniffBuffSize)) |
| |
| // Don't try to detect the content type based on possibly incomplete data. |
| if cs.err != nil { |
| return "", false |
| } |
| |
| cs.ctype = http.DetectContentType(cs.start) |
| return cs.ctype, true |
| } |
| |
| // DetermineContentType determines the content type of the supplied reader. |
| // If the content type is already known, it can be specified via ctype. |
| // Otherwise, the content of media will be sniffed to determine the content type. |
| // If media implements googleapi.ContentTyper (deprecated), this will be used |
| // instead of sniffing the content. |
| // After calling DetectContentType the caller must not perform further reads on |
| // media, but rather read from the Reader that is returned. |
| func DetermineContentType(media io.Reader, ctype string) (io.Reader, string) { |
| // Note: callers could avoid calling DetectContentType if ctype != "", |
| // but doing the check inside this function reduces the amount of |
| // generated code. |
| if ctype != "" { |
| return media, ctype |
| } |
| |
| // For backwards compatability, allow clients to set content |
| // type by providing a ContentTyper for media. |
| if typer, ok := media.(googleapi.ContentTyper); ok { |
| return media, typer.ContentType() |
| } |
| |
| sniffer := newContentSniffer(media) |
| if ctype, ok := sniffer.ContentType(); ok { |
| return sniffer, ctype |
| } |
| // If content type could not be sniffed, reads from sniffer will eventually fail with an error. |
| return sniffer, "" |
| } |
| |
| type typeReader struct { |
| io.Reader |
| typ string |
| } |
| |
| // multipartReader combines the contents of multiple readers to creat a multipart/related HTTP body. |
| // Close must be called if reads from the multipartReader are abandoned before reaching EOF. |
| type multipartReader struct { |
| pr *io.PipeReader |
| pipeOpen bool |
| ctype string |
| } |
| |
| func newMultipartReader(parts []typeReader) *multipartReader { |
| mp := &multipartReader{pipeOpen: true} |
| var pw *io.PipeWriter |
| mp.pr, pw = io.Pipe() |
| mpw := multipart.NewWriter(pw) |
| mp.ctype = "multipart/related; boundary=" + mpw.Boundary() |
| go func() { |
| for _, part := range parts { |
| w, err := mpw.CreatePart(typeHeader(part.typ)) |
| if err != nil { |
| mpw.Close() |
| pw.CloseWithError(fmt.Errorf("googleapi: CreatePart failed: %v", err)) |
| return |
| } |
| _, err = io.Copy(w, part.Reader) |
| if err != nil { |
| mpw.Close() |
| pw.CloseWithError(fmt.Errorf("googleapi: Copy failed: %v", err)) |
| return |
| } |
| } |
| |
| mpw.Close() |
| pw.Close() |
| }() |
| return mp |
| } |
| |
| func (mp *multipartReader) Read(data []byte) (n int, err error) { |
| return mp.pr.Read(data) |
| } |
| |
| func (mp *multipartReader) Close() error { |
| if !mp.pipeOpen { |
| return nil |
| } |
| mp.pipeOpen = false |
| return mp.pr.Close() |
| } |
| |
| // CombineBodyMedia combines a json body with media content to create a multipart/related HTTP body. |
| // It returns a ReadCloser containing the combined body, and the overall "multipart/related" content type, with random boundary. |
| // |
| // The caller must call Close on the returned ReadCloser if reads are abandoned before reaching EOF. |
| func CombineBodyMedia(body io.Reader, bodyContentType string, media io.Reader, mediaContentType string) (io.ReadCloser, string) { |
| mp := newMultipartReader([]typeReader{ |
| {body, bodyContentType}, |
| {media, mediaContentType}, |
| }) |
| return mp, mp.ctype |
| } |
| |
| func typeHeader(contentType string) textproto.MIMEHeader { |
| h := make(textproto.MIMEHeader) |
| if contentType != "" { |
| h.Set("Content-Type", contentType) |
| } |
| return h |
| } |
| |
| // PrepareUpload determines whether the data in the supplied reader should be |
| // uploaded in a single request, or in sequential chunks. |
| // chunkSize is the size of the chunk that media should be split into. |
| // If chunkSize is non-zero and the contents of media do not fit in a single |
| // chunk (or there is an error reading media), then media will be returned as a |
| // MediaBuffer. Otherwise, media will be returned as a Reader. |
| // |
| // After PrepareUpload has been called, media should no longer be used: the |
| // media content should be accessed via one of the return values. |
| func PrepareUpload(media io.Reader, chunkSize int) (io.Reader, *MediaBuffer) { |
| if chunkSize == 0 { // do not chunk |
| return media, nil |
| } |
| |
| mb := NewMediaBuffer(media, chunkSize) |
| rdr, _, _, err := mb.Chunk() |
| |
| if err == io.EOF { // we can upload this in a single request |
| return rdr, nil |
| } |
| // err might be a non-EOF error. If it is, the next call to mb.Chunk will |
| // return the same error. Returning a MediaBuffer ensures that this error |
| // will be handled at some point. |
| |
| return nil, mb |
| } |