// Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package filepath_windows was copied from https://github.com/golang/go/blob/master/src/path/filepath/path_windows.go // to be able to verify Windows paths from Go code running on unix. package filepath_windows func isSlash(c uint8) bool { return c == '\\' || c == '/' } // IsAbs reports whether the path is absolute. func IsAbs(path string) (b bool) { l := volumeNameLen(path) if l == 0 { return false } // If the volume name starts with a double slash, this is an absolute path. if isSlash(path[0]) && isSlash(path[1]) { return true } path = path[l:] if path == "" { return false } return isSlash(path[0]) } func toUpper(c byte) byte { if 'a' <= c && c <= 'z' { return c - ('a' - 'A') } return c } // pathHasPrefixFold tests whether the path s begins with prefix, // ignoring case and treating all path separators as equivalent. // If s is longer than prefix, then s[len(prefix)] must be a path separator. func pathHasPrefixFold(s, prefix string) bool { if len(s) < len(prefix) { return false } for i := 0; i < len(prefix); i++ { if isSlash(prefix[i]) { if !isSlash(s[i]) { return false } } else if toUpper(prefix[i]) != toUpper(s[i]) { return false } } if len(s) > len(prefix) && !isSlash(s[len(prefix)]) { return false } return true } // volumeNameLen returns length of the leading volume name on Windows. // It returns 0 elsewhere. // // See: // https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats // https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html func volumeNameLen(path string) int { switch { case len(path) >= 2 && path[1] == ':': // Path starts with a drive letter. // // Not all Windows functions necessarily enforce the requirement that // drive letters be in the set A-Z, and we don't try to here. // // We don't handle the case of a path starting with a non-ASCII character, // in which case the "drive letter" might be multiple bytes long. return 2 case len(path) == 0 || !isSlash(path[0]): // Path does not have a volume component. return 0 case pathHasPrefixFold(path, `\\.\UNC`): // We're going to treat the UNC host and share as part of the volume // prefix for historical reasons, but this isn't really principled; // Windows's own GetFullPathName will happily remove the first // component of the path in this space, converting // \\.\unc\a\b\..\c into \\.\unc\a\c. return uncLen(path, len(`\\.\UNC\`)) case pathHasPrefixFold(path, `\\.`) || pathHasPrefixFold(path, `\\?`) || pathHasPrefixFold(path, `\??`): // Path starts with \\.\, and is a Local Device path; or // path starts with \\?\ or \??\ and is a Root Local Device path. // // We treat the next component after the \\.\ prefix as // part of the volume name, which means Clean(`\\?\c:\`) // won't remove the trailing \. (See #64028.) if len(path) == 3 { return 3 // exactly \\. } _, rest, ok := cutPath(path[4:]) if !ok { return len(path) } return len(path) - len(rest) - 1 case len(path) >= 2 && isSlash(path[1]): // Path starts with \\, and is a UNC path. return uncLen(path, 2) } return 0 } // uncLen returns the length of the volume prefix of a UNC path. // prefixLen is the prefix prior to the start of the UNC host; // for example, for "//host/share", the prefixLen is len("//")==2. func uncLen(path string, prefixLen int) int { count := 0 for i := prefixLen; i < len(path); i++ { if isSlash(path[i]) { count++ if count == 2 { return i } } } return len(path) } // cutPath slices path around the first path separator. func cutPath(path string) (before, after string, found bool) { for i := range path { if isSlash(path[i]) { return path[:i], path[i+1:], true } } return path, "", false }