1fd4e5da5Sopenharmony_ci// Copyright 2019 The Go Authors.
2fd4e5da5Sopenharmony_ci//
3fd4e5da5Sopenharmony_ci// Licensed under the Apache License, Version 2.0 (the "License");
4fd4e5da5Sopenharmony_ci// you may not use this file except in compliance with the License.
5fd4e5da5Sopenharmony_ci// You may obtain a copy of the License at
6fd4e5da5Sopenharmony_ci//
7fd4e5da5Sopenharmony_ci//     http://www.apache.org/licenses/LICENSE-2.0
8fd4e5da5Sopenharmony_ci//
9fd4e5da5Sopenharmony_ci// Unless required by applicable law or agreed to in writing, software
10fd4e5da5Sopenharmony_ci// distributed under the License is distributed on an "AS IS" BASIS,
11fd4e5da5Sopenharmony_ci// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12fd4e5da5Sopenharmony_ci// See the License for the specific language governing permissions and
13fd4e5da5Sopenharmony_ci// limitations under the License.
14fd4e5da5Sopenharmony_ci
15fd4e5da5Sopenharmony_cipackage span
16fd4e5da5Sopenharmony_ci
17fd4e5da5Sopenharmony_ciimport (
18fd4e5da5Sopenharmony_ci	"fmt"
19fd4e5da5Sopenharmony_ci	"net/url"
20fd4e5da5Sopenharmony_ci	"os"
21fd4e5da5Sopenharmony_ci	"path"
22fd4e5da5Sopenharmony_ci	"path/filepath"
23fd4e5da5Sopenharmony_ci	"runtime"
24fd4e5da5Sopenharmony_ci	"strings"
25fd4e5da5Sopenharmony_ci	"unicode"
26fd4e5da5Sopenharmony_ci)
27fd4e5da5Sopenharmony_ci
28fd4e5da5Sopenharmony_ciconst fileScheme = "file"
29fd4e5da5Sopenharmony_ci
30fd4e5da5Sopenharmony_ci// URI represents the full URI for a file.
31fd4e5da5Sopenharmony_citype URI string
32fd4e5da5Sopenharmony_ci
33fd4e5da5Sopenharmony_ci// Filename returns the file path for the given URI.
34fd4e5da5Sopenharmony_ci// It is an error to call this on a URI that is not a valid filename.
35fd4e5da5Sopenharmony_cifunc (uri URI) Filename() string {
36fd4e5da5Sopenharmony_ci	filename, err := filename(uri)
37fd4e5da5Sopenharmony_ci	if err != nil {
38fd4e5da5Sopenharmony_ci		panic(err)
39fd4e5da5Sopenharmony_ci	}
40fd4e5da5Sopenharmony_ci	return filepath.FromSlash(filename)
41fd4e5da5Sopenharmony_ci}
42fd4e5da5Sopenharmony_ci
43fd4e5da5Sopenharmony_cifunc filename(uri URI) (string, error) {
44fd4e5da5Sopenharmony_ci	if uri == "" {
45fd4e5da5Sopenharmony_ci		return "", nil
46fd4e5da5Sopenharmony_ci	}
47fd4e5da5Sopenharmony_ci	u, err := url.ParseRequestURI(string(uri))
48fd4e5da5Sopenharmony_ci	if err != nil {
49fd4e5da5Sopenharmony_ci		return "", err
50fd4e5da5Sopenharmony_ci	}
51fd4e5da5Sopenharmony_ci	if u.Scheme != fileScheme {
52fd4e5da5Sopenharmony_ci		return "", fmt.Errorf("only file URIs are supported, got %q from %q", u.Scheme, uri)
53fd4e5da5Sopenharmony_ci	}
54fd4e5da5Sopenharmony_ci	if isWindowsDriveURI(u.Path) {
55fd4e5da5Sopenharmony_ci		u.Path = u.Path[1:]
56fd4e5da5Sopenharmony_ci	}
57fd4e5da5Sopenharmony_ci	return u.Path, nil
58fd4e5da5Sopenharmony_ci}
59fd4e5da5Sopenharmony_ci
60fd4e5da5Sopenharmony_ci// NewURI returns a span URI for the string.
61fd4e5da5Sopenharmony_ci// It will attempt to detect if the string is a file path or uri.
62fd4e5da5Sopenharmony_cifunc NewURI(s string) URI {
63fd4e5da5Sopenharmony_ci	if u, err := url.PathUnescape(s); err == nil {
64fd4e5da5Sopenharmony_ci		s = u
65fd4e5da5Sopenharmony_ci	}
66fd4e5da5Sopenharmony_ci	if strings.HasPrefix(s, fileScheme+"://") {
67fd4e5da5Sopenharmony_ci		return URI(s)
68fd4e5da5Sopenharmony_ci	}
69fd4e5da5Sopenharmony_ci	return FileURI(s)
70fd4e5da5Sopenharmony_ci}
71fd4e5da5Sopenharmony_ci
72fd4e5da5Sopenharmony_cifunc CompareURI(a, b URI) int {
73fd4e5da5Sopenharmony_ci	if equalURI(a, b) {
74fd4e5da5Sopenharmony_ci		return 0
75fd4e5da5Sopenharmony_ci	}
76fd4e5da5Sopenharmony_ci	if a < b {
77fd4e5da5Sopenharmony_ci		return -1
78fd4e5da5Sopenharmony_ci	}
79fd4e5da5Sopenharmony_ci	return 1
80fd4e5da5Sopenharmony_ci}
81fd4e5da5Sopenharmony_ci
82fd4e5da5Sopenharmony_cifunc equalURI(a, b URI) bool {
83fd4e5da5Sopenharmony_ci	if a == b {
84fd4e5da5Sopenharmony_ci		return true
85fd4e5da5Sopenharmony_ci	}
86fd4e5da5Sopenharmony_ci	// If we have the same URI basename, we may still have the same file URIs.
87fd4e5da5Sopenharmony_ci	if !strings.EqualFold(path.Base(string(a)), path.Base(string(b))) {
88fd4e5da5Sopenharmony_ci		return false
89fd4e5da5Sopenharmony_ci	}
90fd4e5da5Sopenharmony_ci	fa, err := filename(a)
91fd4e5da5Sopenharmony_ci	if err != nil {
92fd4e5da5Sopenharmony_ci		return false
93fd4e5da5Sopenharmony_ci	}
94fd4e5da5Sopenharmony_ci	fb, err := filename(b)
95fd4e5da5Sopenharmony_ci	if err != nil {
96fd4e5da5Sopenharmony_ci		return false
97fd4e5da5Sopenharmony_ci	}
98fd4e5da5Sopenharmony_ci	// Stat the files to check if they are equal.
99fd4e5da5Sopenharmony_ci	infoa, err := os.Stat(filepath.FromSlash(fa))
100fd4e5da5Sopenharmony_ci	if err != nil {
101fd4e5da5Sopenharmony_ci		return false
102fd4e5da5Sopenharmony_ci	}
103fd4e5da5Sopenharmony_ci	infob, err := os.Stat(filepath.FromSlash(fb))
104fd4e5da5Sopenharmony_ci	if err != nil {
105fd4e5da5Sopenharmony_ci		return false
106fd4e5da5Sopenharmony_ci	}
107fd4e5da5Sopenharmony_ci	return os.SameFile(infoa, infob)
108fd4e5da5Sopenharmony_ci}
109fd4e5da5Sopenharmony_ci
110fd4e5da5Sopenharmony_ci// FileURI returns a span URI for the supplied file path.
111fd4e5da5Sopenharmony_ci// It will always have the file scheme.
112fd4e5da5Sopenharmony_cifunc FileURI(path string) URI {
113fd4e5da5Sopenharmony_ci	if path == "" {
114fd4e5da5Sopenharmony_ci		return ""
115fd4e5da5Sopenharmony_ci	}
116fd4e5da5Sopenharmony_ci	// Handle standard library paths that contain the literal "$GOROOT".
117fd4e5da5Sopenharmony_ci	// TODO(rstambler): The go/packages API should allow one to determine a user's $GOROOT.
118fd4e5da5Sopenharmony_ci	const prefix = "$GOROOT"
119fd4e5da5Sopenharmony_ci	if len(path) >= len(prefix) && strings.EqualFold(prefix, path[:len(prefix)]) {
120fd4e5da5Sopenharmony_ci		suffix := path[len(prefix):]
121fd4e5da5Sopenharmony_ci		path = runtime.GOROOT() + suffix
122fd4e5da5Sopenharmony_ci	}
123fd4e5da5Sopenharmony_ci	if !isWindowsDrivePath(path) {
124fd4e5da5Sopenharmony_ci		if abs, err := filepath.Abs(path); err == nil {
125fd4e5da5Sopenharmony_ci			path = abs
126fd4e5da5Sopenharmony_ci		}
127fd4e5da5Sopenharmony_ci	}
128fd4e5da5Sopenharmony_ci	// Check the file path again, in case it became absolute.
129fd4e5da5Sopenharmony_ci	if isWindowsDrivePath(path) {
130fd4e5da5Sopenharmony_ci		path = "/" + path
131fd4e5da5Sopenharmony_ci	}
132fd4e5da5Sopenharmony_ci	path = filepath.ToSlash(path)
133fd4e5da5Sopenharmony_ci	u := url.URL{
134fd4e5da5Sopenharmony_ci		Scheme: fileScheme,
135fd4e5da5Sopenharmony_ci		Path:   path,
136fd4e5da5Sopenharmony_ci	}
137fd4e5da5Sopenharmony_ci	uri := u.String()
138fd4e5da5Sopenharmony_ci	if unescaped, err := url.PathUnescape(uri); err == nil {
139fd4e5da5Sopenharmony_ci		uri = unescaped
140fd4e5da5Sopenharmony_ci	}
141fd4e5da5Sopenharmony_ci	return URI(uri)
142fd4e5da5Sopenharmony_ci}
143fd4e5da5Sopenharmony_ci
144fd4e5da5Sopenharmony_ci// isWindowsDrivePath returns true if the file path is of the form used by
145fd4e5da5Sopenharmony_ci// Windows. We check if the path begins with a drive letter, followed by a ":".
146fd4e5da5Sopenharmony_cifunc isWindowsDrivePath(path string) bool {
147fd4e5da5Sopenharmony_ci	if len(path) < 4 {
148fd4e5da5Sopenharmony_ci		return false
149fd4e5da5Sopenharmony_ci	}
150fd4e5da5Sopenharmony_ci	return unicode.IsLetter(rune(path[0])) && path[1] == ':'
151fd4e5da5Sopenharmony_ci}
152fd4e5da5Sopenharmony_ci
153fd4e5da5Sopenharmony_ci// isWindowsDriveURI returns true if the file URI is of the format used by
154fd4e5da5Sopenharmony_ci// Windows URIs. The url.Parse package does not specially handle Windows paths
155fd4e5da5Sopenharmony_ci// (see https://golang.org/issue/6027). We check if the URI path has
156fd4e5da5Sopenharmony_ci// a drive prefix (e.g. "/C:"). If so, we trim the leading "/".
157fd4e5da5Sopenharmony_cifunc isWindowsDriveURI(uri string) bool {
158fd4e5da5Sopenharmony_ci	if len(uri) < 4 {
159fd4e5da5Sopenharmony_ci		return false
160fd4e5da5Sopenharmony_ci	}
161fd4e5da5Sopenharmony_ci	return uri[0] == '/' && unicode.IsLetter(rune(uri[1])) && uri[2] == ':'
162fd4e5da5Sopenharmony_ci}
163