normalizer_windows.go 5.1 KB

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