...

Source file src/github.com/go-openapi/spec/normalizer_windows.go

Documentation: github.com/go-openapi/spec

     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  

View as plain text