github.com/rawahars/moby@v24.0.4+incompatible/api/server/httputils/httputils.go (about)

     1  package httputils // import "github.com/docker/docker/api/server/httputils"
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"io"
     7  	"mime"
     8  	"net/http"
     9  	"strings"
    10  
    11  	"github.com/docker/docker/errdefs"
    12  	"github.com/pkg/errors"
    13  )
    14  
    15  // APIVersionKey is the client's requested API version.
    16  type APIVersionKey struct{}
    17  
    18  // APIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints.
    19  // Any function that has the appropriate signature can be registered as an API endpoint (e.g. getVersion).
    20  type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
    21  
    22  // HijackConnection interrupts the http response writer to get the
    23  // underlying connection and operate with it.
    24  func HijackConnection(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
    25  	conn, _, err := w.(http.Hijacker).Hijack()
    26  	if err != nil {
    27  		return nil, nil, err
    28  	}
    29  	// Flush the options to make sure the client sets the raw mode
    30  	_, _ = conn.Write([]byte{})
    31  	return conn, conn, nil
    32  }
    33  
    34  // CloseStreams ensures that a list for http streams are properly closed.
    35  func CloseStreams(streams ...interface{}) {
    36  	for _, stream := range streams {
    37  		if tcpc, ok := stream.(interface {
    38  			CloseWrite() error
    39  		}); ok {
    40  			_ = tcpc.CloseWrite()
    41  		} else if closer, ok := stream.(io.Closer); ok {
    42  			_ = closer.Close()
    43  		}
    44  	}
    45  }
    46  
    47  // CheckForJSON makes sure that the request's Content-Type is application/json.
    48  func CheckForJSON(r *http.Request) error {
    49  	ct := r.Header.Get("Content-Type")
    50  
    51  	// No Content-Type header is ok as long as there's no Body
    52  	if ct == "" && (r.Body == nil || r.ContentLength == 0) {
    53  		return nil
    54  	}
    55  
    56  	// Otherwise it better be json
    57  	return matchesContentType(ct, "application/json")
    58  }
    59  
    60  // ReadJSON validates the request to have the correct content-type, and decodes
    61  // the request's Body into out.
    62  func ReadJSON(r *http.Request, out interface{}) error {
    63  	err := CheckForJSON(r)
    64  	if err != nil {
    65  		return err
    66  	}
    67  	if r.Body == nil || r.ContentLength == 0 {
    68  		// an empty body is not invalid, so don't return an error; see
    69  		// https://lists.w3.org/Archives/Public/ietf-http-wg/2010JulSep/0272.html
    70  		return nil
    71  	}
    72  
    73  	dec := json.NewDecoder(r.Body)
    74  	err = dec.Decode(out)
    75  	defer r.Body.Close()
    76  	if err != nil {
    77  		if err == io.EOF {
    78  			return errdefs.InvalidParameter(errors.New("invalid JSON: got EOF while reading request body"))
    79  		}
    80  		return errdefs.InvalidParameter(errors.Wrap(err, "invalid JSON"))
    81  	}
    82  
    83  	if dec.More() {
    84  		return errdefs.InvalidParameter(errors.New("unexpected content after JSON"))
    85  	}
    86  	return nil
    87  }
    88  
    89  // WriteJSON writes the value v to the http response stream as json with standard json encoding.
    90  func WriteJSON(w http.ResponseWriter, code int, v interface{}) error {
    91  	w.Header().Set("Content-Type", "application/json")
    92  	w.WriteHeader(code)
    93  	enc := json.NewEncoder(w)
    94  	enc.SetEscapeHTML(false)
    95  	return enc.Encode(v)
    96  }
    97  
    98  // ParseForm ensures the request form is parsed even with invalid content types.
    99  // If we don't do this, POST method without Content-type (even with empty body) will fail.
   100  func ParseForm(r *http.Request) error {
   101  	if r == nil {
   102  		return nil
   103  	}
   104  	if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
   105  		return errdefs.InvalidParameter(err)
   106  	}
   107  	return nil
   108  }
   109  
   110  // VersionFromContext returns an API version from the context using APIVersionKey.
   111  // It panics if the context value does not have version.Version type.
   112  func VersionFromContext(ctx context.Context) string {
   113  	if ctx == nil {
   114  		return ""
   115  	}
   116  
   117  	if val := ctx.Value(APIVersionKey{}); val != nil {
   118  		return val.(string)
   119  	}
   120  
   121  	return ""
   122  }
   123  
   124  // matchesContentType validates the content type against the expected one
   125  func matchesContentType(contentType, expectedType string) error {
   126  	mimetype, _, err := mime.ParseMediaType(contentType)
   127  	if err != nil {
   128  		return errdefs.InvalidParameter(errors.Wrapf(err, "malformed Content-Type header (%s)", contentType))
   129  	}
   130  	if mimetype != expectedType {
   131  		return errdefs.InvalidParameter(errors.Errorf("unsupported Content-Type header (%s): must be '%s'", contentType, expectedType))
   132  	}
   133  	return nil
   134  }