2018-02-05 16:05:59 -05:00
|
|
|
package httputils // import "github.com/docker/docker/api/server/httputils"
|
2015-09-23 19:42:08 -04:00
|
|
|
|
|
|
|
import (
|
2018-04-19 18:30:59 -04:00
|
|
|
"context"
|
2015-09-23 19:42:08 -04:00
|
|
|
"io"
|
2017-06-23 10:26:17 -04:00
|
|
|
"mime"
|
2015-09-23 19:42:08 -04:00
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
|
2019-02-09 09:53:29 -05:00
|
|
|
"github.com/docker/docker/api/types"
|
|
|
|
"github.com/docker/docker/api/types/versions"
|
2018-01-11 14:53:06 -05:00
|
|
|
"github.com/docker/docker/errdefs"
|
2019-02-09 09:53:29 -05:00
|
|
|
"github.com/gorilla/mux"
|
2017-07-19 10:20:13 -04:00
|
|
|
"github.com/pkg/errors"
|
2017-07-26 17:42:13 -04:00
|
|
|
"github.com/sirupsen/logrus"
|
2019-02-09 09:53:29 -05:00
|
|
|
"google.golang.org/grpc/status"
|
2015-09-23 19:42:08 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
// APIVersionKey is the client's requested API version.
|
2018-08-21 22:20:22 -04:00
|
|
|
type APIVersionKey struct{}
|
2015-09-23 19:42:08 -04:00
|
|
|
|
|
|
|
// APIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints.
|
2016-05-07 21:36:10 -04:00
|
|
|
// Any function that has the appropriate signature can be registered as an API endpoint (e.g. getVersion).
|
2015-09-23 19:42:08 -04:00
|
|
|
type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
|
|
|
|
|
|
|
|
// HijackConnection interrupts the http response writer to get the
|
|
|
|
// underlying connection and operate with it.
|
|
|
|
func HijackConnection(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
|
|
|
conn, _, err := w.(http.Hijacker).Hijack()
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
// Flush the options to make sure the client sets the raw mode
|
2019-08-05 11:51:22 -04:00
|
|
|
_, _ = conn.Write([]byte{})
|
2015-09-23 19:42:08 -04:00
|
|
|
return conn, conn, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CloseStreams ensures that a list for http streams are properly closed.
|
|
|
|
func CloseStreams(streams ...interface{}) {
|
|
|
|
for _, stream := range streams {
|
|
|
|
if tcpc, ok := stream.(interface {
|
|
|
|
CloseWrite() error
|
|
|
|
}); ok {
|
2019-08-05 11:51:22 -04:00
|
|
|
_ = tcpc.CloseWrite()
|
2015-09-23 19:42:08 -04:00
|
|
|
} else if closer, ok := stream.(io.Closer); ok {
|
2019-08-05 11:51:22 -04:00
|
|
|
_ = closer.Close()
|
2015-09-23 19:42:08 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// CheckForJSON makes sure that the request's Content-Type is application/json.
|
|
|
|
func CheckForJSON(r *http.Request) error {
|
|
|
|
ct := r.Header.Get("Content-Type")
|
|
|
|
|
|
|
|
// No Content-Type header is ok as long as there's no Body
|
|
|
|
if ct == "" {
|
|
|
|
if r.Body == nil || r.ContentLength == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise it better be json
|
2017-06-23 10:26:17 -04:00
|
|
|
if matchesContentType(ct, "application/json") {
|
2015-09-23 19:42:08 -04:00
|
|
|
return nil
|
|
|
|
}
|
2017-11-28 23:09:37 -05:00
|
|
|
return errdefs.InvalidParameter(errors.Errorf("Content-Type specified (%s) must be 'application/json'", ct))
|
2015-09-23 19:42:08 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// ParseForm ensures the request form is parsed even with invalid content types.
|
|
|
|
// If we don't do this, POST method without Content-type (even with empty body) will fail.
|
|
|
|
func ParseForm(r *http.Request) error {
|
|
|
|
if r == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
2017-11-28 23:09:37 -05:00
|
|
|
return errdefs.InvalidParameter(err)
|
2015-09-23 19:42:08 -04:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// VersionFromContext returns an API version from the context using APIVersionKey.
|
|
|
|
// It panics if the context value does not have version.Version type.
|
2016-11-14 12:15:24 -05:00
|
|
|
func VersionFromContext(ctx context.Context) string {
|
2015-09-23 19:42:08 -04:00
|
|
|
if ctx == nil {
|
2016-11-14 12:15:24 -05:00
|
|
|
return ""
|
2015-09-23 19:42:08 -04:00
|
|
|
}
|
2016-11-14 12:15:24 -05:00
|
|
|
|
2018-08-21 22:20:22 -04:00
|
|
|
if val := ctx.Value(APIVersionKey{}); val != nil {
|
2016-11-14 12:15:24 -05:00
|
|
|
return val.(string)
|
2015-09-23 19:42:08 -04:00
|
|
|
}
|
2016-11-14 12:15:24 -05:00
|
|
|
|
|
|
|
return ""
|
2015-09-23 19:42:08 -04:00
|
|
|
}
|
2017-06-23 10:26:17 -04:00
|
|
|
|
2019-02-09 09:53:29 -05:00
|
|
|
// MakeErrorHandler makes an HTTP handler that decodes a Docker error and
|
|
|
|
// returns it in the response.
|
|
|
|
func MakeErrorHandler(err error) http.HandlerFunc {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
statusCode := errdefs.GetHTTPErrorStatusCode(err)
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
if apiVersionSupportsJSONErrors(vars["version"]) {
|
|
|
|
response := &types.ErrorResponse{
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
2019-08-05 11:51:22 -04:00
|
|
|
_ = WriteJSON(w, statusCode, response)
|
2019-02-09 09:53:29 -05:00
|
|
|
} else {
|
|
|
|
http.Error(w, status.Convert(err).Message(), statusCode)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func apiVersionSupportsJSONErrors(version string) bool {
|
|
|
|
const firstAPIVersionWithJSONErrors = "1.23"
|
|
|
|
return version == "" || versions.GreaterThan(version, firstAPIVersionWithJSONErrors)
|
|
|
|
}
|
|
|
|
|
2017-06-23 10:26:17 -04:00
|
|
|
// matchesContentType validates the content type against the expected one
|
|
|
|
func matchesContentType(contentType, expectedType string) bool {
|
|
|
|
mimetype, _, err := mime.ParseMediaType(contentType)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error parsing media type: %s error: %v", contentType, err)
|
|
|
|
}
|
|
|
|
return err == nil && mimetype == expectedType
|
|
|
|
}
|