Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
44 changes: 39 additions & 5 deletions internal/providers/sourceproviders/fedorasource/fedorasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"log/slog"
"net/url"
"path/filepath"
"regexp"
"strings"
Expand Down Expand Up @@ -314,8 +315,12 @@ const (
// [PlaceholderFilename], [PlaceholderHashType], and [PlaceholderHash].
// Placeholders not present in the template are simply ignored.
//
// Substituted values are URL path-escaped via [url.PathEscape] so that reserved
// characters such as /, ?, #, and % do not alter the URL structure.
//
// Returns an error if any of the provided values contain a placeholder string, as this
// would cause ambiguous substitution results depending on replacement order.
// would cause ambiguous substitution results depending on replacement order, or if the
// resulting URL is not valid.
func BuildLookasideURL(template, packageName, fileName, hashType, hash string) (string, error) {
// allPlaceholders lists all supported lookaside URI template placeholders.
allPlaceholders := []string{PlaceholderPkg, PlaceholderFilename, PlaceholderHashType, PlaceholderHash}
Expand All @@ -329,10 +334,39 @@ func BuildLookasideURL(template, packageName, fileName, hashType, hash string) (
}

uri := template
uri = strings.ReplaceAll(uri, PlaceholderPkg, packageName)
uri = strings.ReplaceAll(uri, PlaceholderFilename, fileName)
uri = strings.ReplaceAll(uri, PlaceholderHashType, strings.ToLower(hashType))
uri = strings.ReplaceAll(uri, PlaceholderHash, hash)
uri = strings.ReplaceAll(uri, PlaceholderPkg, url.PathEscape(packageName))
uri = strings.ReplaceAll(uri, PlaceholderFilename, url.PathEscape(fileName))
uri = strings.ReplaceAll(uri, PlaceholderHashType, url.PathEscape(strings.ToLower(hashType)))
Comment thread
dmcilvaney marked this conversation as resolved.
Outdated
uri = strings.ReplaceAll(uri, PlaceholderHash, url.PathEscape(hash))

_, err := url.Parse(uri)
if err != nil {
return "", fmt.Errorf("resulting lookaside URL is not valid:\n%w", err)
}
Comment thread
dmcilvaney marked this conversation as resolved.
Outdated
Comment thread
dmcilvaney marked this conversation as resolved.
Outdated

return uri, nil
}

// BuildDistGitURL constructs a dist-git repository URL by substituting the
// [PlaceholderPkg] placeholder in the URI template with the provided package name.
//
// The package name is URL path-escaped via [url.PathEscape] so that reserved
// characters such as /, ?, #, and % do not alter the URL structure.
//
// Returns an error if the package name contains a placeholder string, or if the
// resulting URL is not valid.
func BuildDistGitURL(template, packageName string) (string, error) {
if strings.Contains(packageName, PlaceholderPkg) {
return "", fmt.Errorf("package name %#q contains placeholder %s, which would cause ambiguous substitution",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return "", fmt.Errorf("package name %#q contains placeholder %s, which would cause ambiguous substitution",
return "", fmt.Errorf("package name %#q contains placeholder %#q, which would cause ambiguous substitution",

packageName, PlaceholderPkg)
}

uri := strings.ReplaceAll(template, PlaceholderPkg, url.PathEscape(packageName))
Comment thread
dmcilvaney marked this conversation as resolved.

_, err := url.Parse(uri)
if err != nil {
return "", fmt.Errorf("resulting dist-git URL is not valid:\n%w", err)
}
Comment thread
dmcilvaney marked this conversation as resolved.
Outdated

return uri, nil
}
113 changes: 113 additions & 0 deletions internal/providers/sourceproviders/fedorasource/fedorasource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,60 @@ func TestBuildLookasideURL(t *testing.T) {
hash: "abc123",
expectedError: "ambiguous substitution",
},
{
name: "filename with slash is path-escaped",
template: "https://example.com/$pkg/$filename/$hashtype/$hash",
pkg: "my-pkg",
filename: "foo/bar",
hashType: "SHA512",
hash: "abc123",
expected: "https://example.com/my-pkg/foo%2Fbar/sha512/abc123",
},
{
name: "filename with question mark is path-escaped",
template: "https://example.com/$pkg/$filename/$hashtype/$hash",
pkg: "my-pkg",
filename: "file?x=1",
hashType: "SHA512",
hash: "abc123",
expected: "https://example.com/my-pkg/file%3Fx=1/sha512/abc123",
},
{
name: "filename with hash is path-escaped",
template: "https://example.com/$pkg/$filename/$hashtype/$hash",
pkg: "my-pkg",
filename: "file#frag",
hashType: "SHA512",
hash: "abc123",
expected: "https://example.com/my-pkg/file%23frag/sha512/abc123",
},
{
name: "filename with malformed percent is path-escaped",
template: "https://example.com/$pkg/$filename/$hashtype/$hash",
pkg: "my-pkg",
filename: "file%zz",
hashType: "SHA512",
hash: "abc123",
expected: "https://example.com/my-pkg/file%25zz/sha512/abc123",
},
{
name: "packageName with slash is path-escaped",
template: "https://example.com/$pkg/$filename/$hashtype/$hash",
pkg: "foo/bar",
filename: "source.tar.gz",
hashType: "SHA512",
hash: "abc123",
expected: "https://example.com/foo%2Fbar/source.tar.gz/sha512/abc123",
},
{
name: "packageName with hash is path-escaped",
template: "https://example.com/$pkg/$filename/$hashtype/$hash",
pkg: "foo#bar",
filename: "source.tar.gz",
hashType: "SHA512",
hash: "abc123",
expected: "https://example.com/foo%23bar/source.tar.gz/sha512/abc123",
},
}

for _, testCase := range tests {
Expand All @@ -342,3 +396,62 @@ func TestBuildLookasideURL(t *testing.T) {
})
}
}

