mirror of https://github.com/docker/buildx.git
234 lines
6.5 KiB
Go
234 lines
6.5 KiB
Go
/*
|
|
Copyright 2020 The Compose Specification Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package paths
|
|
|
|
// This file contains utilities to check for Windows absolute paths on Linux.
|
|
// The code in this file was largely copied from the Golang filepath package
|
|
// https://github.com/golang/go/blob/master/src/internal/filepathlite/path_windows.go
|
|
|
|
import "slices"
|
|
|
|
// 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.
|
|
// https://github.com/golang/go/blob/master/LICENSE
|
|
|
|
func IsPathSeparator(c uint8) bool {
|
|
return c == '\\' || c == '/'
|
|
}
|
|
|
|
// IsWindowsAbs reports whether the path is absolute.
|
|
// copied from IsAbs(path string) (b bool) from internal.filetpathlite
|
|
func IsWindowsAbs(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 IsPathSeparator(path[0]) && IsPathSeparator(path[1]) {
|
|
return true
|
|
}
|
|
path = path[l:]
|
|
if path == "" {
|
|
return false
|
|
}
|
|
return IsPathSeparator(path[0])
|
|
}
|
|
|
|
// 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 || !IsPathSeparator(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 && IsPathSeparator(path[1]):
|
|
// Path starts with \\, and is a UNC path.
|
|
return uncLen(path, 2)
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// 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 IsPathSeparator(prefix[i]) {
|
|
if !IsPathSeparator(s[i]) {
|
|
return false
|
|
}
|
|
} else if toUpper(prefix[i]) != toUpper(s[i]) {
|
|
return false
|
|
}
|
|
}
|
|
if len(s) > len(prefix) && !IsPathSeparator(s[len(prefix)]) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// 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 IsPathSeparator(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 IsPathSeparator(path[i]) {
|
|
return path[:i], path[i+1:], true
|
|
}
|
|
}
|
|
return path, "", false
|
|
}
|
|
|
|
// postClean adjusts the results of Clean to avoid turning a relative path
|
|
// into an absolute or rooted one.
|
|
func postClean(out *lazybuf) {
|
|
if out.volLen != 0 || out.buf == nil {
|
|
return
|
|
}
|
|
// If a ':' appears in the path element at the start of a path,
|
|
// insert a .\ at the beginning to avoid converting relative paths
|
|
// like a/../c: into c:.
|
|
for _, c := range out.buf {
|
|
if IsPathSeparator(c) {
|
|
break
|
|
}
|
|
if c == ':' {
|
|
out.prepend('.', Separator)
|
|
return
|
|
}
|
|
}
|
|
// If a path begins with \??\, insert a \. at the beginning
|
|
// to avoid converting paths like \a\..\??\c:\x into \??\c:\x
|
|
// (equivalent to c:\x).
|
|
if len(out.buf) >= 3 && IsPathSeparator(out.buf[0]) && out.buf[1] == '?' && out.buf[2] == '?' {
|
|
out.prepend(Separator, '.')
|
|
}
|
|
}
|
|
|
|
func toUpper(c byte) byte {
|
|
if 'a' <= c && c <= 'z' {
|
|
return c - ('a' - 'A')
|
|
}
|
|
return c
|
|
}
|
|
|
|
const (
|
|
Separator = '\\' // OS-specific path separator
|
|
)
|
|
|
|
// A lazybuf is a lazily constructed path buffer.
|
|
// It supports append, reading previously appended bytes,
|
|
// and retrieving the final string. It does not allocate a buffer
|
|
// to hold the output until that output diverges from s.
|
|
type lazybuf struct {
|
|
path string
|
|
buf []byte
|
|
w int
|
|
volAndPath string
|
|
volLen int
|
|
}
|
|
|
|
func (b *lazybuf) index(i int) byte {
|
|
if b.buf != nil {
|
|
return b.buf[i]
|
|
}
|
|
return b.path[i]
|
|
}
|
|
|
|
func (b *lazybuf) append(c byte) {
|
|
if b.buf == nil {
|
|
if b.w < len(b.path) && b.path[b.w] == c {
|
|
b.w++
|
|
return
|
|
}
|
|
b.buf = make([]byte, len(b.path))
|
|
copy(b.buf, b.path[:b.w])
|
|
}
|
|
b.buf[b.w] = c
|
|
b.w++
|
|
}
|
|
|
|
func (b *lazybuf) prepend(prefix ...byte) {
|
|
b.buf = slices.Insert(b.buf, 0, prefix...)
|
|
b.w += len(prefix)
|
|
}
|
|
|
|
func (b *lazybuf) string() string {
|
|
if b.buf == nil {
|
|
return b.volAndPath[:b.volLen+b.w]
|
|
}
|
|
return b.volAndPath[:b.volLen] + string(b.buf[:b.w])
|
|
}
|