github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/lib/encoder/encoder_test.go (about) 1 package encoder 2 3 import ( 4 "regexp" 5 "strconv" 6 "strings" 7 "testing" 8 ) 9 10 type testCase struct { 11 mask uint 12 in string 13 out string 14 } 15 16 func TestEncodeSingleMask(t *testing.T) { 17 for i, tc := range testCasesSingle { 18 e := MultiEncoder(tc.mask) 19 t.Run(strconv.FormatInt(int64(i), 10), func(t *testing.T) { 20 got := e.Encode(tc.in) 21 if got != tc.out { 22 t.Errorf("Encode(%q) want %q got %q", tc.in, tc.out, got) 23 } 24 got2 := e.Decode(got) 25 if got2 != tc.in { 26 t.Errorf("Decode(%q) want %q got %q", got, tc.in, got2) 27 } 28 }) 29 } 30 } 31 32 func TestEncodeSingleMaskEdge(t *testing.T) { 33 for i, tc := range testCasesSingleEdge { 34 e := MultiEncoder(tc.mask) 35 t.Run(strconv.FormatInt(int64(i), 10), func(t *testing.T) { 36 got := e.Encode(tc.in) 37 if got != tc.out { 38 t.Errorf("Encode(%q) want %q got %q", tc.in, tc.out, got) 39 } 40 got2 := e.Decode(got) 41 if got2 != tc.in { 42 t.Errorf("Decode(%q) want %q got %q", got, tc.in, got2) 43 } 44 }) 45 } 46 } 47 48 func TestEncodeInvalidUnicode(t *testing.T) { 49 for i, tc := range []testCase{ 50 { 51 mask: EncodeInvalidUtf8, 52 in: "\xBF", 53 out: "‛BF", 54 }, { 55 mask: EncodeInvalidUtf8, 56 in: "\xBF\xFE", 57 out: "‛BF‛FE", 58 }, { 59 mask: EncodeInvalidUtf8, 60 in: "a\xBF\xFEb", 61 out: "a‛BF‛FEb", 62 }, { 63 mask: EncodeInvalidUtf8, 64 in: "a\xBFξ\xFEb", 65 out: "a‛BFξ‛FEb", 66 }, { 67 mask: EncodeInvalidUtf8 | EncodeBackSlash, 68 in: "a\xBF\\\xFEb", 69 out: "a‛BF\‛FEb", 70 }, { 71 mask: 0, 72 in: "\xBF", 73 out: "\xBF", 74 }, { 75 mask: 0, 76 in: "\xBF\xFE", 77 out: "\xBF\xFE", 78 }, { 79 mask: 0, 80 in: "a\xBF\xFEb", 81 out: "a\xBF\xFEb", 82 }, { 83 mask: 0, 84 in: "a\xBFξ\xFEb", 85 out: "a\xBFξ\xFEb", 86 }, { 87 mask: EncodeBackSlash, 88 in: "a\xBF\\\xFEb", 89 out: "a\xBF\\xFEb", 90 }, 91 } { 92 e := MultiEncoder(tc.mask) 93 t.Run(strconv.FormatInt(int64(i), 10), func(t *testing.T) { 94 got := e.Encode(tc.in) 95 if got != tc.out { 96 t.Errorf("Encode(%q) want %q got %q", tc.in, tc.out, got) 97 } 98 got2 := e.Decode(got) 99 if got2 != tc.in { 100 t.Errorf("Decode(%q) want %q got %q", got, tc.in, got2) 101 } 102 }) 103 } 104 } 105 func TestDecodeHalf(t *testing.T) { 106 for i, tc := range []testCase{ 107 { 108 mask: 0, 109 in: "‛", 110 out: "‛", 111 }, { 112 mask: 0, 113 in: "‛‛", 114 out: "‛", 115 }, { 116 mask: 0, 117 in: "‛a‛", 118 out: "‛a‛", 119 }, { 120 mask: EncodeInvalidUtf8, 121 in: "a‛B‛Eg", 122 out: "a‛B‛Eg", 123 }, { 124 mask: EncodeInvalidUtf8, 125 in: "a‛B\‛Eg", 126 out: "a‛B\‛Eg", 127 }, { 128 mask: EncodeInvalidUtf8 | EncodeBackSlash, 129 in: "a‛B\‛Eg", 130 out: "a‛B\\‛Eg", 131 }, 132 } { 133 e := MultiEncoder(tc.mask) 134 t.Run(strconv.FormatInt(int64(i), 10), func(t *testing.T) { 135 got := e.Decode(tc.in) 136 if got != tc.out { 137 t.Errorf("Decode(%q) want %q got %q", tc.in, tc.out, got) 138 } 139 }) 140 } 141 } 142 143 const oneDrive = MultiEncoder( 144 EncodeStandard | 145 EncodeWin | 146 EncodeBackSlash | 147 EncodeHashPercent | 148 EncodeDel | 149 EncodeCtl | 150 EncodeLeftTilde | 151 EncodeRightSpace | 152 EncodeRightPeriod) 153 154 var benchTests = []struct { 155 in string 156 out string 157 }{ 158 {"", ""}, 159 {"abc 123", "abc 123"}, 160 {`\*<>?:|#%".~`, `\*<>?:|#%".~`}, 161 {`\*<>?:|#%".~/\*<>?:|#%".~`, `\*<>?:|#%".~/\*<>?:|#%".~`}, 162 {" leading space", " leading space"}, 163 {"~leading tilde", "~leading tilde"}, 164 {"trailing dot.", "trailing dot."}, 165 {" leading space/ leading space/ leading space", " leading space/ leading space/ leading space"}, 166 {"~leading tilde/~leading tilde/~leading tilde", "~leading tilde/~leading tilde/~leading tilde"}, 167 {"leading tilde/~leading tilde", "leading tilde/~leading tilde"}, 168 {"trailing dot./trailing dot./trailing dot.", "trailing dot./trailing dot./trailing dot."}, 169 } 170 171 func benchReplace(b *testing.B, f func(string) string) { 172 for range make([]struct{}, b.N) { 173 for _, test := range benchTests { 174 got := f(test.in) 175 if got != test.out { 176 b.Errorf("Encode(%q) want %q got %q", test.in, test.out, got) 177 } 178 } 179 } 180 } 181 182 func benchRestore(b *testing.B, f func(string) string) { 183 for range make([]struct{}, b.N) { 184 for _, test := range benchTests { 185 got := f(test.out) 186 if got != test.in { 187 b.Errorf("Decode(%q) want %q got %q", got, test.in, got) 188 } 189 } 190 } 191 } 192 func BenchmarkOneDriveReplaceNew(b *testing.B) { 193 benchReplace(b, oneDrive.Encode) 194 } 195 func BenchmarkOneDriveReplaceOld(b *testing.B) { 196 benchReplace(b, replaceReservedChars) 197 } 198 func BenchmarkOneDriveRestoreNew(b *testing.B) { 199 benchRestore(b, oneDrive.Decode) 200 } 201 func BenchmarkOneDriveRestoreOld(b *testing.B) { 202 benchRestore(b, restoreReservedChars) 203 } 204 205 var ( 206 charMap = map[rune]rune{ 207 '\\': '\', // FULLWIDTH REVERSE SOLIDUS 208 '*': '*', // FULLWIDTH ASTERISK 209 '<': '<', // FULLWIDTH LESS-THAN SIGN 210 '>': '>', // FULLWIDTH GREATER-THAN SIGN 211 '?': '?', // FULLWIDTH QUESTION MARK 212 ':': ':', // FULLWIDTH COLON 213 '|': '|', // FULLWIDTH VERTICAL LINE 214 '#': '#', // FULLWIDTH NUMBER SIGN 215 '%': '%', // FULLWIDTH PERCENT SIGN 216 '"': '"', // FULLWIDTH QUOTATION MARK - not on the list but seems to be reserved 217 '.': '.', // FULLWIDTH FULL STOP 218 '~': '~', // FULLWIDTH TILDE 219 ' ': '␠', // SYMBOL FOR SPACE 220 } 221 invCharMap map[rune]rune 222 fixEndingInPeriod = regexp.MustCompile(`\.(/|$)`) 223 fixEndingWithSpace = regexp.MustCompile(` (/|$)`) 224 fixStartingWithTilde = regexp.MustCompile(`(/|^)~`) 225 ) 226 227 func init() { 228 // Create inverse charMap 229 invCharMap = make(map[rune]rune, len(charMap)) 230 for k, v := range charMap { 231 invCharMap[v] = k 232 } 233 } 234 235 // replaceReservedChars takes a path and substitutes any reserved 236 // characters in it 237 func replaceReservedChars(in string) string { 238 // Folder names can't end with a period '.' 239 in = fixEndingInPeriod.ReplaceAllString(in, string(charMap['.'])+"$1") 240 // OneDrive for Business file or folder names cannot begin with a tilde '~' 241 in = fixStartingWithTilde.ReplaceAllString(in, "$1"+string(charMap['~'])) 242 // Apparently file names can't start with space either 243 in = fixEndingWithSpace.ReplaceAllString(in, string(charMap[' '])+"$1") 244 // Encode reserved characters 245 return strings.Map(func(c rune) rune { 246 if replacement, ok := charMap[c]; ok && c != '.' && c != '~' && c != ' ' { 247 return replacement 248 } 249 return c 250 }, in) 251 } 252 253 // restoreReservedChars takes a path and undoes any substitutions 254 // made by replaceReservedChars 255 func restoreReservedChars(in string) string { 256 return strings.Map(func(c rune) rune { 257 if replacement, ok := invCharMap[c]; ok { 258 return replacement 259 } 260 return c 261 }, in) 262 }