...

Source file src/oras.land/oras-go/pkg/content/utils.go

Documentation: oras.land/oras-go/pkg/content

     1  /*
     2  Copyright The ORAS Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7  http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package content
    17  
    18  import (
    19  	"archive/tar"
    20  	"compress/gzip"
    21  	"fmt"
    22  	"io"
    23  	"os"
    24  	"path/filepath"
    25  	"strings"
    26  	"time"
    27  
    28  	digest "github.com/opencontainers/go-digest"
    29  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    30  	"github.com/pkg/errors"
    31  )
    32  
    33  // ResolveName resolves name from descriptor
    34  func ResolveName(desc ocispec.Descriptor) (string, bool) {
    35  	name, ok := desc.Annotations[ocispec.AnnotationTitle]
    36  	return name, ok
    37  }
    38  
    39  // tarDirectory walks the directory specified by path, and tar those files with a new
    40  // path prefix.
    41  func tarDirectory(root, prefix string, w io.Writer, stripTimes bool) error {
    42  	tw := tar.NewWriter(w)
    43  	defer tw.Close()
    44  	if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
    45  		if err != nil {
    46  			return err
    47  		}
    48  
    49  		// Rename path
    50  		name, err := filepath.Rel(root, path)
    51  		if err != nil {
    52  			return err
    53  		}
    54  		name = filepath.Join(prefix, name)
    55  		name = filepath.ToSlash(name)
    56  
    57  		// Generate header
    58  		var link string
    59  		mode := info.Mode()
    60  		if mode&os.ModeSymlink != 0 {
    61  			if link, err = os.Readlink(path); err != nil {
    62  				return err
    63  			}
    64  		}
    65  		header, err := tar.FileInfoHeader(info, link)
    66  		if err != nil {
    67  			return errors.Wrap(err, path)
    68  		}
    69  		header.Name = name
    70  		header.Uid = 0
    71  		header.Gid = 0
    72  		header.Uname = ""
    73  		header.Gname = ""
    74  
    75  		if stripTimes {
    76  			header.ModTime = time.Time{}
    77  			header.AccessTime = time.Time{}
    78  			header.ChangeTime = time.Time{}
    79  		}
    80  
    81  		// Write file
    82  		if err := tw.WriteHeader(header); err != nil {
    83  			return errors.Wrap(err, "tar")
    84  		}
    85  		if mode.IsRegular() {
    86  			file, err := os.Open(path)
    87  			if err != nil {
    88  				return err
    89  			}
    90  			defer file.Close()
    91  			if _, err := io.Copy(tw, file); err != nil {
    92  				return errors.Wrap(err, path)
    93  			}
    94  		}
    95  
    96  		return nil
    97  	}); err != nil {
    98  		return err
    99  	}
   100  	return nil
   101  }
   102  
   103  // extractTarDirectory extracts tar file to a directory specified by the `root`
   104  // parameter. The file name prefix is ensured to be the string specified by the
   105  // `prefix` parameter and is trimmed.
   106  func extractTarDirectory(root, prefix string, r io.Reader) error {
   107  	tr := tar.NewReader(r)
   108  	for {
   109  		header, err := tr.Next()
   110  		if err != nil {
   111  			if err == io.EOF {
   112  				return nil
   113  			}
   114  			return err
   115  		}
   116  
   117  		// Name check
   118  		name := header.Name
   119  		path, err := ensureBasePath(root, prefix, name)
   120  		if err != nil {
   121  			return err
   122  		}
   123  		path = filepath.Join(root, path)
   124  
   125  		// Link check
   126  		switch header.Typeflag {
   127  		case tar.TypeLink, tar.TypeSymlink:
   128  			link := header.Linkname
   129  			if !filepath.IsAbs(link) {
   130  				link = filepath.Join(filepath.Dir(name), link)
   131  			}
   132  			if _, err := ensureBasePath(root, prefix, link); err != nil {
   133  				return err
   134  			}
   135  		}
   136  
   137  		// Create content
   138  		switch header.Typeflag {
   139  		case tar.TypeReg:
   140  			err = writeFile(path, tr, header.FileInfo().Mode())
   141  		case tar.TypeDir:
   142  			err = os.MkdirAll(path, header.FileInfo().Mode())
   143  		case tar.TypeLink:
   144  			err = os.Link(header.Linkname, path)
   145  		case tar.TypeSymlink:
   146  			err = os.Symlink(header.Linkname, path)
   147  		default:
   148  			continue // Non-regular files are skipped
   149  		}
   150  		if err != nil {
   151  			return err
   152  		}
   153  
   154  		// Change access time and modification time if possible (error ignored)
   155  		os.Chtimes(path, header.AccessTime, header.ModTime)
   156  	}
   157  }
   158  
   159  // ensureBasePath ensures the target path is in the base path,
   160  // returning its relative path to the base path.
   161  func ensureBasePath(root, base, target string) (string, error) {
   162  	path, err := filepath.Rel(base, target)
   163  	if err != nil {
   164  		return "", err
   165  	}
   166  	cleanPath := filepath.ToSlash(filepath.Clean(path))
   167  	if cleanPath == ".." || strings.HasPrefix(cleanPath, "../") {
   168  		return "", fmt.Errorf("%q is outside of %q", target, base)
   169  	}
   170  
   171  	// No symbolic link allowed in the relative path
   172  	dir := filepath.Dir(path)
   173  	for dir != "." {
   174  		if info, err := os.Lstat(filepath.Join(root, dir)); err != nil {
   175  			if !os.IsNotExist(err) {
   176  				return "", err
   177  			}
   178  		} else if info.Mode()&os.ModeSymlink != 0 {
   179  			return "", fmt.Errorf("no symbolic link allowed between %q and %q", base, target)
   180  		}
   181  		dir = filepath.Dir(dir)
   182  	}
   183  
   184  	return path, nil
   185  }
   186  
   187  func writeFile(path string, r io.Reader, perm os.FileMode) error {
   188  	file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
   189  	if err != nil {
   190  		return err
   191  	}
   192  	defer file.Close()
   193  	_, err = io.Copy(file, r)
   194  	return err
   195  }
   196  
   197  func extractTarGzip(root, prefix, filename, checksum string) error {
   198  	file, err := os.Open(filename)
   199  	if err != nil {
   200  		return err
   201  	}
   202  	defer file.Close()
   203  	zr, err := gzip.NewReader(file)
   204  	if err != nil {
   205  		return err
   206  	}
   207  	defer zr.Close()
   208  	var r io.Reader = zr
   209  	var verifier digest.Verifier
   210  	if checksum != "" {
   211  		if digest, err := digest.Parse(checksum); err == nil {
   212  			verifier = digest.Verifier()
   213  			r = io.TeeReader(r, verifier)
   214  		}
   215  	}
   216  	if err := extractTarDirectory(root, prefix, r); err != nil {
   217  		return err
   218  	}
   219  	if verifier != nil && !verifier.Verified() {
   220  		return errors.New("content digest mismatch")
   221  	}
   222  	return nil
   223  }
   224  

View as plain text