github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/api/handlers/utils/handler.go (about)

     1  package utils
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"net/http"
     7  	"net/url"
     8  	"os"
     9  	"strings"
    10  	"unsafe"
    11  
    12  	"github.com/blang/semver"
    13  	"github.com/hanks177/podman/v4/version"
    14  	"github.com/gorilla/mux"
    15  	jsoniter "github.com/json-iterator/go"
    16  	"github.com/pkg/errors"
    17  	"github.com/sirupsen/logrus"
    18  )
    19  
    20  var (
    21  	// ErrVersionNotGiven returned when version not given by client
    22  	ErrVersionNotGiven = errors.New("version not given in URL path")
    23  	// ErrVersionNotSupported returned when given version is too old
    24  	ErrVersionNotSupported = errors.New("given version is not supported")
    25  )
    26  
    27  // IsLibpodRequest returns true if the request related to a libpod endpoint
    28  // (e.g., /v2/libpod/...).
    29  func IsLibpodRequest(r *http.Request) bool {
    30  	split := strings.Split(r.URL.String(), "/")
    31  	return len(split) >= 3 && split[2] == "libpod"
    32  }
    33  
    34  // SupportedVersion validates that the version provided by client is included in the given condition
    35  // https://github.com/blang/semver#ranges provides the details for writing conditions
    36  // If a version is not given in URL path, ErrVersionNotGiven is returned
    37  func SupportedVersion(r *http.Request, condition string) (semver.Version, error) {
    38  	version := semver.Version{}
    39  	val, ok := mux.Vars(r)["version"]
    40  	if !ok {
    41  		return version, ErrVersionNotGiven
    42  	}
    43  	safeVal, err := url.PathUnescape(val)
    44  	if err != nil {
    45  		return version, errors.Wrapf(err, "unable to unescape given API version: %q", val)
    46  	}
    47  	version, err = semver.ParseTolerant(safeVal)
    48  	if err != nil {
    49  		return version, errors.Wrapf(err, "unable to parse given API version: %q from %q", safeVal, val)
    50  	}
    51  
    52  	inRange, err := semver.ParseRange(condition)
    53  	if err != nil {
    54  		return version, err
    55  	}
    56  
    57  	if inRange(version) {
    58  		return version, nil
    59  	}
    60  	return version, ErrVersionNotSupported
    61  }
    62  
    63  // SupportedVersionWithDefaults validates that the version provided by client valid is supported by server
    64  // minimal API version <= client path version <= maximum API version focused on the endpoint tree from URL
    65  func SupportedVersionWithDefaults(r *http.Request) (semver.Version, error) {
    66  	tree := version.Compat
    67  	if IsLibpodRequest(r) {
    68  		tree = version.Libpod
    69  	}
    70  
    71  	return SupportedVersion(r,
    72  		fmt.Sprintf(">=%s <=%s", version.APIVersion[tree][version.MinimalAPI].String(),
    73  			version.APIVersion[tree][version.CurrentAPI].String()))
    74  }
    75  
    76  // WriteResponse encodes the given value as JSON or string and renders it for http client
    77  func WriteResponse(w http.ResponseWriter, code int, value interface{}) {
    78  	// RFC2616 explicitly states that the following status codes "MUST NOT
    79  	// include a message-body":
    80  	switch code {
    81  	case http.StatusNoContent, http.StatusNotModified: // 204, 304
    82  		w.WriteHeader(code)
    83  		return
    84  	}
    85  
    86  	switch v := value.(type) {
    87  	case string:
    88  		w.Header().Set("Content-Type", "text/plain; charset=us-ascii")
    89  		w.WriteHeader(code)
    90  
    91  		if _, err := fmt.Fprintln(w, v); err != nil {
    92  			logrus.Errorf("Unable to send string response: %q", err)
    93  		}
    94  	case *os.File:
    95  		w.Header().Set("Content-Type", "application/octet; charset=us-ascii")
    96  		w.WriteHeader(code)
    97  
    98  		if _, err := io.Copy(w, v); err != nil {
    99  			logrus.Errorf("Unable to copy to response: %q", err)
   100  		}
   101  	case io.Reader:
   102  		w.Header().Set("Content-Type", "application/x-tar")
   103  		w.WriteHeader(code)
   104  
   105  		if _, err := io.Copy(w, v); err != nil {
   106  			logrus.Errorf("Unable to copy to response: %q", err)
   107  		}
   108  	default:
   109  		WriteJSON(w, code, value)
   110  	}
   111  }
   112  
   113  func init() {
   114  	jsoniter.RegisterTypeEncoderFunc("error", MarshalErrorJSON, MarshalErrorJSONIsEmpty)
   115  	jsoniter.RegisterTypeEncoderFunc("[]error", MarshalErrorSliceJSON, MarshalErrorSliceJSONIsEmpty)
   116  }
   117  
   118  var json = jsoniter.ConfigCompatibleWithStandardLibrary
   119  
   120  // MarshalErrorJSON writes error to stream as string
   121  func MarshalErrorJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
   122  	p := *((*error)(ptr))
   123  	if p == nil {
   124  		stream.WriteNil()
   125  	} else {
   126  		stream.WriteString(p.Error())
   127  	}
   128  }
   129  
   130  // MarshalErrorSliceJSON writes []error to stream as []string JSON blob
   131  func MarshalErrorSliceJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
   132  	a := *((*[]error)(ptr))
   133  	switch {
   134  	case len(a) == 0:
   135  		stream.WriteNil()
   136  	default:
   137  		stream.WriteArrayStart()
   138  		for i, e := range a {
   139  			if i > 0 {
   140  				stream.WriteMore()
   141  			}
   142  			stream.WriteString(e.Error())
   143  		}
   144  		stream.WriteArrayEnd()
   145  	}
   146  }
   147  
   148  func MarshalErrorJSONIsEmpty(ptr unsafe.Pointer) bool {
   149  	return *((*error)(ptr)) == nil
   150  }
   151  
   152  func MarshalErrorSliceJSONIsEmpty(ptr unsafe.Pointer) bool {
   153  	return len(*((*[]error)(ptr))) == 0
   154  }
   155  
   156  // WriteJSON writes an interface value encoded as JSON to w
   157  func WriteJSON(w http.ResponseWriter, code int, value interface{}) {
   158  	// FIXME: we don't need to write the header in all/some circumstances.
   159  	w.Header().Set("Content-Type", "application/json")
   160  	w.WriteHeader(code)
   161  
   162  	coder := json.NewEncoder(w)
   163  	coder.SetEscapeHTML(true)
   164  	if err := coder.Encode(value); err != nil {
   165  		logrus.Errorf("Unable to write json: %q", err)
   166  	}
   167  }
   168  
   169  func FilterMapToString(filters map[string][]string) (string, error) {
   170  	f, err := json.Marshal(filters)
   171  	if err != nil {
   172  		return "", err
   173  	}
   174  	return string(f), nil
   175  }
   176  
   177  func GetVar(r *http.Request, k string) string {
   178  	val := mux.Vars(r)[k]
   179  	safeVal, err := url.PathUnescape(val)
   180  	if err != nil {
   181  		logrus.Error(errors.Wrapf(err, "failed to unescape mux key %s, value %s", k, val))
   182  		return val
   183  	}
   184  	return safeVal
   185  }
   186  
   187  // GetName extracts the name from the mux
   188  func GetName(r *http.Request) string {
   189  	return GetVar(r, "name")
   190  }