github.com/LazyboyChen7/engine@v17.12.1-ce-rc2+incompatible/builder/remotecontext/remote.go (about) 1 package remotecontext 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "net" 9 "net/http" 10 "net/url" 11 "regexp" 12 13 "github.com/docker/docker/pkg/ioutils" 14 "github.com/pkg/errors" 15 ) 16 17 // When downloading remote contexts, limit the amount (in bytes) 18 // to be read from the response body in order to detect its Content-Type 19 const maxPreambleLength = 100 20 21 const acceptableRemoteMIME = `(?:application/(?:(?:x\-)?tar|octet\-stream|((?:x\-)?(?:gzip|bzip2?|xz)))|(?:text/plain))` 22 23 var mimeRe = regexp.MustCompile(acceptableRemoteMIME) 24 25 // downloadRemote context from a url and returns it, along with the parsed content type 26 func downloadRemote(remoteURL string) (string, io.ReadCloser, error) { 27 response, err := GetWithStatusError(remoteURL) 28 if err != nil { 29 return "", nil, fmt.Errorf("error downloading remote context %s: %v", remoteURL, err) 30 } 31 32 contentType, contextReader, err := inspectResponse( 33 response.Header.Get("Content-Type"), 34 response.Body, 35 response.ContentLength) 36 if err != nil { 37 response.Body.Close() 38 return "", nil, fmt.Errorf("error detecting content type for remote %s: %v", remoteURL, err) 39 } 40 41 return contentType, ioutils.NewReadCloserWrapper(contextReader, response.Body.Close), nil 42 } 43 44 // GetWithStatusError does an http.Get() and returns an error if the 45 // status code is 4xx or 5xx. 46 func GetWithStatusError(address string) (resp *http.Response, err error) { 47 if resp, err = http.Get(address); err != nil { 48 if uerr, ok := err.(*url.Error); ok { 49 if derr, ok := uerr.Err.(*net.DNSError); ok && !derr.IsTimeout { 50 return nil, dnsError{err} 51 } 52 } 53 return nil, systemError{err} 54 } 55 if resp.StatusCode < 400 { 56 return resp, nil 57 } 58 msg := fmt.Sprintf("failed to GET %s with status %s", address, resp.Status) 59 body, err := ioutil.ReadAll(resp.Body) 60 resp.Body.Close() 61 if err != nil { 62 return nil, errors.Wrap(systemError{err}, msg+": error reading body") 63 } 64 65 msg += ": " + string(bytes.TrimSpace(body)) 66 switch resp.StatusCode { 67 case http.StatusNotFound: 68 return nil, notFoundError(msg) 69 case http.StatusBadRequest: 70 return nil, requestError(msg) 71 case http.StatusUnauthorized: 72 return nil, unauthorizedError(msg) 73 case http.StatusForbidden: 74 return nil, forbiddenError(msg) 75 } 76 return nil, unknownError{errors.New(msg)} 77 } 78 79 // inspectResponse looks into the http response data at r to determine whether its 80 // content-type is on the list of acceptable content types for remote build contexts. 81 // This function returns: 82 // - a string representation of the detected content-type 83 // - an io.Reader for the response body 84 // - an error value which will be non-nil either when something goes wrong while 85 // reading bytes from r or when the detected content-type is not acceptable. 86 func inspectResponse(ct string, r io.Reader, clen int64) (string, io.Reader, error) { 87 plen := clen 88 if plen <= 0 || plen > maxPreambleLength { 89 plen = maxPreambleLength 90 } 91 92 preamble := make([]byte, plen) 93 rlen, err := r.Read(preamble) 94 if rlen == 0 { 95 return ct, r, errors.New("empty response") 96 } 97 if err != nil && err != io.EOF { 98 return ct, r, err 99 } 100 101 preambleR := bytes.NewReader(preamble[:rlen]) 102 bodyReader := io.MultiReader(preambleR, r) 103 // Some web servers will use application/octet-stream as the default 104 // content type for files without an extension (e.g. 'Dockerfile') 105 // so if we receive this value we better check for text content 106 contentType := ct 107 if len(ct) == 0 || ct == mimeTypes.OctetStream { 108 contentType, _, err = detectContentType(preamble) 109 if err != nil { 110 return contentType, bodyReader, err 111 } 112 } 113 114 contentType = selectAcceptableMIME(contentType) 115 var cterr error 116 if len(contentType) == 0 { 117 cterr = fmt.Errorf("unsupported Content-Type %q", ct) 118 contentType = ct 119 } 120 121 return contentType, bodyReader, cterr 122 } 123 124 func selectAcceptableMIME(ct string) string { 125 return mimeRe.FindString(ct) 126 }