Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/cran/cran.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ func parseDescription(content string) descriptionInfo {
func extractRepository(urlField string) string {
urls := strings.Split(urlField, ",")
for _, u := range urls {
if parsed := urlparser.Parse(strings.TrimSpace(u)); parsed != "" {
if parsed := urlparser.CanonicalURL(strings.TrimSpace(u)); parsed != "" {
return parsed
}
}
Expand Down
28 changes: 28 additions & 0 deletions internal/cran/cran_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,34 @@ func TestFetchPackage(t *testing.T) {
}
}

func TestExtractRepositoryUsesOnlyKnownForges(t *testing.T) {
tests := []struct {
name string
urlField string
want string
}{
{
name: "skip project website",
urlField: "https://example.com/projects/ggplot2, https://github.com/tidyverse/ggplot2",
want: "https://github.com/tidyverse/ggplot2",
},
{
name: "reject project website",
urlField: "https://example.com/projects/ggplot2",
want: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := extractRepository(tt.urlField)
if got != tt.want {
t.Errorf("extractRepository() = %q, want %q", got, tt.want)
}
})
}
}

func TestFetchVersions(t *testing.T) {
mux := http.NewServeMux()

Expand Down
2 changes: 1 addition & 1 deletion internal/dub/dub.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func (r *Registry) FetchPackage(ctx context.Context, name string) (*core.Package
// Extract repository URL
repository := urlparser.Parse(resp.Repository)
if repository == "" {
repository = urlparser.Parse(resp.Homepage)
repository = urlparser.CanonicalURL(resp.Homepage)
}

return &core.Package{
Expand Down
21 changes: 21 additions & 0 deletions internal/dub/dub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,27 @@ func TestFetchPackage(t *testing.T) {
}
}

func TestFetchPackageDoesNotUseNonForgeHomepageAsRepository(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := packageResponse{
Name: "example",
Homepage: "https://example.com/packages/example",
}
_ = json.NewEncoder(w).Encode(resp)
}))
defer server.Close()

reg := New(server.URL, core.DefaultClient())
pkg, err := reg.FetchPackage(context.Background(), "example")
if err != nil {
t.Fatalf("FetchPackage failed: %v", err)
}

if pkg.Repository != "" {
t.Errorf("expected no repository, got %q", pkg.Repository)
}
}

func TestFetchVersions(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := packageResponse{
Expand Down
4 changes: 2 additions & 2 deletions internal/golang/golang.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func (r *Registry) fetchPackagePkgsite(ctx context.Context, name string) (*core.
repoURL = mod.RepoURL
}
if repoURL == "" {
repoURL = urlparser.Parse(deriveRepoURL(name))
repoURL = urlparser.CanonicalURL(deriveRepoURL(name))
}

var types []string
Expand Down Expand Up @@ -164,7 +164,7 @@ func (r *Registry) fetchPackageProxy(ctx context.Context, name string) (*core.Pa
return nil, &core.NotFoundError{Ecosystem: ecosystem, Name: name}
}

repoURL := urlparser.Parse(deriveRepoURL(name))
repoURL := urlparser.CanonicalURL(deriveRepoURL(name))

return &core.Package{
Name: name,
Expand Down
21 changes: 21 additions & 0 deletions internal/golang/golang_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,27 @@ func TestFetchPackage(t *testing.T) {
}
}

func TestFetchPackageDoesNotDeriveRepositoryFromVanityPath(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/golang.org/x/sync/@v/list" {
_, _ = w.Write([]byte("v0.7.0\n"))
return
}
w.WriteHeader(404)
}))
defer server.Close()

reg := New(server.URL, core.DefaultClient())
pkg, err := reg.FetchPackage(context.Background(), "golang.org/x/sync")
if err != nil {
t.Fatalf("FetchPackage failed: %v", err)
}

if pkg.Repository != "" {
t.Errorf("expected no repository, got %q", pkg.Repository)
}
}

