github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/internal/safefilepath/path_windows.go (about) 1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package safefilepath 6 7 import ( 8 "internal/bytealg" 9 "syscall" 10 ) 11 12 func localize(path string) (string, error) { 13 for i := 0; i < len(path); i++ { 14 switch path[i] { 15 case ':', '\\', 0: 16 return "", errInvalidPath 17 } 18 } 19 containsSlash := false 20 for p := path; p != ""; { 21 // Find the next path element. 22 var element string 23 i := bytealg.IndexByteString(p, '/') 24 if i < 0 { 25 element = p 26 p = "" 27 } else { 28 containsSlash = true 29 element = p[:i] 30 p = p[i+1:] 31 } 32 if IsReservedName(element) { 33 return "", errInvalidPath 34 } 35 } 36 if containsSlash { 37 // We can't depend on strings, so substitute \ for / manually. 38 buf := []byte(path) 39 for i, b := range buf { 40 if b == '/' { 41 buf[i] = '\\' 42 } 43 } 44 path = string(buf) 45 } 46 return path, nil 47 } 48 49 // IsReservedName reports if name is a Windows reserved device name. 50 // It does not detect names with an extension, which are also reserved on some Windows versions. 51 // 52 // For details, search for PRN in 53 // https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file. 54 func IsReservedName(name string) bool { 55 // Device names can have arbitrary trailing characters following a dot or colon. 56 base := name 57 for i := 0; i < len(base); i++ { 58 switch base[i] { 59 case ':', '.': 60 base = base[:i] 61 } 62 } 63 // Trailing spaces in the last path element are ignored. 64 for len(base) > 0 && base[len(base)-1] == ' ' { 65 base = base[:len(base)-1] 66 } 67 if !isReservedBaseName(base) { 68 return false 69 } 70 if len(base) == len(name) { 71 return true 72 } 73 // The path element is a reserved name with an extension. 74 // Some Windows versions consider this a reserved name, 75 // while others do not. Use FullPath to see if the name is 76 // reserved. 77 if p, _ := syscall.FullPath(name); len(p) >= 4 && p[:4] == `\\.\` { 78 return true 79 } 80 return false 81 } 82 83 func isReservedBaseName(name string) bool { 84 if len(name) == 3 { 85 switch string([]byte{toUpper(name[0]), toUpper(name[1]), toUpper(name[2])}) { 86 case "CON", "PRN", "AUX", "NUL": 87 return true 88 } 89 } 90 if len(name) >= 4 { 91 switch string([]byte{toUpper(name[0]), toUpper(name[1]), toUpper(name[2])}) { 92 case "COM", "LPT": 93 if len(name) == 4 && '1' <= name[3] && name[3] <= '9' { 94 return true 95 } 96 // Superscript ¹, ², and ³ are considered numbers as well. 97 switch name[3:] { 98 case "\u00b2", "\u00b3", "\u00b9": 99 return true 100 } 101 return false 102 } 103 } 104 105 // Passing CONIN$ or CONOUT$ to CreateFile opens a console handle. 106 // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#consoles 107 // 108 // While CONIN$ and CONOUT$ aren't documented as being files, 109 // they behave the same as CON. For example, ./CONIN$ also opens the console input. 110 if len(name) == 6 && name[5] == '$' && equalFold(name, "CONIN$") { 111 return true 112 } 113 if len(name) == 7 && name[6] == '$' && equalFold(name, "CONOUT$") { 114 return true 115 } 116 return false 117 } 118 119 func equalFold(a, b string) bool { 120 if len(a) != len(b) { 121 return false 122 } 123 for i := 0; i < len(a); i++ { 124 if toUpper(a[i]) != toUpper(b[i]) { 125 return false 126 } 127 } 128 return true 129 } 130 131 func toUpper(c byte) byte { 132 if 'a' <= c && c <= 'z' { 133 return c - ('a' - 'A') 134 } 135 return c 136 }