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
15package spec
16
17import (
18	"fmt"
19	"net/url"
20	"os"
21	"path"
22	"path/filepath"
23	"strings"
24)
25
26// normalize absolute path for cache.
27// on Windows, drive letters should be converted to lower as scheme in net/url.URL
28func normalizeAbsPath(path string) string {
29	u, err := url.Parse(path)
30	if err != nil {
31		debugLog("normalize absolute path failed: %s", err)
32		return path
33	}
34	return u.String()
35}
36
37// base or refPath could be a file path or a URL
38// given a base absolute path and a ref path, return the absolute path of refPath
39// 1) if refPath is absolute, return it
40// 2) if refPath is relative, join it with basePath keeping the scheme, hosts, and ports if exists
41// base could be a directory or a full file path
42func normalizePaths(refPath, base string) string {
43	refURL, _ := url.Parse(refPath)
44	if path.IsAbs(refURL.Path) || filepath.IsAbs(refPath) {
45		// refPath is actually absolute
46		if refURL.Host != "" {
47			return refPath
48		}
49		parts := strings.Split(refPath, "#")
50		result := filepath.FromSlash(parts[0])
51		if len(parts) == 2 {
52			result += "#" + parts[1]
53		}
54		return result
55	}
56
57	// relative refPath
58	baseURL, _ := url.Parse(base)
59	if !strings.HasPrefix(refPath, "#") {
60		// combining paths
61		if baseURL.Host != "" {
62			baseURL.Path = path.Join(path.Dir(baseURL.Path), refURL.Path)
63		} else { // base is a file
64			newBase := fmt.Sprintf("%s#%s", filepath.Join(filepath.Dir(base), filepath.FromSlash(refURL.Path)), refURL.Fragment)
65			return newBase
66		}
67
68	}
69	// copying fragment from ref to base
70	baseURL.Fragment = refURL.Fragment
71	return baseURL.String()
72}
73
74// denormalizePaths returns to simplest notation on file $ref,
75// i.e. strips the absolute path and sets a path relative to the base path.
76//
77// This is currently used when we rewrite ref after a circular ref has been detected
78func denormalizeFileRef(ref *Ref, relativeBase, originalRelativeBase string) *Ref {
79	debugLog("denormalizeFileRef for: %s", ref.String())
80
81	if ref.String() == "" || ref.IsRoot() || ref.HasFragmentOnly {
82		return ref
83	}
84	// strip relativeBase from URI
85	relativeBaseURL, _ := url.Parse(relativeBase)
86	relativeBaseURL.Fragment = ""
87
88	if relativeBaseURL.IsAbs() && strings.HasPrefix(ref.String(), relativeBase) {
89		// this should work for absolute URI (e.g. http://...): we have an exact match, just trim prefix
90		r, _ := NewRef(strings.TrimPrefix(ref.String(), relativeBase))
91		return &r
92	}
93
94	if relativeBaseURL.IsAbs() {
95		// other absolute URL get unchanged (i.e. with a non-empty scheme)
96		return ref
97	}
98
99	// for relative file URIs:
100	originalRelativeBaseURL, _ := url.Parse(originalRelativeBase)
101	originalRelativeBaseURL.Fragment = ""
102	if strings.HasPrefix(ref.String(), originalRelativeBaseURL.String()) {
103		// the resulting ref is in the expanded spec: return a local ref
104		r, _ := NewRef(strings.TrimPrefix(ref.String(), originalRelativeBaseURL.String()))
105		return &r
106	}
107
108	// check if we may set a relative path, considering the original base path for this spec.
109	// Example:
110	//   spec is located at /mypath/spec.json
111	//   my normalized ref points to: /mypath/item.json#/target
112	//   expected result: item.json#/target
113	parts := strings.Split(ref.String(), "#")
114	relativePath, err := filepath.Rel(path.Dir(originalRelativeBaseURL.String()), parts[0])
115	if err != nil {
116		// there is no common ancestor (e.g. different drives on windows)
117		// leaves the ref unchanged
118		return ref
119	}
120	if len(parts) == 2 {
121		relativePath += "#" + parts[1]
122	}
123	r, _ := NewRef(relativePath)
124	return &r
125}
126
127// relativeBase could be an ABSOLUTE file path or an ABSOLUTE URL
128func normalizeFileRef(ref *Ref, relativeBase string) *Ref {
129	// This is important for when the reference is pointing to the root schema
130	if ref.String() == "" {
131		r, _ := NewRef(relativeBase)
132		return &r
133	}
134
135	debugLog("normalizing %s against %s", ref.String(), relativeBase)
136
137	s := normalizePaths(ref.String(), relativeBase)
138	r, _ := NewRef(s)
139	return &r
140}
141
142// absPath returns the absolute path of a file
143func absPath(fname string) (string, error) {
144	if strings.HasPrefix(fname, "http") {
145		return fname, nil
146	}
147	if filepath.IsAbs(fname) {
148		return fname, nil
149	}
150	wd, err := os.Getwd()
151	return filepath.Join(wd, fname), err
152}
153