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 17 package spec 18 19 import ( 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. 30 func 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. 59 func 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, _ := parseURL("") 64 65 return u, "" 66 } 67 68 // attempt the repair, stripping the scheme should be sufficient 69 u, _ := parseURL(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. 89 func 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