| package httputils | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| "net/http" | |
| "strconv" | |
| "strings" | |
| "github.com/distribution/reference" | |
| "github.com/moby/moby/v2/errdefs" | |
| "github.com/pkg/errors" | |
| ocispec "github.com/opencontainers/image-spec/specs-go/v1" | |
| ) | |
| // BoolValue transforms a form value in different formats into a boolean type. | |
| func BoolValue(r *http.Request, k string) bool { | |
| switch strings.ToLower(strings.TrimSpace(r.FormValue(k))) { | |
| case "", "0", "no", "false", "none": | |
| return false | |
| default: | |
| return true | |
| } | |
| } | |
| // BoolValueOrDefault returns the default bool passed if the query param is | |
| // missing, otherwise it's just a proxy to boolValue above. | |
| func BoolValueOrDefault(r *http.Request, k string, d bool) bool { | |
| if _, ok := r.Form[k]; !ok { | |
| return d | |
| } | |
| return BoolValue(r, k) | |
| } | |
| // Uint32Value parses a form value into an uint32 type. It returns an error | |
| // if the field is not set, empty, incorrectly formatted, or out of range. | |
| func Uint32Value(r *http.Request, field string) (uint32, error) { | |
| // strconv.ParseUint returns an "strconv.ErrSyntax" for negative values, | |
| // not an "out of range". Strip the prefix before parsing, and use it | |
| // later to detect valid, but negative values. | |
| v, isNeg := strings.CutPrefix(r.Form.Get(field), "-") | |
| if v == "" || v[0] == '+' { | |
| // Fast-path for invalid values. | |
| return 0, strconv.ErrSyntax | |
| } | |
| i, err := strconv.ParseUint(v, 10, 32) | |
| if err != nil { | |
| // Unwrap to remove the 'strconv.ParseUint: parsing "some-invalid-value":' prefix. | |
| return 0, errors.Unwrap(err) | |
| } | |
| if isNeg { | |
| return 0, strconv.ErrRange | |
| } | |
| return uint32(i), nil | |
| } | |
| // Int64ValueOrZero parses a form value into an int64 type. | |
| // It returns 0 if the parsing fails. | |
| func Int64ValueOrZero(r *http.Request, k string) int64 { | |
| val, err := Int64ValueOrDefault(r, k, 0) | |
| if err != nil { | |
| return 0 | |
| } | |
| return val | |
| } | |
| // Int64ValueOrDefault parses a form value into an int64 type. If there is an | |
| // error, returns the error. If there is no value returns the default value. | |
| func Int64ValueOrDefault(r *http.Request, field string, def int64) (int64, error) { | |
| if r.Form.Get(field) != "" { | |
| value, err := strconv.ParseInt(r.Form.Get(field), 10, 64) | |
| return value, err | |
| } | |
| return def, nil | |
| } | |
| // RepoTagReference parses form values "repo" and "tag" and returns a valid | |
| // reference with repository and tag. | |
| // If repo is empty, then a nil reference is returned. | |
| // If no tag is given, then the default "latest" tag is set. | |
| func RepoTagReference(repo, tag string) (reference.NamedTagged, error) { | |
| if repo == "" { | |
| return nil, nil | |
| } | |
| ref, err := reference.ParseNormalizedNamed(repo) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if _, isDigested := ref.(reference.Digested); isDigested { | |
| return nil, errors.New("cannot import digest reference") | |
| } | |
| if tag != "" { | |
| return reference.WithTag(ref, tag) | |
| } | |
| withDefaultTag := reference.TagNameOnly(ref) | |
| namedTagged, ok := withDefaultTag.(reference.NamedTagged) | |
| if !ok { | |
| return nil, fmt.Errorf("unexpected reference: %q", ref.String()) | |
| } | |
| return namedTagged, nil | |
| } | |
| // ArchiveOptions stores archive information for different operations. | |
| type ArchiveOptions struct { | |
| Name string | |
| Path string | |
| } | |
| // ArchiveFormValues parses form values and turns them into ArchiveOptions. | |
| // It fails if the archive name and path are not in the request. | |
| func ArchiveFormValues(r *http.Request, vars map[string]string) (ArchiveOptions, error) { | |
| if err := ParseForm(r); err != nil { | |
| return ArchiveOptions{}, err | |
| } | |
| name := vars["name"] | |
| if name == "" { | |
| return ArchiveOptions{}, errdefs.InvalidParameter(errors.New("bad parameter: name cannot be empty")) | |
| } | |
| path := r.Form.Get("path") | |
| if path == "" { | |
| return ArchiveOptions{}, errdefs.InvalidParameter(errors.New("bad parameter: path cannot be empty")) | |
| } | |
| return ArchiveOptions{name, path}, nil | |
| } | |
| // DecodePlatform decodes the OCI platform JSON string into a Platform struct. | |
| func DecodePlatform(platformJSON string) (*ocispec.Platform, error) { | |
| var p ocispec.Platform | |
| if err := json.Unmarshal([]byte(platformJSON), &p); err != nil { | |
| return nil, errdefs.InvalidParameter(errors.Wrap(err, "failed to parse platform")) | |
| } | |
| hasAnyOptional := (p.Variant != "" || p.OSVersion != "" || len(p.OSFeatures) > 0) | |
| if p.OS == "" && p.Architecture == "" && hasAnyOptional { | |
| return nil, errdefs.InvalidParameter(errors.New("optional platform fields provided, but OS and Architecture are missing")) | |
| } | |
| if p.OS == "" || p.Architecture == "" { | |
| return nil, errdefs.InvalidParameter(errors.New("both OS and Architecture must be provided")) | |
| } | |
| return &p, nil | |
| } | |
| // DecodePlatforms decodes the OCI platform JSON string into a Platform struct. | |
| // | |
| // Typically, the argument is a value of: r.Form["platform"] | |
| func DecodePlatforms(platformJSONs []string) ([]ocispec.Platform, error) { | |
| if len(platformJSONs) == 0 { | |
| return nil, nil | |
| } | |
| var output []ocispec.Platform | |
| for _, platform := range platformJSONs { | |
| p, err := DecodePlatform(platform) | |
| if err != nil { | |
| return nil, err | |
| } | |
| output = append(output, *p) | |
| } | |
| return output, nil | |
| } |