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 }