mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
a74cc83345
The test was passing previously because the preamble was already buffered. After the change to return Scanner.Err() the final read error on the buffer was no longer being ignored. Signed-off-by: Daniel Nephin <dnephin@docker.com>
126 lines
3.8 KiB
Go
126 lines
3.8 KiB
Go
package remotecontext
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"regexp"
|
|
|
|
"github.com/docker/docker/pkg/ioutils"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// When downloading remote contexts, limit the amount (in bytes)
|
|
// to be read from the response body in order to detect its Content-Type
|
|
const maxPreambleLength = 100
|
|
|
|
const acceptableRemoteMIME = `(?:application/(?:(?:x\-)?tar|octet\-stream|((?:x\-)?(?:gzip|bzip2?|xz)))|(?:text/plain))`
|
|
|
|
var mimeRe = regexp.MustCompile(acceptableRemoteMIME)
|
|
|
|
// downloadRemote context from a url and returns it, along with the parsed content type
|
|
func downloadRemote(remoteURL string) (string, io.ReadCloser, error) {
|
|
response, err := GetWithStatusError(remoteURL)
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("error downloading remote context %s: %v", remoteURL, err)
|
|
}
|
|
|
|
contentType, contextReader, err := inspectResponse(
|
|
response.Header.Get("Content-Type"),
|
|
response.Body,
|
|
response.ContentLength)
|
|
if err != nil {
|
|
response.Body.Close()
|
|
return "", nil, fmt.Errorf("error detecting content type for remote %s: %v", remoteURL, err)
|
|
}
|
|
|
|
return contentType, ioutils.NewReadCloserWrapper(contextReader, response.Body.Close), nil
|
|
}
|
|
|
|
// GetWithStatusError does an http.Get() and returns an error if the
|
|
// status code is 4xx or 5xx.
|
|
func GetWithStatusError(address string) (resp *http.Response, err error) {
|
|
if resp, err = http.Get(address); err != nil {
|
|
if uerr, ok := err.(*url.Error); ok {
|
|
if derr, ok := uerr.Err.(*net.DNSError); ok && !derr.IsTimeout {
|
|
return nil, dnsError{err}
|
|
}
|
|
}
|
|
return nil, systemError{err}
|
|
}
|
|
if resp.StatusCode < 400 {
|
|
return resp, nil
|
|
}
|
|
msg := fmt.Sprintf("failed to GET %s with status %s", address, resp.Status)
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
resp.Body.Close()
|
|
if err != nil {
|
|
return nil, errors.Wrap(systemError{err}, msg+": error reading body")
|
|
}
|
|
|
|
msg += ": " + string(bytes.TrimSpace(body))
|
|
switch resp.StatusCode {
|
|
case http.StatusNotFound:
|
|
return nil, notFoundError(msg)
|
|
case http.StatusBadRequest:
|
|
return nil, requestError(msg)
|
|
case http.StatusUnauthorized:
|
|
return nil, unauthorizedError(msg)
|
|
case http.StatusForbidden:
|
|
return nil, forbiddenError(msg)
|
|
}
|
|
return nil, unknownError{errors.New(msg)}
|
|
}
|
|
|
|
// inspectResponse looks into the http response data at r to determine whether its
|
|
// content-type is on the list of acceptable content types for remote build contexts.
|
|
// This function returns:
|
|
// - a string representation of the detected content-type
|
|
// - an io.Reader for the response body
|
|
// - an error value which will be non-nil either when something goes wrong while
|
|
// reading bytes from r or when the detected content-type is not acceptable.
|
|
func inspectResponse(ct string, r io.Reader, clen int64) (string, io.Reader, error) {
|
|
plen := clen
|
|
if plen <= 0 || plen > maxPreambleLength {
|
|
plen = maxPreambleLength
|
|
}
|
|
|
|
preamble := make([]byte, plen)
|
|
rlen, err := r.Read(preamble)
|
|
if rlen == 0 {
|
|
return ct, r, errors.New("empty response")
|
|
}
|
|
if err != nil && err != io.EOF {
|
|
return ct, r, err
|
|
}
|
|
|
|
preambleR := bytes.NewReader(preamble[:rlen])
|
|
bodyReader := io.MultiReader(preambleR, r)
|
|
// Some web servers will use application/octet-stream as the default
|
|
// content type for files without an extension (e.g. 'Dockerfile')
|
|
// so if we receive this value we better check for text content
|
|
contentType := ct
|
|
if len(ct) == 0 || ct == mimeTypes.OctetStream {
|
|
contentType, _, err = detectContentType(preamble)
|
|
if err != nil {
|
|
return contentType, bodyReader, err
|
|
}
|
|
}
|
|
|
|
contentType = selectAcceptableMIME(contentType)
|
|
var cterr error
|
|
if len(contentType) == 0 {
|
|
cterr = fmt.Errorf("unsupported Content-Type %q", ct)
|
|
contentType = ct
|
|
}
|
|
|
|
return contentType, bodyReader, cterr
|
|
}
|
|
|
|
func selectAcceptableMIME(ct string) string {
|
|
return mimeRe.FindString(ct)
|
|
}
|