github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/libfs/util.go (about)

     1  // Copyright 2019 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package libfs
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"os"
    11  	"path"
    12  	"regexp"
    13  	"strings"
    14  
    15  	billy "gopkg.in/src-d/go-billy.v4"
    16  )
    17  
    18  // RecursiveDelete deletes the given entry from the given filesystem.
    19  // If it's a directory, first all the items in the directory are
    20  // deleted recursively.
    21  func RecursiveDelete(
    22  	ctx context.Context, fs billy.Filesystem, fi os.FileInfo) error {
    23  	select {
    24  	case <-ctx.Done():
    25  		return ctx.Err()
    26  	default:
    27  	}
    28  
    29  	if !fi.IsDir() {
    30  		// Delete regular files and symlinks directly.
    31  		return fs.Remove(fi.Name())
    32  	}
    33  
    34  	subdirFS, err := fs.Chroot(fi.Name())
    35  	if err != nil {
    36  		return err
    37  	}
    38  
    39  	children, err := subdirFS.ReadDir("/")
    40  	if err != nil {
    41  		return err
    42  	}
    43  	for _, childFI := range children {
    44  		if childFI.Name() == "." {
    45  			continue
    46  		}
    47  		err := RecursiveDelete(ctx, subdirFS, childFI)
    48  		if err != nil {
    49  			return err
    50  		}
    51  	}
    52  
    53  	return fs.Remove(fi.Name())
    54  }
    55  
    56  var obsConflictRegexp = regexp.MustCompile(`-([[:digit:]]+)(\.|$)`)
    57  
    58  func stripObfuscatedConflictSuffix(s string) string {
    59  	replace := ""
    60  	if strings.Contains(s, ".") {
    61  		replace = "."
    62  	}
    63  	return obsConflictRegexp.ReplaceAllString(s, replace)
    64  }
    65  
    66  func deobfuscate(
    67  	ctx context.Context, fs *FS, pathParts []string) (res []string, err error) {
    68  	if len(pathParts) == 0 {
    69  		return nil, nil
    70  	}
    71  
    72  	fis, err := fs.ReadDir("")
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	elem := stripObfuscatedConflictSuffix(pathParts[0])
    78  	for _, fi := range fis {
    79  		name := fi.Name()
    80  		obsName := stripObfuscatedConflictSuffix(fs.PathForLogging(name))
    81  		if obsName == elem {
    82  			if len(pathParts) == 1 {
    83  				res = append(res, name)
    84  			} else {
    85  				childFS, err := fs.ChrootAsLibFS(name)
    86  				if err != nil {
    87  					return nil, err
    88  				}
    89  
    90  				children, err := deobfuscate(ctx, childFS, pathParts[1:])
    91  				if err != nil {
    92  					return nil, err
    93  				}
    94  				for _, c := range children {
    95  					res = append(res, path.Join(name, c))
    96  				}
    97  			}
    98  		}
    99  
   100  		if fi.Mode()&os.ModeSymlink > 0 {
   101  			link, err := fs.Readlink(name)
   102  			if err != nil {
   103  				return nil, err
   104  			}
   105  			obsName := fs.RootNode().ChildName(link).String()
   106  			if obsName == elem {
   107  				res = append(res, fmt.Sprintf("%s (%s)", name, link))
   108  			}
   109  		}
   110  	}
   111  	return res, nil
   112  }
   113  
   114  // Deobfuscate returns a set of possible plaintext paths, given an
   115  // obfuscated path as input.  The set is ambiguous because of possible
   116  // conflicts in the obfuscated name.  If the last element of the
   117  // obfuscated path matches the obfuscated version of a symlink target
   118  // within the target directory, the returned string includes the
   119  // symlink itself, followed by the target name in parentheses like
   120  // `/keybase/private/me/link (/etc/passwd)`.
   121  func Deobfuscate(
   122  	ctx context.Context, fs *FS, obfuscatedPath string) ([]string, error) {
   123  	s := strings.Split(obfuscatedPath, "/")
   124  	return deobfuscate(ctx, fs, s)
   125  }