// Source: https://github.com/distribution/distribution // Copyright 2014 https://github.com/distribution/distribution Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package storage import ( "fmt" "path" "strings" "github.com/opencontainers/go-digest" ) const ( storagePathRoot = "/" docker = "docker" blobs = "blobs" ) func pathFor(spec pathSpec) (string, error) { rootPrefix := []string{storagePathRoot} switch v := spec.(type) { case blobsPathSpec: blobsPathPrefix := rootPrefix blobsPathPrefix = append(blobsPathPrefix, blobs) return path.Join(blobsPathPrefix...), nil case blobPathSpec: components, err := digestPathComponents(v.digest, true) if err != nil { return "", err } blobPathPrefix := rootPrefix blobPathPrefix = append(blobPathPrefix, v.path, docker, blobs) return path.Join(append(blobPathPrefix, components...)...), nil case blobDataPathSpec: components, err := digestPathComponents(v.digest, true) if err != nil { return "", err } components = append(components, "data") blobPathPrefix := rootPrefix blobPathPrefix = append(blobPathPrefix, v.path, docker, "blobs") return path.Join(append(blobPathPrefix, components...)...), nil case uploadDataPathSpec: return path.Join(append(rootPrefix, v.path, docker, "_uploads", v.repoName, v.id, "data")...), nil case uploadHashStatePathSpec: offset := fmt.Sprintf("%d", v.offset) if v.list { offset = "" // Limit to the prefix for listing offsets. } return path.Join( append( rootPrefix, v.path, docker, "_uploads", v.repoName, v.id, "hashstates", string(v.alg), offset, )..., ), nil case repositoriesRootPathSpec: return path.Join(rootPrefix...), nil default: return "", fmt.Errorf("unknown path spec: %#v", v) } } // pathSpec is a type to mark structs as path specs. There is no // implementation because we'd like to keep the specs and the mappers // decoupled. type pathSpec interface { pathSpec() } // blobAlgorithmReplacer does some very simple path sanitization for user // input. Paths should be "safe" before getting this far due to strict digest // requirements but we can add further path conversion here, if needed. var blobAlgorithmReplacer = strings.NewReplacer( "+", "/", ".", "/", ";", "/", ) // blobsPathSpec contains the path for the blobs directory. type blobsPathSpec struct{} func (blobsPathSpec) pathSpec() {} // blobPathSpec contains the path for the registry global blob store. type blobPathSpec struct { digest digest.Digest path string } func (blobPathSpec) pathSpec() {} // blobDataPathSpec contains the path for the StorageService global blob store. For // now, this contains layer data, exclusively. type blobDataPathSpec struct { digest digest.Digest path string } func (blobDataPathSpec) pathSpec() {} // uploadDataPathSpec defines the path parameters of the data file for // uploads. type uploadDataPathSpec struct { path string repoName string id string } func (uploadDataPathSpec) pathSpec() {} // uploadHashStatePathSpec defines the path parameters for the file that stores // the hash function state of an upload at a specific byte offset. If `list` is // set, then the path mapper will generate a list prefix for all hash state // offsets for the upload identified by the name, id, and alg. type uploadHashStatePathSpec struct { path string repoName string id string alg digest.Algorithm offset int64 list bool } func (uploadHashStatePathSpec) pathSpec() {} // repositoriesRootPathSpec returns the root of repositories. type repositoriesRootPathSpec struct{} func (repositoriesRootPathSpec) pathSpec() {} // digestPathComponents provides a consistent path breakdown for a given // digest. For a generic digest, it will be as follows: // // / // // If multilevel is true, the first two bytes of the digest will separate // groups of digest folder. It will be as follows: // // // func digestPathComponents(dgst digest.Digest, multilevel bool) ([]string, error) { if err := dgst.Validate(); err != nil { return nil, err } algorithm := blobAlgorithmReplacer.Replace(string(dgst.Algorithm())) hex := dgst.Encoded() prefix := []string{algorithm} var suffix []string if multilevel { suffix = append(suffix, hex[:2]) } suffix = append(suffix, hex) return append(prefix, suffix...), nil }