1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
|
package internal
import (
"errors"
"fmt"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
)
// annotation spex from https://github.com/opencontainers/image-spec/blob/master/annotations.md#pre-defined-annotation-keys
const (
separator = `(?:[-._:@+]|--)`
alphanum = `(?:[A-Za-z0-9]+)`
component = `(?:` + alphanum + `(?:` + separator + alphanum + `)*)`
)
var refRegexp = regexp.MustCompile(`^` + component + `(?:/` + component + `)*$`)
var windowsRefRegexp = regexp.MustCompile(`^([a-zA-Z]:\\.+?):(.*)$`)
// ValidateImageName returns nil if the image name is empty or matches the open-containers image name specs.
// In any other case an error is returned.
func ValidateImageName(image string) error {
if len(image) == 0 {
return nil
}
var err error
if !refRegexp.MatchString(image) {
err = fmt.Errorf("Invalid image %s", image)
}
return err
}
// SplitPathAndImage tries to split the provided OCI reference into the OCI path and image.
// Neither path nor image parts are validated at this stage.
func SplitPathAndImage(reference string) (string, string) {
if runtime.GOOS == "windows" {
return splitPathAndImageWindows(reference)
}
return splitPathAndImageNonWindows(reference)
}
func splitPathAndImageWindows(reference string) (string, string) {
groups := windowsRefRegexp.FindStringSubmatch(reference)
// nil group means no match
if groups == nil {
return reference, ""
}
// we expect three elements. First one full match, second the capture group for the path and
// the third the capture group for the image
if len(groups) != 3 {
return reference, ""
}
return groups[1], groups[2]
}
func splitPathAndImageNonWindows(reference string) (string, string) {
path, image, _ := strings.Cut(reference, ":") // image is set to "" if there is no ":"
return path, image
}
// ValidateOCIPath takes the OCI path and validates it.
func ValidateOCIPath(path string) error {
if runtime.GOOS == "windows" {
// On Windows we must allow for a ':' as part of the path
if strings.Count(path, ":") > 1 {
return fmt.Errorf("Invalid OCI reference: path %s contains more than one colon", path)
}
} else {
if strings.Contains(path, ":") {
return fmt.Errorf("Invalid OCI reference: path %s contains a colon", path)
}
}
return nil
}
// ValidateScope validates a policy configuration scope for an OCI transport.
func ValidateScope(scope string) error {
var err error
if runtime.GOOS == "windows" {
err = validateScopeWindows(scope)
} else {
err = validateScopeNonWindows(scope)
}
if err != nil {
return err
}
cleaned := filepath.Clean(scope)
if cleaned != scope {
return fmt.Errorf(`Invalid scope %s: Uses non-canonical path format, perhaps try with path %s`, scope, cleaned)
}
return nil
}
func validateScopeWindows(scope string) error {
matched, _ := regexp.MatchString(`^[a-zA-Z]:\\`, scope)
if !matched {
return fmt.Errorf("Invalid scope '%s'. Must be an absolute path", scope)
}
return nil
}
func validateScopeNonWindows(scope string) error {
if !strings.HasPrefix(scope, "/") {
return fmt.Errorf("Invalid scope %s: must be an absolute path", scope)
}
// Refuse also "/", otherwise "/" and "" would have the same semantics,
// and "" could be unexpectedly shadowed by the "/" entry.
if scope == "/" {
return errors.New(`Invalid scope "/": Use the generic default scope ""`)
}
return nil
}
// parseOCIReferenceName parses the image from the oci reference.
func parseOCIReferenceName(image string) (img string, index int, err error) {
index = -1
if strings.HasPrefix(image, "@") {
idx, err := strconv.Atoi(image[1:])
if err != nil {
return "", index, fmt.Errorf("Invalid source index @%s: not an integer: %w", image[1:], err)
}
if idx < 0 {
return "", index, fmt.Errorf("Invalid source index @%d: must not be negative", idx)
}
index = idx
} else {
img = image
}
return img, index, nil
}
// ParseReferenceIntoElements splits the oci reference into location, image name and source index if exists
func ParseReferenceIntoElements(reference string) (string, string, int, error) {
dir, image := SplitPathAndImage(reference)
image, index, err := parseOCIReferenceName(image)
if err != nil {
return "", "", -1, err
}
return dir, image, index, nil
}
|