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