func TestFetchPackagePkgsite(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/module/golang.org/x/sync" {
Expand Down
2 changes: 1 addition & 1 deletion internal/haxelib/haxelib.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func (r *Registry) FetchPackage(ctx context.Context, name string) (*core.Package
}

// Extract repository URL from website
repository := urlparser.Parse(resp.Website)
repository := urlparser.CanonicalURL(resp.Website)

return &core.Package{
Name: resp.Name,
Expand Down
21 changes: 21 additions & 0 deletions internal/haxelib/haxelib_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,27 @@ func TestFetchPackage(t *testing.T) {
}
}

func TestFetchPackageDoesNotUseNonForgeWebsiteAsRepository(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := packageResponse{
Name: "example",
Website: "https://example.com/packages/example",
}
_ = json.NewEncoder(w).Encode(resp)
}))
defer server.Close()

reg := New(server.URL, core.DefaultClient())
pkg, err := reg.FetchPackage(context.Background(), "example")
if err != nil {
t.Fatalf("FetchPackage failed: %v", err)
}

if pkg.Repository != "" {
t.Errorf("expected no repository, got %q", pkg.Repository)
}
}

func TestFetchVersions(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := packageResponse{
Expand Down
2 changes: 1 addition & 1 deletion internal/hex/hex.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func (r *Registry) FetchPackage(ctx context.Context, name string) (*core.Package
}
}
if repository == "" {
repository = urlparser.Parse(homepage)
repository = urlparser.CanonicalURL(homepage)
}

return &core.Package{
Expand Down
25 changes: 25 additions & 0 deletions internal/hex/hex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,31 @@ func TestFetchPackage(t *testing.T) {
}
}

func TestFetchPackageDoesNotUseNonForgeHomepageAsRepository(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := packageResponse{
Name: "example",
Meta: metaInfo{
Links: map[string]string{
"Website": "https://example.com/packages/example",
},
},
}
_ = json.NewEncoder(w).Encode(resp)
}))
defer server.Close()

reg := New(server.URL, core.DefaultClient())
pkg, err := reg.FetchPackage(context.Background(), "example")
if err != nil {
t.Fatalf("FetchPackage failed: %v", err)
}

if pkg.Repository != "" {
t.Errorf("expected no repository, got %q", pkg.Repository)
}
}

