1// -build windows 2 3// Copyright 2015 go-swagger maintainers 4// 5// Licensed under the Apache License, Version 2.0 (the "License"); 6// you may not use this file except in compliance with the License. 7// You may obtain a copy of the License at 8// 9// http://www.apache.org/licenses/LICENSE-2.0 10// 11// Unless required by applicable law or agreed to in writing, software 12// distributed under the License is distributed on an "AS IS" BASIS, 13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14// See the License for the specific language governing permissions and 15// limitations under the License. 16 17package spec 18 19import ( 20 "net/url" 21 "os" 22 "path" 23 "path/filepath" 24 "strings" 25) 26 27// absPath makes a file path absolute and compatible with a URI path component 28// 29// The parameter must be a path, not an URI. 30func absPath(in string) string { 31 // NOTE(windows): filepath.Abs exhibits a special behavior on windows for empty paths. 32 // See https://github.com/golang/go/issues/24441 33 if in == "" { 34 in = "." 35 } 36 37 anchored, err := filepath.Abs(in) 38 if err != nil { 39 specLogger.Printf("warning: could not resolve current working directory: %v", err) 40 return in 41 } 42 43 pth := strings.ReplaceAll(strings.ToLower(anchored), `\`, `/`) 44 if !strings.HasPrefix(pth, "/") { 45 pth = "/" + pth 46 } 47 48 return path.Clean(pth) 49} 50 51// repairURI tolerates invalid file URIs with common typos 52// such as 'file://E:\folder\file', that break the regular URL parser. 53// 54// Adopting the same defaults as for unixes (e.g. return an empty path) would 55// result into a counter-intuitive result for that case (e.g. E:\folder\file is 56// eventually resolved as the current directory). The repair will detect the missing "/". 57// 58// Note that this only works for the file scheme. 59func repairURI(in string) (*url.URL, string) { 60 const prefix = fileScheme + "://" 61 if !strings.HasPrefix(in, prefix) { 62 // giving up: resolve to empty path 63 u, _ := url.Parse("") 64 65 return u, "" 66 } 67 68 // attempt the repair, stripping the scheme should be sufficient 69 u, _ := url.Parse(strings.TrimPrefix(in, prefix)) 70 debugLog("repaired URI: original: %q, repaired: %q", in, u.String()) 71 72 return u, u.String() 73} 74 75// fixWindowsURI tolerates an absolute file path on windows such as C:\Base\File.yaml or \\host\share\Base\File.yaml 76// and makes it a canonical URI: file:///c:/base/file.yaml 77// 78// Catch 22 notes for Windows: 79// 80// * There may be a drive letter on windows (it is lower-cased) 81// * There may be a share UNC, e.g. \\server\folder\data.xml 82// * Paths are case insensitive 83// * Paths may already contain slashes 84// * Paths must be slashed 85// 86// NOTE: there is no escaping. "/" may be valid separators just like "\". 87// We don't use ToSlash() (which escapes everything) because windows now also 88// tolerates the use of "/". Hence, both C:\File.yaml and C:/File.yaml will work. 89func fixWindowsURI(u *url.URL, in string) { 90 drive := filepath.VolumeName(in) 91 92 if len(drive) > 0 { 93 if len(u.Scheme) == 1 && strings.EqualFold(u.Scheme, drive[:1]) { // a path with a drive letter 94 u.Scheme = fileScheme 95 u.Host = "" 96 u.Path = strings.Join([]string{drive, u.Opaque, u.Path}, `/`) // reconstruct the full path component (no fragment, no query) 97 } else if u.Host == "" && strings.HasPrefix(u.Path, drive) { // a path with a \\host volume 98 // NOTE: the special host@port syntax for UNC is not supported (yet) 99 u.Scheme = fileScheme 100 101 // this is a modified version of filepath.Dir() to apply on the VolumeName itself 102 i := len(drive) - 1 103 for i >= 0 && !os.IsPathSeparator(drive[i]) { 104 i-- 105 } 106 host := drive[:i] // \\host\share => host 107 108 u.Path = strings.TrimPrefix(u.Path, host) 109 u.Host = strings.TrimPrefix(host, `\\`) 110 } 111 112 u.Opaque = "" 113 u.Path = strings.ReplaceAll(strings.ToLower(u.Path), `\`, `/`) 114 115 // ensure we form an absolute path 116 if !strings.HasPrefix(u.Path, "/") { 117 u.Path = "/" + u.Path 118 } 119 120 u.Path = path.Clean(u.Path) 121 122 return 123 } 124 125 if u.Scheme == fileScheme { 126 // Handle dodgy cases for file://{...} URIs on windows. 127 // A canonical URI should always be followed by an absolute path. 128 // 129 // Examples: 130 // * file:///folder/file => valid, unchanged 131 // * file:///c:\folder\file => slashed 132 // * file:///./folder/file => valid, cleaned to remove the dot 133 // * file:///.\folder\file => remapped to cwd 134 // * file:///. => dodgy, remapped to / (consistent with the behavior on unix) 135 // * file:///.. => dodgy, remapped to / (consistent with the behavior on unix) 136 if (!path.IsAbs(u.Path) && !filepath.IsAbs(u.Path)) || (strings.HasPrefix(u.Path, `/.`) && strings.Contains(u.Path, `\`)) { 137 // ensure we form an absolute path 138 u.Path, _ = filepath.Abs(strings.TrimLeft(u.Path, `/`)) 139 if !strings.HasPrefix(u.Path, "/") { 140 u.Path = "/" + u.Path 141 } 142 } 143 u.Path = strings.ToLower(u.Path) 144 } 145 146 // NOTE: lower case normalization does not propagate to inner resources, 147 // generated when rebasing: when joining a relative URI with a file to an absolute base, 148 // only the base is currently lower-cased. 149 // 150 // For now, we assume this is good enough for most use cases 151 // and try not to generate too many differences 152 // between the output produced on different platforms. 153 u.Path = path.Clean(strings.ReplaceAll(u.Path, `\`, `/`)) 154} 155