github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/copy/fileinfo.go (about)

     1  package copy
     2  
     3  import (
     4  	"encoding/base64"
     5  	"encoding/json"
     6  	"net/http"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/hanks177/podman/v4/libpod/define"
    12  	"github.com/pkg/errors"
    13  )
    14  
    15  // XDockerContainerPathStatHeader is the *key* in http headers pointing to the
    16  // base64 encoded JSON payload of stating a path in a container.
    17  const XDockerContainerPathStatHeader = "X-Docker-Container-Path-Stat"
    18  
    19  // ErrENOENT mimics the stdlib's ErrENOENT and can be used to implement custom logic
    20  // while preserving the user-visible error message.
    21  var ErrENOENT = errors.New("No such file or directory")
    22  
    23  // FileInfo describes a file or directory and is returned by
    24  // (*CopyItem).Stat().
    25  type FileInfo = define.FileInfo
    26  
    27  // EncodeFileInfo serializes the specified FileInfo as a base64 encoded JSON
    28  // payload.  Intended for Docker compat.
    29  func EncodeFileInfo(info *FileInfo) (string, error) {
    30  	buf, err := json.Marshal(&info)
    31  	if err != nil {
    32  		return "", errors.Wrap(err, "failed to serialize file stats")
    33  	}
    34  	return base64.URLEncoding.EncodeToString(buf), nil
    35  }
    36  
    37  // ExtractFileInfoFromHeader extracts a base64 encoded JSON payload of a
    38  // FileInfo in the http header.  If no such header entry is found, nil is
    39  // returned.  Intended for Docker compat.
    40  func ExtractFileInfoFromHeader(header *http.Header) (*FileInfo, error) {
    41  	rawData := header.Get(XDockerContainerPathStatHeader)
    42  	if len(rawData) == 0 {
    43  		return nil, nil
    44  	}
    45  
    46  	info := FileInfo{}
    47  	base64Decoder := base64.NewDecoder(base64.URLEncoding, strings.NewReader(rawData))
    48  	if err := json.NewDecoder(base64Decoder).Decode(&info); err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	return &info, nil
    53  }
    54  
    55  // ResolveHostPath resolves the specified, possibly relative, path on the host.
    56  func ResolveHostPath(path string) (*FileInfo, error) {
    57  	resolvedHostPath, err := filepath.Abs(path)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	resolvedHostPath = PreserveBasePath(path, resolvedHostPath)
    62  
    63  	statInfo, err := os.Stat(resolvedHostPath)
    64  	if err != nil {
    65  		if os.IsNotExist(err) {
    66  			return nil, ErrENOENT
    67  		}
    68  		return nil, err
    69  	}
    70  
    71  	return &FileInfo{
    72  		Name:       statInfo.Name(),
    73  		Size:       statInfo.Size(),
    74  		Mode:       statInfo.Mode(),
    75  		ModTime:    statInfo.ModTime(),
    76  		IsDir:      statInfo.IsDir(),
    77  		LinkTarget: resolvedHostPath,
    78  	}, nil
    79  }
    80  
    81  // PreserveBasePath makes sure that the original base path (e.g., "/" or "./")
    82  // is preserved.  The filepath API among tends to clean up a bit too much but
    83  // we *must* preserve this data by all means.
    84  func PreserveBasePath(original, resolved string) string {
    85  	// Handle "/"
    86  	if strings.HasSuffix(original, "/") {
    87  		if !strings.HasSuffix(resolved, "/") {
    88  			resolved += "/"
    89  		}
    90  		return resolved
    91  	}
    92  
    93  	// Handle "/."
    94  	if strings.HasSuffix(original, "/.") {
    95  		if strings.HasSuffix(resolved, "/") { // could be root!
    96  			resolved += "."
    97  		} else if !strings.HasSuffix(resolved, "/.") {
    98  			resolved += "/."
    99  		}
   100  		return resolved
   101  	}
   102  
   103  	return resolved
   104  }