func TestFetchVersions(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
Expand Down
2 changes: 1 addition & 1 deletion internal/homebrew/homebrew.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func (r *Registry) FetchPackage(ctx context.Context, name string) (*core.Package
}

// Extract repository URL from homepage
repository := urlparser.Parse(resp.Homepage)
repository := urlparser.CanonicalURL(resp.Homepage)

var status string
if resp.Deprecated {
Expand Down
3 changes: 3 additions & 0 deletions internal/homebrew/homebrew_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ func TestFetchPackage(t *testing.T) {
if pkg.Licenses != "GPL-3.0-or-later" {
t.Errorf("unexpected license: %q", pkg.Licenses)
}
if pkg.Repository != "" {
t.Errorf("expected no repository, got %q", pkg.Repository)
}
}

func TestFetchPackageWithGitHubRepo(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion internal/nuget/nuget.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func (r *Registry) FetchPackage(ctx context.Context, name string) (*core.Package
}

func extractRepository(projectURL string) string {
return urlparser.Parse(projectURL)
return urlparser.CanonicalURL(projectURL)
}

func (r *Registry) FetchVersions(ctx context.Context, name string) ([]core.Version, error) {
Expand Down
5 changes: 4 additions & 1 deletion internal/nuget/nuget_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func TestFetchPackage(t *testing.T) {
ID: "Newtonsoft.Json",
Version: "13.0.3",
Description: "Json.NET is a popular high-performance JSON framework for .NET",
ProjectURL: "https://www.newtonsoft.com/json",
ProjectURL: "https://www.newtonsoft.com/products/json",
LicenseExpression: "MIT",
Listed: true,
Tags: []string{"json"},
Expand Down Expand Up @@ -58,6 +58,9 @@ func TestFetchPackage(t *testing.T) {
if len(pkg.Keywords) != 1 || pkg.Keywords[0] != "json" {
t.Errorf("unexpected keywords: %v", pkg.Keywords)
}
if pkg.Repository != "" {
t.Errorf("expected no repository, got %q", pkg.Repository)
}
}

func TestFetchPackageWithGitHubRepository(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion internal/pub/pub.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func (r *Registry) FetchPackage(ctx context.Context, name string) (*core.Package
latest := resp.Latest.Pubspec
repository := urlparser.Parse(latest.Repository)
if repository == "" {
repository = urlparser.Parse(latest.Homepage)
repository = urlparser.CanonicalURL(latest.Homepage)
}

return &core.Package{
Expand Down
25 changes: 25 additions & 0 deletions internal/pub/pub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,31 @@ func TestFetchPackage(t *testing.T) {
}
}

func TestFetchPackageDoesNotUseNonForgeHomepageAsRepository(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := packageResponse{
Name: "example",
Latest: versionInfo{
Pubspec: pubspec{
Homepage: "https://example.com/packages/example",
},
},
}
_ = json.NewEncoder(w).Encode(resp)
}))
defer server.Close()

reg := New(server.URL, core.DefaultClient())
pkg, err := reg.FetchPackage(context.Background(), "example")
if err != nil {
t.Fatalf("FetchPackage failed: %v", err)
}

if pkg.Repository != "" {
t.Errorf("expected no repository, got %q", pkg.Repository)
}
}

func TestFetchVersions(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := packageResponse{
Expand Down
14 changes: 8 additions & 6 deletions internal/pypi/pypi.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,12 @@ func (r *Registry) FetchPackage(ctx context.Context, name string) (*core.Package

func extractRepoURL(projectURLs map[string]string, homePage string) string {
priorityKeys := []string{"Repository", "Source", "Source Code", "Code"}
for _, key := range priorityKeys {
if url, ok := projectURLs[key]; ok && url != "" {
if parsed := urlparser.Parse(url); parsed != "" {
return parsed
for _, priorityKey := range priorityKeys {
for key, projectURL := range projectURLs {
if strings.EqualFold(key, priorityKey) && projectURL != "" {
if parsed := urlparser.Parse(projectURL); parsed != "" {
return parsed
}
}
}
}
Expand All @@ -130,12 +132,12 @@ func extractRepoURL(projectURLs map[string]string, homePage string) string {
if strings.Contains(url, "/sponsors") {
continue
}
if parsed := urlparser.Parse(url); parsed != "" {
if parsed := urlparser.CanonicalURL(url); parsed != "" {
return parsed
}
}

if parsed := urlparser.Parse(homePage); parsed != "" {
if parsed := urlparser.CanonicalURL(homePage); parsed != "" {
return parsed
}

Expand Down
42 changes: 42 additions & 0 deletions internal/pypi/pypi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,48 @@ func TestFetchPackage(t *testing.T) {
}
}

func TestExtractRepoURLUsesOnlyKnownForgeFallbacks(t *testing.T) {
tests := []struct {
name string
projectURLs map[string]string
homepage string
want string
}{
{
name: "skip documentation URL",
projectURLs: map[string]string{
"Documentation": "https://docs.example.com/projects/example",
},
homepage: "https://github.com/example/example",
want: "https://github.com/example/example",
},
{
name: "reject non-forge URLs",
projectURLs: map[string]string{
"Documentation": "https://docs.example.com/projects/example",
},
homepage: "https://example.com/projects/example",
want: "",
},
{
name: "accept lowercase explicit self-hosted repository",
projectURLs: map[string]string{
"repository": "https://git.example.com/example/example",
},
want: "https://git.example.com/example/example",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := extractRepoURL(tt.projectURLs, tt.homepage)
if got != tt.want {
t.Errorf("extractRepoURL() = %q, want %q", got, tt.want)
}
})
}
}

func TestFetchPackageEscapesName(t *testing.T) {
var gotPath, gotQuery string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand Down
2 changes: 1 addition & 1 deletion internal/urlparser/urlparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ var knownHosts = map[string]string{
"bitbucket.org": "https://bitbucket.org",
"bitbucket.com": "https://bitbucket.org",
"codeberg.org": "https://codeberg.org",
"gitea.com": "https://gitea.com",
"sr.ht": "https://sr.ht",
"sourceforge.net": "https://sourceforge.net",
}

// Subdomains to strip only for known hosts
Expand Down
Loading