github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/data/path_part_string.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 data
     6  
     7  import (
     8  	"encoding"
     9  	"encoding/json"
    10  	"fmt"
    11  	"strings"
    12  )
    13  
    14  const (
    15  	prefixToSkipObfsucation = ".kbfs_"
    16  	prefixFileInfo          = ".kbfs_fileinfo_"
    17  	suffixConflictStart     = ".conflicted ("
    18  	tarGzSuffix             = ".tar.gz"
    19  
    20  	// extRestoreMaxLen specifies the max length of an extension, such
    21  	// that it's restored to the obfuscated string.  It includes the
    22  	// leading dot.
    23  	extRestoreMaxLen = 5
    24  )
    25  
    26  // PathPartString returns an obfuscated version of part of a path,
    27  // preserving the suffixes if they're small enough or if it's a
    28  // conflicted copy of a file.  Two `PathPartString`s for the same
    29  // plaintext data, created using the same `Obfuscator`, should pass
    30  // equality checks.
    31  type PathPartString struct {
    32  	plaintext   string
    33  	toObfuscate string // if different from `plaintext`
    34  	ext         string
    35  	obfuscator  Obfuscator
    36  	isFileInfo  bool
    37  }
    38  
    39  var _ fmt.Stringer = PathPartString{}
    40  
    41  // NewPathPartString creates a new `PathPartString` instance, given a
    42  // plaintext string representing one part of a path, and a
    43  // `Obfuscator`.
    44  func NewPathPartString(
    45  	plaintext string, obfuscator Obfuscator) PathPartString {
    46  	var toObfuscate string
    47  	_, ext := SplitFileExtension(plaintext)
    48  	// Treat the long tarball suffix specially, since it's already
    49  	// parsed specially by `SplitFileExtension`.  Otherwise, strictly
    50  	// filter for length, so we don't accidentally expose suffixes
    51  	// that aren't common file suffixes, but instead part of the
    52  	// actual privately-named file (e.g., "this.file.is.secret").
    53  	if len(ext) > extRestoreMaxLen && ext != tarGzSuffix {
    54  		ext = ""
    55  	}
    56  
    57  	isFileInfo := false
    58  	if strings.HasPrefix(plaintext, prefixFileInfo) {
    59  		toObfuscate = strings.TrimPrefix(plaintext, prefixFileInfo)
    60  		isFileInfo = true
    61  	} else if strings.HasPrefix(plaintext, prefixToSkipObfsucation) {
    62  		// Nil out the obfuscator since this string doesn't need
    63  		// obfuscation.
    64  		obfuscator = nil
    65  	}
    66  
    67  	conflictedIndex := strings.LastIndex(plaintext, suffixConflictStart)
    68  	if conflictedIndex > 0 {
    69  		// If this is a conflict file, we should obfuscate everything
    70  		// besides the conflict part, to get a string that matches the
    71  		// non-conflict portion.  (Also continue to ignore any file
    72  		// info prefix when obfuscating, as above.)
    73  		if len(toObfuscate) == 0 {
    74  			toObfuscate = plaintext
    75  		}
    76  		toObfuscate = toObfuscate[:strings.LastIndex(
    77  			toObfuscate, suffixConflictStart)] + ext
    78  		ext = plaintext[conflictedIndex:]
    79  	}
    80  	return PathPartString{plaintext, toObfuscate, ext, obfuscator, isFileInfo}
    81  }
    82  
    83  func (pps PathPartString) String() string {
    84  	if pps.obfuscator == nil {
    85  		return pps.plaintext
    86  	}
    87  
    88  	var prefix string
    89  	if pps.isFileInfo {
    90  		// Preserve the fileinfo prefix.
    91  		prefix = prefixFileInfo
    92  	}
    93  
    94  	toObfuscate := pps.plaintext
    95  	if len(pps.toObfuscate) > 0 {
    96  		toObfuscate = pps.toObfuscate
    97  	}
    98  
    99  	ob := pps.obfuscator.Obfuscate(toObfuscate)
   100  	return prefix + ob + pps.ext
   101  }
   102  
   103  // Plaintext returns the plaintext underlying this string part.
   104  func (pps PathPartString) Plaintext() string {
   105  	return pps.plaintext
   106  }
   107  
   108  // Obfuscator returns this string's obfuscator.
   109  func (pps PathPartString) Obfuscator() Obfuscator {
   110  	return pps.obfuscator
   111  }
   112  
   113  var _ json.Marshaler = PathPartString{}
   114  
   115  // MarshalJSON implements the json.Marshaler interface for PathPartString.
   116  func (pps PathPartString) MarshalJSON() ([]byte, error) {
   117  	panic("Cannot marshal PathPartString; use `Plaintext()` instead")
   118  }
   119  
   120  var _ encoding.TextMarshaler = PathPartString{}
   121  
   122  // MarshalText implements the encoding.TextMarshaler interface for
   123  // PathPartString.
   124  func (pps PathPartString) MarshalText() ([]byte, error) {
   125  	panic("Cannot marshal PathPartString; use `Plaintext()` instead")
   126  }