-
Notifications
You must be signed in to change notification settings - Fork 1
/
urlinfo.go
205 lines (187 loc) · 6.84 KB
/
urlinfo.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package urlinfo provides functions for extracting information out
// of url paths.
package urlinfo
import (
"fmt"
"strings"
"golang.org/x/mod/module"
"golang.org/x/mod/semver"
"github.com/khulnasoft-lab/godep/internal"
"github.com/khulnasoft-lab/godep/internal/derrors"
"github.com/khulnasoft-lab/godep/internal/fetch"
"github.com/khulnasoft-lab/godep/internal/stdlib"
"github.com/khulnasoft-lab/godep/internal/version"
)
// URLPathInfo contains the information about what unit is requested in a URL path.
type URLPathInfo struct {
// FullPath is the full import path corresponding to the requested
// package/module/directory page.
FullPath string
// ModulePath is the path of the module corresponding to the FullPath and
// resolvedVersion. If unknown, it is set to internal.UnknownModulePath.
ModulePath string
// requestedVersion is the version requested by the user, which will be one
// of the following: "latest", "master", a Go version tag, or a semantic
// version.
RequestedVersion string
}
type UserError struct {
UserMessage string
err error
}
func (e *UserError) Error() string {
return e.err.Error()
}
func (e *UserError) Unwrap() error {
return e.err
}
// ExtractURLPathInfo extracts information from a request to pkg.go.dev.
// If an error is returned, the user will be served an http.StatusBadRequest.
func ExtractURLPathInfo(urlPath string) (_ *URLPathInfo, err error) {
defer derrors.Wrap(&err, "ExtractURLPathInfo(%q)", urlPath)
if m, _, _ := strings.Cut(strings.TrimPrefix(urlPath, "/"), "@"); stdlib.Contains(m) {
return parseStdlibURLPath(urlPath)
}
return ParseDetailsURLPath(urlPath)
}
// ParseDetailsURLPath parses a URL path that refers (or may refer) to something
// in the Go ecosystem.
//
// After trimming leading and trailing slashes, the path is expected to have one
// of three forms, and we divide it into three parts: a full path, a module
// path, and a version.
//
// 1. The path has no '@', like github.com/hashicorp/vault/api.
// This is the full path. The module path is unknown. So is the version, so we
// treat it as the latest version for whatever the path denotes.
//
// 2. The path has "@version" at the end, like github.com/hashicorp/vault/api@v1.2.3.
// We split this at the '@' into a full path (github.com/hashicorp/vault/api)
// and version (v1.2.3); the module path is still unknown.
//
// 3. The path has "@version" in the middle, like github.com/hashicorp/vault@v1.2.3/api.
// (We call this the "canonical" form of a path.)
// We remove the version to get the full path, which is again
// github.com/hashicorp/vault/api. The version is v1.2.3, and the module path is
// the part before the '@', github.com/hashicorp/vault.
//
// In one case, we do a little more than parse the urlPath into parts: if the full path
// could be a part of the standard library (because it has no '.'), we assume it
// is and set the ModulePath to indicate the standard library.
func ParseDetailsURLPath(urlPath string) (_ *URLPathInfo, err error) {
defer derrors.Wrap(&err, "ParseDetailsURLPath(%q)", urlPath)
// This splits urlPath into either:
// /<module-path>[/<suffix>]
// or
// /<module-path>, @<version>/<suffix>
// or
// /<module-path>/<suffix>, @<version>
modulePath, rest, found := strings.Cut(urlPath, "@")
info := &URLPathInfo{
FullPath: strings.TrimSuffix(strings.TrimPrefix(modulePath, "/"), "/"),
ModulePath: internal.UnknownModulePath,
RequestedVersion: version.Latest,
}
if found {
// The urlPath contains a "@". Parse the version and suffix from
// parts[1], the string after the '@'.
endParts := strings.Split(rest, "/")
// Parse the requestedVersion from the urlPath.
// The first path component after the '@' is the version.
// You cannot explicitly write "latest" for the version.
if endParts[0] == version.Latest {
return nil, &UserError{
err: fmt.Errorf("invalid version: %q", info.RequestedVersion),
UserMessage: fmt.Sprintf("%q is not a valid version", endParts[0]),
}
}
info.RequestedVersion = endParts[0]
// Parse the suffix following the "@version" from the urlPath.
suffix := strings.Join(endParts[1:], "/")
if suffix != "" {
// If "@version" occurred in the middle of the path, the part before it
// is the module path.
info.ModulePath = info.FullPath
info.FullPath = info.FullPath + "/" + suffix
}
}
if !IsValidPath(info.FullPath) {
return nil, &UserError{
err: fmt.Errorf("IsValidPath(%q) is false", info.FullPath),
UserMessage: fmt.Sprintf("%q is not a valid import path", info.FullPath),
}
}
return info, nil
}
func parseStdlibURLPath(urlPath string) (_ *URLPathInfo, err error) {
defer derrors.Wrap(&err, "parseStdlibURLPath(%q)", urlPath)
// This splits urlPath into either:
// /<path>@<tag> or /<path>
fullPath, tag, found := strings.Cut(urlPath, "@")
fullPath = strings.TrimSuffix(strings.TrimPrefix(fullPath, "/"), "/")
if !IsValidPath(fullPath) {
return nil, &UserError{
err: fmt.Errorf("IsValidPath(%q) is false", fullPath),
UserMessage: fmt.Sprintf("%q is not a valid import path", fullPath),
}
}
info := &URLPathInfo{
FullPath: fullPath,
ModulePath: stdlib.ModulePath,
}
if !found {
info.RequestedVersion = version.Latest
return info, nil
}
tag = strings.TrimSuffix(tag, "/")
info.RequestedVersion = stdlib.VersionForTag(tag)
if info.RequestedVersion == "" {
if tag == fetch.LocalVersion {
// Special case: 0.0.0 is the version for a local stdlib
info.RequestedVersion = fetch.LocalVersion
return info, nil
}
return nil, &UserError{
err: fmt.Errorf("invalid Go tag for url: %q", urlPath),
UserMessage: fmt.Sprintf("%q is not a valid tag for the standard library", tag),
}
}
return info, nil
}
// IsValidPath reports whether a requested path could be a valid unit.
func IsValidPath(fullPath string) bool {
if err := module.CheckImportPath(fullPath); err != nil {
return false
}
parts := strings.Split(fullPath, "/")
if parts[0] == "golang.org" {
if len(parts) < 2 {
return false
}
switch parts[1] {
case "dl":
return true
case "x":
return len(parts) >= 3
default:
return false
}
}
if internal.VCSHostWithThreeElementRepoName(parts[0]) && len(parts) < 3 {
return false
}
return true
}
// IsSupportedVersion reports whether the version is supported by the frontend.
func IsSupportedVersion(fullPath, requestedVersion string) bool {
if stdlib.Contains(fullPath) && stdlib.SupportedBranches[requestedVersion] {
return true
}
if _, ok := internal.DefaultBranches[requestedVersion]; ok {
return !stdlib.Contains(fullPath) || requestedVersion == "master"
}
return requestedVersion == version.Latest || semver.IsValid(requestedVersion)
}