github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/libkb/name_encoding.go (about) 1 // Copyright 2021 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 libkb 6 7 import ( 8 "fmt" 9 "strings" 10 ) 11 12 const escapeSacrificeForWindows = '‰' 13 14 const disallowedRunesOnWindows = "<>:\"/\\|?*" 15 16 var kbfsNameToWindowsReplaceSequence [][2]string 17 var windowsNameToKbfsReplaceSequence [][2]string 18 19 func init() { 20 makeEscapePair := func(r rune) [2]string { 21 return [2]string{string(r), fmt.Sprintf("‰%x", r)} 22 } 23 makeUnescapePairs := func(r rune) [][2]string { 24 lower := fmt.Sprintf("‰%x", r) 25 upper := fmt.Sprintf("‰%X", r) 26 if lower == upper { 27 return [][2]string{ 28 {lower, string(r)}, 29 } 30 } 31 return [][2]string{ 32 {lower, string(r)}, 33 {upper, string(r)}, 34 } 35 } 36 37 kbfsNameToWindowsReplaceSequence = nil 38 windowsNameToKbfsReplaceSequence = nil 39 40 kbfsNameToWindowsReplaceSequence = append(kbfsNameToWindowsReplaceSequence, 41 makeEscapePair(escapeSacrificeForWindows), 42 ) 43 for _, r := range disallowedRunesOnWindows { 44 kbfsNameToWindowsReplaceSequence = append( 45 kbfsNameToWindowsReplaceSequence, makeEscapePair(r)) 46 windowsNameToKbfsReplaceSequence = append( 47 windowsNameToKbfsReplaceSequence, makeUnescapePairs(r)...) 48 } 49 windowsNameToKbfsReplaceSequence = append(windowsNameToKbfsReplaceSequence, 50 makeUnescapePairs(escapeSacrificeForWindows)...) 51 } 52 53 // EncodeKbfsNameForWindows encodes a KBFS path element for Windows by 54 // escaping disallowed characters. 55 func EncodeKbfsNameForWindows(kbfsName string) (windowsName string) { 56 // fast path for names that don't have characters that need escaping 57 if !strings.ContainsAny(kbfsName, disallowedRunesOnWindows) && 58 !strings.ContainsRune(kbfsName, escapeSacrificeForWindows) { 59 return kbfsName 60 } 61 windowsName = kbfsName 62 for _, replacement := range kbfsNameToWindowsReplaceSequence { 63 windowsName = strings.ReplaceAll(windowsName, replacement[0], replacement[1]) 64 } 65 return windowsName 66 } 67 68 // InvalidWindowsNameError is the error returned when an invalid path name is 69 // passed in. 70 type InvalidWindowsNameError struct{} 71 72 // Error implements the error interface. 73 func (InvalidWindowsNameError) Error() string { 74 return "invalid windows path name" 75 } 76 77 // DecodeWindowsNameForKbfs decodes a path element encoded by 78 // EncodeKbfsNameForWindows. 79 func DecodeWindowsNameForKbfs(windowsName string) (kbfsName string, err error) { 80 if strings.ContainsAny(windowsName, disallowedRunesOnWindows) { 81 return "", InvalidWindowsNameError{} 82 } 83 84 // fast path for names that don't have escaped characters 85 if !strings.ContainsRune(windowsName, escapeSacrificeForWindows) { 86 return windowsName, nil 87 } 88 89 kbfsName = windowsName 90 for _, replacement := range windowsNameToKbfsReplaceSequence { 91 kbfsName = strings.ReplaceAll(kbfsName, replacement[0], replacement[1]) 92 } 93 return kbfsName, nil 94 }