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  }