Skip to content

Commit 2cdaa8e

Browse files
authored
Merge pull request #16 from git-pkgs/escape-package-names
Escape package names in pypi/cargo/cocoapods/deno API URLs
2 parents 31c0de6 + 2729ff3 commit 2cdaa8e

5 files changed

Lines changed: 41 additions & 13 deletions

File tree

internal/cargo/cargo.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package cargo
44
import (
55
"context"
66
"fmt"
7+
"net/url"
78
"strings"
89
"time"
910

@@ -102,7 +103,7 @@ type ownerInfo struct {
102103
}
103104

104105
func (r *Registry) FetchPackage(ctx context.Context, name string) (*core.Package, error) {
105-
url := fmt.Sprintf("%s/api/v1/crates/%s", r.baseURL, name)
106+
url := fmt.Sprintf("%s/api/v1/crates/%s", r.baseURL, url.PathEscape(name))
106107

107108
var resp crateResponse
108109
if err := r.client.GetJSON(ctx, url, &resp); err != nil {
@@ -132,7 +133,7 @@ func (r *Registry) FetchPackage(ctx context.Context, name string) (*core.Package
132133
}
133134

134135
func (r *Registry) FetchVersions(ctx context.Context, name string) ([]core.Version, error) {
135-
url := fmt.Sprintf("%s/api/v1/crates/%s", r.baseURL, name)
136+
url := fmt.Sprintf("%s/api/v1/crates/%s", r.baseURL, url.PathEscape(name))
136137

137138
var resp crateResponse
138139
if err := r.client.GetJSON(ctx, url, &resp); err != nil {
@@ -181,7 +182,7 @@ func (r *Registry) FetchVersions(ctx context.Context, name string) ([]core.Versi
181182
}
182183

183184
func (r *Registry) FetchDependencies(ctx context.Context, name, version string) ([]core.Dependency, error) {
184-
url := fmt.Sprintf("%s/api/v1/crates/%s/%s/dependencies", r.baseURL, name, version)
185+
url := fmt.Sprintf("%s/api/v1/crates/%s/%s/dependencies", r.baseURL, url.PathEscape(name), url.PathEscape(version))
185186

186187
var resp dependenciesResponse
187188
if err := r.client.GetJSON(ctx, url, &resp); err != nil {
@@ -216,7 +217,7 @@ func mapScope(kind string) core.Scope {
216217
}
217218

218219
func (r *Registry) FetchMaintainers(ctx context.Context, name string) ([]core.Maintainer, error) {
219-
url := fmt.Sprintf("%s/api/v1/crates/%s/owner_user", r.baseURL, name)
220+
url := fmt.Sprintf("%s/api/v1/crates/%s/owner_user", r.baseURL, url.PathEscape(name))
220221

221222
var resp ownersResponse
222223
if err := r.client.GetJSON(ctx, url, &resp); err != nil {

internal/cocoapods/cocoapods.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package cocoapods
44
import (
55
"context"
66
"fmt"
7+
"net/url"
78
"strings"
89
"time"
910

@@ -79,7 +80,7 @@ type ownerInfo struct {
7980
}
8081

8182
func (r *Registry) FetchPackage(ctx context.Context, name string) (*core.Package, error) {
82-
url := fmt.Sprintf("%s/api/v1/pods/%s", r.baseURL, name)
83+
url := fmt.Sprintf("%s/api/v1/pods/%s", r.baseURL, url.PathEscape(name))
8384

8485
var resp podResponse
8586
if err := r.client.GetJSON(ctx, url, &resp); err != nil {
@@ -120,7 +121,7 @@ func (r *Registry) FetchPackage(ctx context.Context, name string) (*core.Package
120121
}
121122

122123
func (r *Registry) FetchVersions(ctx context.Context, name string) ([]core.Version, error) {
123-
url := fmt.Sprintf("%s/api/v1/pods/%s", r.baseURL, name)
124+
url := fmt.Sprintf("%s/api/v1/pods/%s", r.baseURL, url.PathEscape(name))
124125

125126
var resp podResponse
126127
if err := r.client.GetJSON(ctx, url, &resp); err != nil {
@@ -143,7 +144,7 @@ func (r *Registry) FetchVersions(ctx context.Context, name string) ([]core.Versi
143144
}
144145

145146
func (r *Registry) FetchDependencies(ctx context.Context, name, version string) ([]core.Dependency, error) {
146-
url := fmt.Sprintf("%s/api/v1/pods/%s", r.baseURL, name)
147+
url := fmt.Sprintf("%s/api/v1/pods/%s", r.baseURL, url.PathEscape(name))
147148

148149
var resp podResponse
149150
if err := r.client.GetJSON(ctx, url, &resp); err != nil {
@@ -195,7 +196,7 @@ func formatRequirement(req interface{}) string {
195196
}
196197

197198
func (r *Registry) FetchMaintainers(ctx context.Context, name string) ([]core.Maintainer, error) {
198-
url := fmt.Sprintf("%s/api/v1/pods/%s", r.baseURL, name)
199+
url := fmt.Sprintf("%s/api/v1/pods/%s", r.baseURL, url.PathEscape(name))
199200

200201
var resp podResponse
201202
if err := r.client.GetJSON(ctx, url, &resp); err != nil {

internal/deno/deno.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package deno
44
import (
55
"context"
66
"fmt"
7+
"net/url"
78
"strings"
89
"time"
910

@@ -87,7 +88,7 @@ type directoryEntry struct {
8788
}
8889

8990
func (r *Registry) FetchPackage(ctx context.Context, name string) (*core.Package, error) {
90-
url := fmt.Sprintf("%s/v2/modules/%s", r.baseURL, name)
91+
url := fmt.Sprintf("%s/v2/modules/%s", r.baseURL, url.PathEscape(name))
9192

9293
var resp moduleInfoResponse
9394
if err := r.client.GetJSON(ctx, url, &resp); err != nil {
@@ -115,7 +116,7 @@ func (r *Registry) FetchPackage(ctx context.Context, name string) (*core.Package
115116
}
116117

117118
func (r *Registry) FetchVersions(ctx context.Context, name string) ([]core.Version, error) {
118-
url := fmt.Sprintf("%s/v2/modules/%s", r.baseURL, name)
119+
url := fmt.Sprintf("%s/v2/modules/%s", r.baseURL, url.PathEscape(name))
119120

120121
var resp moduleInfoResponse
121122
if err := r.client.GetJSON(ctx, url, &resp); err != nil {

internal/pypi/pypi.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package pypi
44
import (
55
"context"
66
"fmt"
7+
"net/url"
78
"regexp"
89
"strings"
910
"time"
@@ -86,7 +87,7 @@ type versionInfoResponse struct {
8687
}
8788

8889
func (r *Registry) FetchPackage(ctx context.Context, name string) (*core.Package, error) {
89-
url := fmt.Sprintf("%s/pypi/%s/json", r.baseURL, name)
90+
url := fmt.Sprintf("%s/pypi/%s/json", r.baseURL, url.PathEscape(name))
9091

9192
var resp packageResponse
9293
if err := r.client.GetJSON(ctx, url, &resp); err != nil {
@@ -190,7 +191,7 @@ func normalizeName(name string) string {
190191
}
191192

192193
func (r *Registry) FetchVersions(ctx context.Context, name string) ([]core.Version, error) {
193-
url := fmt.Sprintf("%s/pypi/%s/json", r.baseURL, name)
194+
url := fmt.Sprintf("%s/pypi/%s/json", r.baseURL, url.PathEscape(name))
194195

195196
var resp packageResponse
196197
if err := r.client.GetJSON(ctx, url, &resp); err != nil {
@@ -246,7 +247,7 @@ func (r *Registry) FetchVersions(ctx context.Context, name string) ([]core.Versi
246247
var pep508NameRegex = regexp.MustCompile(`^([A-Za-z0-9][-A-Za-z0-9._]*[A-Za-z0-9]|[A-Za-z0-9])(\s*\[.*?\])?`)
247248

248249
func (r *Registry) FetchDependencies(ctx context.Context, name, version string) ([]core.Dependency, error) {
249-
url := fmt.Sprintf("%s/pypi/%s/%s/json", r.baseURL, name, version)
250+
url := fmt.Sprintf("%s/pypi/%s/%s/json", r.baseURL, url.PathEscape(name), url.PathEscape(version))
250251

251252
var resp versionInfoResponse
252253
if err := r.client.GetJSON(ctx, url, &resp); err != nil {

internal/pypi/pypi_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,30 @@ func TestFetchPackage(t *testing.T) {
6565
}
6666
}
6767

68+
func TestFetchPackageEscapesName(t *testing.T) {
69+
var gotPath, gotQuery string
70+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
71+
gotPath = r.URL.Path
72+
gotQuery = r.URL.RawQuery
73+
w.WriteHeader(404)
74+
}))
75+
defer server.Close()
76+
77+
reg := New(server.URL, core.DefaultClient())
78+
_, _ = reg.FetchPackage(context.Background(), "requests?evil=1#frag")
79+
80+
// Without escaping the ? starts a query string client-side and the
81+
// server sees path /pypi/requests with query evil=1/json. With
82+
// escaping the whole name is one path segment, the # doesn't
83+
// truncate as a fragment, and /json survives at the end.
84+
if gotQuery != "" {
85+
t.Errorf("query string leaked: %q (path was %q)", gotQuery, gotPath)
86+
}
87+
if gotPath != "/pypi/requests?evil=1#frag/json" {
88+
t.Errorf("path = %q, expected name kept intact with /json suffix", gotPath)
89+
}
90+
}
91+
6892
func TestFetchPackageWithLicenseExpression(t *testing.T) {
6993
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
7094
resp := packageResponse{

0 commit comments

Comments
 (0)