github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/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 }