| // Copyright 2022, Google Inc. |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| package gax |
| |
| import ( |
| "io" |
| "io/ioutil" |
| "net/http" |
| ) |
| |
| 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 successfully 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. |
| // The content of media will be sniffed to determine the content type. |
| // 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) (io.Reader, string) { |
| // For backwards compatibility, allow clients to set content |
| // type by providing a ContentTyper for media. |
| // Note: This is an anonymous interface definition copied from googleapi.ContentTyper. |
| if typer, ok := media.(interface { |
| ContentType() string |
| }); 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, "" |
| } |