...

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

Documentation: github.com/go-openapi/spec

     1  // Copyright 2015 go-swagger maintainers
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package spec
    16  
    17  import (
    18  	"net/url"
    19  	"path"
    20  	"strings"
    21  )
    22  
    23  const fileScheme = "file"
    24  
    25  // normalizeURI ensures that all $ref paths used internally by the expander are canonicalized.
    26  //
    27  // NOTE(windows): there is a tolerance over the strict URI format on windows.
    28  //
    29  // The normalizer accepts relative file URLs like 'Path\File.JSON' as well as absolute file URLs like
    30  // 'C:\Path\file.Yaml'.
    31  //
    32  // Both are canonicalized with a "file://" scheme, slashes and a lower-cased path:
    33  // 'file:///c:/path/file.yaml'
    34  //
    35  // URLs can be specified with a file scheme, like in 'file:///folder/file.json' or
    36  // 'file:///c:\folder\File.json'.
    37  //
    38  // URLs like file://C:\folder are considered invalid (i.e. there is no host 'c:\folder') and a "repair"
    39  // is attempted.
    40  //
    41  // The base path argument is assumed to be canonicalized (e.g. using normalizeBase()).
    42  func normalizeURI(refPath, base string) string {
    43  	refURL, err := parseURL(refPath)
    44  	if err != nil {
    45  		specLogger.Printf("warning: invalid URI in $ref  %q: %v", refPath, err)
    46  		refURL, refPath = repairURI(refPath)
    47  	}
    48  
    49  	fixWindowsURI(refURL, refPath) // noop on non-windows OS
    50  
    51  	refURL.Path = path.Clean(refURL.Path)
    52  	if refURL.Path == "." {
    53  		refURL.Path = ""
    54  	}
    55  
    56  	r := MustCreateRef(refURL.String())
    57  	if r.IsCanonical() {
    58  		return refURL.String()
    59  	}
    60  
    61  	baseURL, _ := parseURL(base)
    62  	if path.IsAbs(refURL.Path) {
    63  		baseURL.Path = refURL.Path
    64  	} else if refURL.Path != "" {
    65  		baseURL.Path = path.Join(path.Dir(baseURL.Path), refURL.Path)
    66  	}
    67  	// copying fragment from ref to base
    68  	baseURL.Fragment = refURL.Fragment
    69  
    70  	return baseURL.String()
    71  }
    72  
    73  // denormalizeRef returns the simplest notation for a normalized $ref, given the path of the original root document.
    74  //
    75  // When calling this, we assume that:
    76  // * $ref is a canonical URI
    77  // * originalRelativeBase is a canonical URI
    78  //
    79  // denormalizeRef is currently used when we rewrite a $ref after a circular $ref has been detected.
    80  // In this case, expansion stops and normally renders the internal canonical $ref.
    81  //
    82  // This internal $ref is eventually rebased to the original RelativeBase used for the expansion.
    83  //
    84  // There is a special case for schemas that are anchored with an "id":
    85  // in that case, the rebasing is performed // against the id only if this is an anchor for the initial root document.
    86  // All other intermediate "id"'s found along the way are ignored for the purpose of rebasing.
    87  func denormalizeRef(ref *Ref, originalRelativeBase, id string) Ref {
    88  	debugLog("denormalizeRef called:\n$ref: %q\noriginal: %s\nroot ID:%s", ref.String(), originalRelativeBase, id)
    89  
    90  	if ref.String() == "" || ref.IsRoot() || ref.HasFragmentOnly {
    91  		// short circuit: $ref to current doc
    92  		return *ref
    93  	}
    94  
    95  	if id != "" {
    96  		idBaseURL, err := parseURL(id)
    97  		if err == nil { // if the schema id is not usable as a URI, ignore it
    98  			if ref, ok := rebase(ref, idBaseURL, true); ok { // rebase, but keep references to root unchaged (do not want $ref: "")
    99  				// $ref relative to the ID of the schema in the root document
   100  				return ref
   101  			}
   102  		}
   103  	}
   104  
   105  	originalRelativeBaseURL, _ := parseURL(originalRelativeBase)
   106  
   107  	r, _ := rebase(ref, originalRelativeBaseURL, false)
   108  
   109  	return r
   110  }
   111  
   112  func rebase(ref *Ref, v *url.URL, notEqual bool) (Ref, bool) {
   113  	var newBase url.URL
   114  
   115  	u := ref.GetURL()
   116  
   117  	if u.Scheme != v.Scheme || u.Host != v.Host {
   118  		return *ref, false
   119  	}
   120  
   121  	docPath := v.Path
   122  	v.Path = path.Dir(v.Path)
   123  
   124  	if v.Path == "." {
   125  		v.Path = ""
   126  	} else if !strings.HasSuffix(v.Path, "/") {
   127  		v.Path += "/"
   128  	}
   129  
   130  	newBase.Fragment = u.Fragment
   131  
   132  	if strings.HasPrefix(u.Path, docPath) {
   133  		newBase.Path = strings.TrimPrefix(u.Path, docPath)
   134  	} else {
   135  		newBase.Path = strings.TrimPrefix(u.Path, v.Path)
   136  	}
   137  
   138  	if notEqual && newBase.Path == "" && newBase.Fragment == "" {
   139  		// do not want rebasing to end up in an empty $ref
   140  		return *ref, false
   141  	}
   142  
   143  	if path.IsAbs(newBase.Path) {
   144  		// whenever we end up with an absolute path, specify the scheme and host
   145  		newBase.Scheme = v.Scheme
   146  		newBase.Host = v.Host
   147  	}
   148  
   149  	return MustCreateRef(newBase.String()), true
   150  }
   151  
   152  // normalizeRef canonicalize a Ref, using a canonical relativeBase as its absolute anchor
   153  func normalizeRef(ref *Ref, relativeBase string) *Ref {
   154  	r := MustCreateRef(normalizeURI(ref.String(), relativeBase))
   155  	return &r
   156  }
   157  
   158  // normalizeBase performs a normalization of the input base path.
   159  //
   160  // This always yields a canonical URI (absolute), usable for the document cache.
   161  //
   162  // It ensures that all further internal work on basePath may safely assume
   163  // a non-empty, cross-platform, canonical URI (i.e. absolute).
   164  //
   165  // This normalization tolerates windows paths (e.g. C:\x\y\File.dat) and transform this
   166  // in a file:// URL with lower cased drive letter and path.
   167  //
   168  // See also: https://en.wikipedia.org/wiki/File_URI_scheme
   169  func normalizeBase(in string) string {
   170  	u, err := parseURL(in)
   171  	if err != nil {
   172  		specLogger.Printf("warning: invalid URI in RelativeBase  %q: %v", in, err)
   173  		u, in = repairURI(in)
   174  	}
   175  
   176  	u.Fragment = "" // any fragment in the base is irrelevant
   177  
   178  	fixWindowsURI(u, in) // noop on non-windows OS
   179  
   180  	u.Path = path.Clean(u.Path)
   181  	if u.Path == "." { // empty after Clean()
   182  		u.Path = ""
   183  	}
   184  
   185  	if u.Scheme != "" {
   186  		if path.IsAbs(u.Path) || u.Scheme != fileScheme {
   187  			// this is absolute or explicitly not a local file: we're good
   188  			return u.String()
   189  		}
   190  	}
   191  
   192  	// no scheme or file scheme with relative path: assume file and make it absolute
   193  	// enforce scheme file://... with absolute path.
   194  	//
   195  	// If the input path is relative, we anchor the path to the current working directory.
   196  	// NOTE: we may end up with a host component. Leave it unchanged: e.g. file://host/folder/file.json
   197  
   198  	u.Scheme = fileScheme
   199  	u.Path = absPath(u.Path) // platform-dependent
   200  	u.RawQuery = ""          // any query component is irrelevant for a base
   201  	return u.String()
   202  }
   203  

View as plain text