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 }