Validate and cap ansible pagination params
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful

This commit is contained in:
2026-10-17 12:56:16 +11:00
parent ef5a38475f
commit 285e8674ca
2 changed files with 48 additions and 6 deletions
+18 -2
View File
@@ -9,6 +9,7 @@ import (
"encoding/json"
"net/http"
"net/url"
"strconv"
"strings"
"git.unkin.net/unkin/artifactapi/internal/auth"
@@ -42,12 +43,27 @@ func (p *Provider) UpstreamURL(remote models.Remote, path string) string {
return strings.TrimRight(remote.BaseURL, "/") + "/" + strings.TrimLeft(path, "/")
}
const maxPageLimit = 100
// ForwardedQuery keeps limit/offset pagination on metadata paths only.
func (p *Provider) ForwardedQuery(path string) []string {
func (p *Provider) ForwardedQuery(path string) map[string]provider.QueryValidator {
if isArtifact(path) {
return nil
}
return []string{"limit", "offset"}
return map[string]provider.QueryValidator{"limit": pageLimit, "offset": pageOffset}
}
func pageOffset(v string) (string, bool) {
n, err := strconv.ParseUint(v, 10, 32)
return strconv.FormatUint(n, 10), err == nil
}
func pageLimit(v string) (string, bool) {
n, err := strconv.ParseUint(v, 10, 32)
if err != nil || n == 0 {
return "", false
}
return strconv.FormatUint(min(n, maxPageLimit), 10), true
}
// RewriteResponse points href, download_url and links.* at the remote. Links
+30 -4
View File
@@ -2,7 +2,6 @@ package ansible
import (
"encoding/json"
"reflect"
"testing"
"git.unkin.net/unkin/artifactapi/internal/provider"
@@ -40,12 +39,39 @@ func TestContentTypeIgnoresQuery(t *testing.T) {
func TestForwardedQuery(t *testing.T) {
p := &Provider{}
if got := p.ForwardedQuery("api/v3/collections/a/b/versions/"); !reflect.DeepEqual(got, []string{"limit", "offset"}) {
t.Errorf("metadata ForwardedQuery = %v", got)
}
if got := p.ForwardedQuery(tarball); got != nil {
t.Errorf("tarball ForwardedQuery = %v, want nil", got)
}
q := p.ForwardedQuery("api/v3/collections/a/b/versions/")
if len(q) != 2 || q["limit"] == nil || q["offset"] == nil {
t.Fatalf("metadata ForwardedQuery keys = %v, want limit and offset", q)
}
tests := []struct {
param, in, want string
ok bool
}{
{"limit", "10", "10", true},
{"limit", "007", "7", true},
{"limit", "100", "100", true},
{"limit", "5000", "100", true},
{"limit", "0", "", false},
{"limit", "", "", false},
{"limit", "-1", "", false},
{"limit", "+5", "", false},
{"limit", "1e3", "", false},
{"limit", "10 ", "", false},
{"offset", "0", "0", true},
{"offset", "000", "0", true},
{"offset", "0100", "100", true},
{"offset", "abc", "", false},
{"offset", "99999999999999999999", "", false},
}
for _, tt := range tests {
got, ok := q[tt.param](tt.in)
if ok != tt.ok || (ok && got != tt.want) {
t.Errorf("%s(%q) = %q, %v, want %q, %v", tt.param, tt.in, got, ok, tt.want, tt.ok)
}
}
}
func rewrite(t *testing.T, remote models.Remote, body string) map[string]any {