diff --git a/tpl/collections/collections.go b/tpl/collections/collections.go index 5d2fc86e9..1447a3fab 100644 --- a/tpl/collections/collections.go +++ b/tpl/collections/collections.go @@ -409,6 +409,11 @@ func (ns *Namespace) Reverse(l any) (any, error) { return sliceCopy.Interface(), nil } +// Sanity check for slices created by Seq and D. +const maxSeqSize = 1000000 + +var errSeqSizeExceedsLimit = errors.New("size of result exceeds limit") + // Seq creates a sequence of integers from args. It's named and used as GNU's seq. // // Examples: @@ -462,14 +467,14 @@ func (ns *Namespace) Seq(args ...any) ([]int, error) { } // sanity check - if last < -100000 { - return nil, errors.New("size of result exceeds limit") + if last < -maxSeqSize { + return nil, errSeqSizeExceedsLimit } size := ((last - first) / inc) + 1 // sanity check - if size <= 0 || size > 2000 { - return nil, errors.New("size of result exceeds limit") + if size <= 0 || size > maxSeqSize { + return nil, errSeqSizeExceedsLimit } seq := make([]int, size) @@ -536,6 +541,12 @@ type dKey struct { // See https://getkerf.wordpress.com/2016/03/30/the-best-algorithm-no-one-knows-about/ func (ns *Namespace) D(seed, n, hi any) []int { key := dKey{seed: cast.ToUint64(seed), n: cast.ToInt(n), hi: cast.ToInt(hi)} + if key.n <= 0 || key.hi <= 0 || key.n > key.hi { + return nil + } + if key.n > maxSeqSize { + panic(errSeqSizeExceedsLimit) + } v, _ := ns.dCache.GetOrCreate(key, func() ([]int, error) { prng := rand.New(rand.NewPCG(key.seed, 0)) result := make([]int, 0, key.n) diff --git a/tpl/collections/collections_integration_test.go b/tpl/collections/collections_integration_test.go index b60aaea87..84af4153d 100644 --- a/tpl/collections/collections_integration_test.go +++ b/tpl/collections/collections_integration_test.go @@ -298,3 +298,33 @@ All|{{ .Title }}| b.AssertFileContent("public/index.html", "Len: 1|") } + +func TestD(t *testing.T) { + t.Parallel() + files := ` +-- hugo.toml -- +-- layouts/home.html -- +{{ $p := site.RegularPages }} +5 random pages: {{ range collections.D 42 5 ($p | len) }}{{ with (index $p .) }}{{ .RelPermalink }}|{{ end }}{{ end }}$ +-- content/a.md -- +-- content/b.md -- +-- content/c.md -- +-- content/d.md -- +-- content/e.md -- +-- content/f.md -- +-- content/g.md -- +-- content/h.md -- +-- content/i.md -- +-- content/j.md -- +-- content/k.md -- +-- content/l.md -- +-- content/m.md -- +-- content/n.md -- +-- content/o.md -- +-- content/p.md -- + +` + b := hugolib.Test(t, files) + + b.AssertFileContentExact("public/index.html", "5 random pages: /b/|/g/|/j/|/k/|/l/|$") +} diff --git a/tpl/collections/collections_test.go b/tpl/collections/collections_test.go index 0e2d99224..0face752c 100644 --- a/tpl/collections/collections_test.go +++ b/tpl/collections/collections_test.go @@ -536,7 +536,7 @@ func TestSeq(t *testing.T) { {[]any{1, -1, 2}, false}, {[]any{2, 1, 1}, false}, {[]any{2, 1, 1, 1}, false}, - {[]any{2001}, false}, + {[]any{-1000001}, false}, {[]any{}, false}, {[]any{0, -1000000}, false}, {[]any{tstNoStringer{}}, false}, @@ -795,6 +795,15 @@ func TestD(t *testing.T) { c.Assert(ns.D(42, 5, 100), qt.DeepEquals, []int{24, 34, 66, 82, 96}) c.Assert(ns.D(31, 5, 100), qt.DeepEquals, []int{12, 37, 38, 69, 98}) + c.Assert(ns.D(42, 9, 10), qt.DeepEquals, []int{0, 1, 2, 3, 4, 6, 7, 8, 9}) + c.Assert(ns.D(42, 10, 10), qt.DeepEquals, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) + c.Assert(ns.D(42, 11, 10), qt.IsNil) // n > hi + c.Assert(ns.D(42, -5, 100), qt.IsNil) + c.Assert(ns.D(42, 0, 100), qt.IsNil) + c.Assert(ns.D(42, 5, 0), qt.IsNil) + c.Assert(ns.D(42, 5, -10), qt.IsNil) + c.Assert(ns.D(42, 5, 3000000), qt.DeepEquals, []int{720363, 1041693, 2009179, 2489106, 2873969}) + c.Assert(func() { ns.D(31, 2000000, 3000000) }, qt.PanicMatches, "size of result exceeds limit") } func BenchmarkD2(b *testing.B) {