X-Git-Url: http://www.git.cypherpunks.ru/?a=blobdiff_plain;f=src%2Finternal%2Fsafefilepath%2Fpath_windows.go;h=7cfd6ce2eac2622b7a74135ea66bee460ae9c910;hb=cae35cd2582878341452547378ffdc14fdb0a022;hp=909c150edc87a281d94f648ae760163418e8edb1;hpb=dc74a3dd4f30c4a3df235aac8a9e84575767ba69;p=gostls13.git diff --git a/src/internal/safefilepath/path_windows.go b/src/internal/safefilepath/path_windows.go index 909c150edc..7cfd6ce2ea 100644 --- a/src/internal/safefilepath/path_windows.go +++ b/src/internal/safefilepath/path_windows.go @@ -20,15 +20,10 @@ func fromFS(path string) (string, error) { for p := path; p != ""; { // Find the next path element. i := 0 - dot := -1 for i < len(p) && p[i] != '/' { switch p[i] { case 0, '\\', ':': return "", errInvalidPath - case '.': - if dot < 0 { - dot = i - } } i++ } @@ -39,22 +34,8 @@ func fromFS(path string) (string, error) { } else { p = "" } - // Trim the extension and look for a reserved name. - base := part - if dot >= 0 { - base = part[:dot] - } - if isReservedName(base) { - if dot < 0 { - return "", errInvalidPath - } - // The path element is a reserved name with an extension. - // Some Windows versions consider this a reserved name, - // while others do not. Use FullPath to see if the name is - // reserved. - if p, _ := syscall.FullPath(part); len(p) >= 4 && p[:4] == `\\.\` { - return "", errInvalidPath - } + if IsReservedName(part) { + return "", errInvalidPath } } if containsSlash { @@ -70,23 +51,88 @@ func fromFS(path string) (string, error) { return path, nil } -// isReservedName reports if name is a Windows reserved device name. +// IsReservedName reports if name is a Windows reserved device name. // It does not detect names with an extension, which are also reserved on some Windows versions. // // For details, search for PRN in // https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file. -func isReservedName(name string) bool { - if 3 <= len(name) && len(name) <= 4 { +func IsReservedName(name string) bool { + // Device names can have arbitrary trailing characters following a dot or colon. + base := name + for i := 0; i < len(base); i++ { + switch base[i] { + case ':', '.': + base = base[:i] + } + } + // Trailing spaces in the last path element are ignored. + for len(base) > 0 && base[len(base)-1] == ' ' { + base = base[:len(base)-1] + } + if !isReservedBaseName(base) { + return false + } + if len(base) == len(name) { + return true + } + // The path element is a reserved name with an extension. + // Some Windows versions consider this a reserved name, + // while others do not. Use FullPath to see if the name is + // reserved. + if p, _ := syscall.FullPath(name); len(p) >= 4 && p[:4] == `\\.\` { + return true + } + return false +} + +func isReservedBaseName(name string) bool { + if len(name) == 3 { switch string([]byte{toUpper(name[0]), toUpper(name[1]), toUpper(name[2])}) { case "CON", "PRN", "AUX", "NUL": - return len(name) == 3 + return true + } + } + if len(name) >= 4 { + switch string([]byte{toUpper(name[0]), toUpper(name[1]), toUpper(name[2])}) { case "COM", "LPT": - return len(name) == 4 && '1' <= name[3] && name[3] <= '9' + if len(name) == 4 && '1' <= name[3] && name[3] <= '9' { + return true + } + // Superscript ¹, ², and ³ are considered numbers as well. + switch name[3:] { + case "\u00b2", "\u00b3", "\u00b9": + return true + } + return false } } + + // Passing CONIN$ or CONOUT$ to CreateFile opens a console handle. + // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#consoles + // + // While CONIN$ and CONOUT$ aren't documented as being files, + // they behave the same as CON. For example, ./CONIN$ also opens the console input. + if len(name) == 6 && name[5] == '$' && equalFold(name, "CONIN$") { + return true + } + if len(name) == 7 && name[6] == '$' && equalFold(name, "CONOUT$") { + return true + } return false } +func equalFold(a, b string) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if toUpper(a[i]) != toUpper(b[i]) { + return false + } + } + return true +} + func toUpper(c byte) byte { if 'a' <= c && c <= 'z' { return c - ('a' - 'A')