github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/builder/remotecontext/remote.go (about) 1 package remotecontext // import "github.com/docker/docker/builder/remotecontext" 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "net" 8 "net/http" 9 "net/url" 10 "regexp" 11 12 "github.com/docker/docker/errdefs" 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, errors.Wrapf(err, "error downloading remote context %s", remoteURL) 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, errors.Wrapf(err, "error detecting content type for remote %s", remoteURL) 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 // #nosec G107 48 if resp, err = http.Get(address); err != nil { 49 if uerr, ok := err.(*url.Error); ok { 50 if derr, ok := uerr.Err.(*net.DNSError); ok && !derr.IsTimeout { 51 return nil, errdefs.NotFound(err) 52 } 53 } 54 return nil, errdefs.System(err) 55 } 56 if resp.StatusCode < 400 { 57 return resp, nil 58 } 59 msg := fmt.Sprintf("failed to GET %s with status %s", address, resp.Status) 60 body, err := io.ReadAll(resp.Body) 61 resp.Body.Close() 62 if err != nil { 63 return nil, errdefs.System(errors.New(msg + ": error reading body")) 64 } 65 66 msg += ": " + string(bytes.TrimSpace(body)) 67 switch resp.StatusCode { 68 case http.StatusNotFound: 69 return nil, errdefs.NotFound(errors.New(msg)) 70 case http.StatusBadRequest: 71 return nil, errdefs.InvalidParameter(errors.New(msg)) 72 case http.StatusUnauthorized: 73 return nil, errdefs.Unauthorized(errors.New(msg)) 74 case http.StatusForbidden: 75 return nil, errdefs.Forbidden(errors.New(msg)) 76 } 77 return nil, errdefs.Unknown(errors.New(msg)) 78 } 79 80 // inspectResponse looks into the http response data at r to determine whether its 81 // content-type is on the list of acceptable content types for remote build contexts. 82 // This function returns: 83 // - a string representation of the detected content-type 84 // - an io.Reader for the response body 85 // - an error value which will be non-nil either when something goes wrong while 86 // reading bytes from r or when the detected content-type is not acceptable. 87 func inspectResponse(ct string, r io.Reader, clen int64) (string, io.Reader, error) { 88 plen := clen 89 if plen <= 0 || plen > maxPreambleLength { 90 plen = maxPreambleLength 91 } 92 93 preamble := make([]byte, plen) 94 rlen, err := r.Read(preamble) 95 if rlen == 0 { 96 return ct, r, errors.New("empty response") 97 } 98 if err != nil && err != io.EOF { 99 return ct, r, err 100 } 101 102 preambleR := bytes.NewReader(preamble[:rlen]) 103 bodyReader := io.MultiReader(preambleR, r) 104 // Some web servers will use application/octet-stream as the default 105 // content type for files without an extension (e.g. 'Dockerfile') 106 // so if we receive this value we better check for text content 107 contentType := ct 108 if len(ct) == 0 || ct == mimeTypes.OctetStream { 109 contentType, _, err = detectContentType(preamble) 110 if err != nil { 111 return contentType, bodyReader, err 112 } 113 } 114 115 contentType = selectAcceptableMIME(contentType) 116 var cterr error 117 if len(contentType) == 0 { 118 cterr = fmt.Errorf("unsupported Content-Type %q", ct) 119 contentType = ct 120 } 121 122 return contentType, bodyReader, cterr 123 } 124 125 func selectAcceptableMIME(ct string) string { 126 return mimeRe.FindString(ct) 127 }