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 5package libkb 6 7import ( 8 "fmt" 9 "strings" 10) 11 12const escapeSacrificeForWindows = '‰' 13 14const disallowedRunesOnWindows = "<>:\"/\\|?*" 15 16var kbfsNameToWindowsReplaceSequence [][2]string 17var windowsNameToKbfsReplaceSequence [][2]string 18 19func 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. 55func 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. 70type InvalidWindowsNameError struct{} 71 72// Error implements the error interface. 73func (InvalidWindowsNameError) Error() string { 74 return "invalid windows path name" 75} 76 77// DecodeWindowsNameForKbfs decodes a path element encoded by 78// EncodeKbfsNameForWindows. 79func 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} 95