func TestBuildDistGitURL(t *testing.T) {
tests := []struct {
name string
template string
pkg string
expected string
expectedError string
}{
{
name: "standard template",
template: "https://src.example.com/rpms/$pkg.git",
pkg: "curl",
expected: "https://src.example.com/rpms/curl.git",
},
{
name: "packageName containing $pkg placeholder",
template: "https://src.example.com/rpms/$pkg.git",
pkg: "evil-$pkg-name",
expectedError: "ambiguous substitution",
},
{
name: "packageName with slash is path-escaped",
template: "https://src.example.com/rpms/$pkg.git",
pkg: "foo/bar",
expected: "https://src.example.com/rpms/foo%2Fbar.git",
},
{
name: "packageName with hash is path-escaped",
template: "https://src.example.com/rpms/$pkg.git",
pkg: "foo#bar",
expected: "https://src.example.com/rpms/foo%23bar.git",
},
{
name: "packageName with question mark is path-escaped",
template: "https://src.example.com/rpms/$pkg.git",
pkg: "foo?bar",
expected: "https://src.example.com/rpms/foo%3Fbar.git",
},
{
name: "packageName with malformed percent is path-escaped",
template: "https://src.example.com/rpms/$pkg.git",
pkg: "foo%zz",
expected: "https://src.example.com/rpms/foo%25zz.git",
},
}

for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
result, err := BuildDistGitURL(testCase.template, testCase.pkg)
if testCase.expectedError != "" {
assert.ErrorContains(t, err, testCase.expectedError)
} else {
require.NoError(t, err)
assert.Equal(t, testCase.expected, result)
}
})
}
}
11 changes: 8 additions & 3 deletions internal/providers/sourceproviders/fedorasourceprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"fmt"
"log/slog"
"path/filepath"
"strings"
"time"

"github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/components"
Expand Down Expand Up @@ -106,7 +105,10 @@ func (g *FedoraSourcesProviderImpl) GetComponent(
return errors.New("destination path cannot be empty")
}

gitRepoURL := strings.ReplaceAll(g.distroGitBaseURI, "$pkg", upstreamNameToUse)
gitRepoURL, err := fedorasource.BuildDistGitURL(g.distroGitBaseURI, upstreamNameToUse)
if err != nil {
return fmt.Errorf("failed to build dist-git URL for %#q:\n%w", upstreamNameToUse, err)
}
Comment thread
dmcilvaney marked this conversation as resolved.

slog.Info("Getting component from git repo",
"component", componentName,
Expand Down Expand Up @@ -259,7 +261,10 @@ func (g *FedoraSourcesProviderImpl) ResolveIdentity(
upstreamName = component.GetName()
}

gitRepoURL := strings.ReplaceAll(g.distroGitBaseURI, "$pkg", upstreamName)
gitRepoURL, err := fedorasource.BuildDistGitURL(g.distroGitBaseURI, upstreamName)
if err != nil {
return "", fmt.Errorf("failed to build dist-git URL for %#q:\n%w", upstreamName, err)
}
Comment thread
dmcilvaney marked this conversation as resolved.

return g.resolveCommit(ctx, gitRepoURL, upstreamName, component.GetConfig().Spec.UpstreamCommit)
}
Expand Down
Loading