From a8aef97b99d44090e46c0e15032c88826040c955 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 03:25:39 +0000 Subject: [PATCH] build(deps): bump github.com/gowebpki/jcs from 1.0.1 to 1.0.2 Bumps [github.com/gowebpki/jcs](https://github.com/gowebpki/jcs) from 1.0.1 to 1.0.2. - [Release notes](https://github.com/gowebpki/jcs/releases) - [Commits](https://github.com/gowebpki/jcs/compare/v1.0.1...v1.0.2) --- updated-dependencies: - dependency-name: github.com/gowebpki/jcs dependency-version: 1.0.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 4 +- go.sum | 18 +- vendor/github.com/gowebpki/jcs/README.md | 5 +- vendor/github.com/gowebpki/jcs/es6numfmt.go | 2 +- vendor/github.com/gowebpki/jcs/jcs.go | 452 +++++++++++++++----- vendor/go.yaml.in/yaml/v3/parserc.go | 178 ++++---- vendor/go.yaml.in/yaml/v3/yamlh.go | 44 +- vendor/modules.txt | 6 +- 8 files changed, 454 insertions(+), 255 deletions(-) diff --git a/go.mod b/go.mod index 93f2b4d..37dc9ce 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.26.5 require ( github.com/google/cel-go v0.29.2 - github.com/gowebpki/jcs v1.0.1 + github.com/gowebpki/jcs v1.0.2 github.com/ncruces/go-sqlite3 v0.35.2 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 @@ -16,7 +16,7 @@ require ( github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/ncruces/go-sqlite3-wasm/v3 v3.2.35303 // indirect github.com/ncruces/julianday v1.0.0 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect golang.org/x/sys v0.46.0 // indirect golang.org/x/text v0.39.0 // indirect diff --git a/go.sum b/go.sum index c755c48..e8c949e 100644 --- a/go.sum +++ b/go.sum @@ -2,31 +2,26 @@ cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/google/cel-go v0.29.2 h1:ZtDxkeiMmz0mxbKDYiNkE5Lk7V5edMRcaaDf2jX002k= github.com/google/cel-go v0.29.2/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/gowebpki/jcs v1.0.1 h1:Qjzg8EOkrOTuWP7DqQ1FbYtcpEbeTzUoTN9bptp8FOU= -github.com/gowebpki/jcs v1.0.1/go.mod h1:CID1cNZ+sHp1CCpAR8mPf6QRtagFBgPJE0FCUQ6+BrI= +github.com/gowebpki/jcs v1.0.2 h1:IY0Iv76ThSvocWinl2rphYmGLhMufS3ZD1giKqkI6ps= +github.com/gowebpki/jcs v1.0.2/go.mod h1:caHbxgiKxrUu6KNItCUKoggR5/ZUSNxVCm9ZxWJXR0U= github.com/ncruces/go-sqlite3 v0.35.2 h1:YOoumI7tkxMIm1MBrkucRb1qtvAPEh8RrZtv6U+2aLs= github.com/ncruces/go-sqlite3 v0.35.2/go.mod h1:lVlozMCF6VGdI1FOgl0pBfMviw6DIh/QIMKQyjmg2ac= github.com/ncruces/go-sqlite3-wasm/v3 v3.2.35303 h1:td8kMW1bWwzc7NnlzPjQV4GbDNLkje8htGBfbZRaSh8= github.com/ncruces/go-sqlite3-wasm/v3 v3.2.35303/go.mod h1:o8gr9w/50fXA5TDskg6bNUjvqmFfw4KaXth4q+yDSjg= github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M= github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= @@ -41,6 +36,5 @@ google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aO google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/github.com/gowebpki/jcs/README.md b/vendor/github.com/gowebpki/jcs/README.md index c2c9767..79ba605 100644 --- a/vendor/github.com/gowebpki/jcs/README.md +++ b/vendor/github.com/gowebpki/jcs/README.md @@ -2,10 +2,7 @@ # JSON Canonicalization -[![Go Report Card](https://goreportcard.com/badge/github.com/gowebpki/jcs)](https://goreportcard.com/report/github.com/gowebpki/jcs) -[![godoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://pkg.go.dev/github.com/gowebpki/jcs) -[![GitHub license](https://img.shields.io/github/license/gowebpki/jcs.svg?style=flat)](https://github.com/gowebpki/jcs/blob/master/LICENSE) -[![GitHub go.mod Go version of a Go module](https://img.shields.io/github/go-mod/go-version/gowebpki/jcs.svg?style=flat)](https://github.com/gowebpki/jcs) +[![Go Reference](https://pkg.go.dev/badge/github.com/gowebpki/jcs.svg)](https://pkg.go.dev/github.com/gowebpki/jcs) [![CI](https://github.com/gowebpki/jcs/actions/workflows/ci.yml/badge.svg)](https://github.com/gowebpki/jcs/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/gowebpki/jcs/graph/badge.svg)](https://codecov.io/gh/gowebpki/jcs) [![Release](https://img.shields.io/github/v/release/gowebpki/jcs)](https://github.com/gowebpki/jcs/releases) [![Go Version](https://img.shields.io/github/go-mod/go-version/gowebpki/jcs)](https://github.com/gowebpki/jcs) [![License](https://img.shields.io/github/license/gowebpki/jcs)](https://github.com/gowebpki/jcs/blob/master/LICENSE) Cryptographic operations like hashing and signing depend on that the target data does not change during serialization, transport, or parsing. diff --git a/vendor/github.com/gowebpki/jcs/es6numfmt.go b/vendor/github.com/gowebpki/jcs/es6numfmt.go index 011e1df..3cb084a 100644 --- a/vendor/github.com/gowebpki/jcs/es6numfmt.go +++ b/vendor/github.com/gowebpki/jcs/es6numfmt.go @@ -1,4 +1,4 @@ -// Copyright 2021 Bret Jordan & Benedikt Thoma, All rights reserved. +// Copyright 2021-2026 Bret Jordan & Benedikt Thoma, All rights reserved. // Copyright 2006-2019 WebPKI.org (http://webpki.org). // // Use of this source code is governed by an Apache 2.0 license that can be diff --git a/vendor/github.com/gowebpki/jcs/jcs.go b/vendor/github.com/gowebpki/jcs/jcs.go index f67524c..cfdbbf2 100644 --- a/vendor/github.com/gowebpki/jcs/jcs.go +++ b/vendor/github.com/gowebpki/jcs/jcs.go @@ -1,4 +1,4 @@ -// Copyright 2021 Bret Jordan & Benedikt Thoma, All rights reserved. +// Copyright 2021-2026 Bret Jordan & Benedikt Thoma, All rights reserved. // Copyright 2006-2019 WebPKI.org (http://webpki.org). // // Use of this source code is governed by an Apache 2.0 license that can be @@ -8,18 +8,106 @@ package jcs import ( - "container/list" + "bytes" "errors" "fmt" + "regexp" + "sort" "strconv" "strings" "unicode/utf16" + "unicode/utf8" ) +// nodeKind identifies which of the node fields carries the parsed value. +type nodeKind uint8 + +const ( + // nodeScalar is a literal, a number, or a string, held as the finished + // canonical text of that value. + nodeScalar nodeKind = iota + // nodeArray is an array, held as its elements in document order. + nodeArray + // nodeObject is an object, held as its members already sorted. + nodeObject +) + +/* +node - One parsed JSON value. + +Parsing and serialization are deliberately separate passes. Parsing builds a +tree of these nodes, and a single serialization pass then writes the whole +tree into one output buffer. + +An earlier design had each parse function return the finished text of its own +subtree, which every enclosing level then copied into a buffer of its own. +That made the total work the sum of every subtree size over every nesting +level, which is O(n * depth) rather than O(n), and it gave an attacker an +amplification factor bounded only by the nesting limit: a 920 KB document +nested to the limit allocated over nine gigabytes and burned several seconds +of CPU. Writing each byte of the output exactly once removes that term +entirely. +*/ +type node struct { + kind nodeKind + text string // nodeScalar + elements []node // nodeArray + members []nameValueType // nodeObject +} + +/* +appendElement and appendMember - Grow a large collection of parsed children by +doubling rather than by the runtime's default policy. + +The runtime doubles a slice's capacity while it is small, and then, past a few +hundred elements, switches to growing it by roughly a quarter at a time. +Filling a large slice by repeated append therefore allocates about five times +the size of the finished slice. The number of children in an array or an +object is chosen by whoever supplies the document, which makes that overhead +attacker controlled: a flat array of 400,000 elements allocated 178 MB where +the finished slice needs 29 MB. + +Doubling is taken over only once the slice is already large, because below +that point the runtime is doing the same thing and doing it without the +over allocation that a fixed starting capacity would impose on the small +containers that make up most real documents. + +These are two nearly identical functions rather than one generic function on +purpose, so that the package keeps building on Go releases older than 1.18. +*/ +const growthTakeoverCapacity = 256 + +func appendElement(elements []node, element node) []node { + if len(elements) == cap(elements) && cap(elements) >= growthTakeoverCapacity { + grown := make([]node, len(elements), cap(elements)*2) + copy(grown, elements) + elements = grown + } + return append(elements, element) +} + +func appendMember(members []nameValueType, member nameValueType) []nameValueType { + if len(members) == cap(members) && cap(members) >= growthTakeoverCapacity { + grown := make([]nameValueType, len(members), cap(members)*2) + copy(grown, members) + members = grown + } + return append(members, member) +} + +/* +nameValueType - One member of a JSON object. + +value is held behind a pointer deliberately. Sorting moves these structs +around, and sort.Slice swaps them through a reflect based swapper that copies +the whole struct each time, so an object with many members is sensitive to how +wide this struct is. A pointer keeps it narrower than an inline node would, +which matters because the number of members in an object is attacker chosen. +*/ type nameValueType struct { name string sortKey []uint16 - value string + value *node } type jcsData struct { @@ -27,8 +115,17 @@ type jcsData struct { jsonData []byte // Current pointer in jsonData index int + // Current nesting depth of arrays and objects + depth int } +// maxNestingDepth bounds the recursion depth of parseElement, parseArray, and +// parseObject. Without a bound, a payload consisting of many nested arrays or +// objects (for example a long run of '[' characters) grows the goroutine call +// stack until the Go runtime aborts the process with a fatal, unrecoverable +// stack overflow. The value matches the nesting limit used by encoding/json. +const maxNestingDepth = 10000 + // JSON standard escapes (modulo \u) var ( asciiEscapes = []byte{'\\', '"', 'b', 'f', 'n', 'r', 't'} @@ -38,6 +135,38 @@ var ( // JSON literals var literals = []string{"true", "false", "null"} +// maxErrorTokenLength bounds how much of the input document may be copied +// into an error message. +const maxErrorTokenLength = 32 + +// forError renders a fragment of the input document for inclusion in an +// error message. It quotes the fragment and truncates it to +// maxErrorTokenLength bytes. Both matter for a canonicalizer that runs on +// untrusted input: error strings are routinely written to logs, so an +// unbounded fragment would let a single request write megabytes of attacker +// chosen data to a log, an unquoted fragment would let that data carry +// newlines or terminal escape sequences into the log, and either way the +// content of a document being signed should not be copied wholesale into +// places the document itself was never meant to reach. +func forError(value string) string { + if len(value) > maxErrorTokenLength { + return fmt.Sprintf("%q (truncated from %d bytes)", + value[:maxErrorTokenLength], len(value)) + } + return fmt.Sprintf("%q", value) +} + +// UTF-16 surrogate ranges, used to validate \u escape pairs. A valid +// surrogate pair is a high surrogate (the first code unit) followed by a low +// surrogate (the second code unit); any other pairing is ill-formed and must +// be rejected rather than silently decoded to U+FFFD. +const ( + highSurrogateMin = 0xD800 + highSurrogateMax = 0xDBFF + lowSurrogateMin = 0xDC00 + lowSurrogateMax = 0xDFFF +) + // Transform converts raw JSON data from a []byte array into a canonicalized version according RFC 8785 func Transform(jsonData []byte) ([]byte, error) { if jsonData == nil { @@ -49,7 +178,7 @@ func Transform(jsonData []byte) ([]byte, error) { jd.jsonData = jsonData j := &jd - transformed, err := j.parseEntry() + root, err := j.parseEntry() if err != nil { return nil, err } @@ -60,7 +189,52 @@ func Transform(jsonData []byte) ([]byte, error) { } j.index++ } - return []byte(transformed), err + + // Serialize the parsed tree in a single pass into one buffer, so that + // every byte of the canonical output is written exactly once. The input + // length is only a starting hint: canonical output is usually smaller + // than its input, because insignificant whitespace is dropped, but a + // number such as 1e20 does expand on the way out. + var canonical bytes.Buffer + canonical.Grow(len(jsonData)) + j.writeNode(&canonical, root) + return canonical.Bytes(), nil +} + +/* +writeNode - Append the canonical text of one node, and of everything below +it, to out. + +Recursion here is bounded by the same maxNestingDepth that bounded parsing, +because the tree cannot be deeper than the input that produced it. +*/ +func (j *jcsData) writeNode(out *bytes.Buffer, n node) { + switch n.kind { + case nodeArray: + out.WriteByte('[') + for i := range n.elements { + if i > 0 { + out.WriteByte(',') + } + j.writeNode(out, n.elements[i]) + } + out.WriteByte(']') + + case nodeObject: + out.WriteByte('{') + for i := range n.members { + if i > 0 { + out.WriteByte(',') + } + out.WriteString(j.decorateString(n.members[i].name)) + out.WriteByte(':') + j.writeNode(out, *n.members[i].value) + } + out.WriteByte('}') + + default: + out.WriteString(n.text) + } } func (j *jcsData) isWhiteSpace(c byte) bool { @@ -101,7 +275,7 @@ func (j *jcsData) scanFor(expected byte) error { return err } if c != expected { - return fmt.Errorf("Expected %s but got %s", string(expected), string(c)) + return fmt.Errorf("Expected %q but got %q", rune(expected), rune(c)) } return nil } @@ -150,25 +324,21 @@ CoreLoop: } // parseEntry is the entrypoint into the parsing control flow -func (j *jcsData) parseEntry() (string, error) { - c, err := j.scan() +func (j *jcsData) parseEntry() (node, error) { + _, err := j.scan() if err != nil { - return "", err + return node{}, err } j.index-- - switch c { - case '{', '"', '[': - return j.parseElement() - default: - value, err := parseLiteral(string(j.jsonData)) - if err != nil { - return "", err - } - - j.index = len(j.jsonData) - return value, nil - } + // Every top level value, a bare literal or number included, is parsed by + // the ordinary element parser. Handing the entire buffer to parseLiteral + // instead, as this function used to, made any insignificant whitespace + // around a top level scalar part of the token itself, so that ordinary + // documents such as "true\n" or " 42" were rejected even though RFC 8259 + // permits whitespace around the top level value. Transform checks for + // trailing content once the value has been parsed. + return j.parseElement() } func (j *jcsData) parseQuotedString() (string, error) { @@ -205,6 +375,13 @@ CoreLoop: } if utf16.IsSurrogate(firstUTF16) { + // Only a high surrogate may begin a pair. A lone low + // surrogate here is ill-formed and RFC 8785 requires + // that it be rejected rather than decoded. + if firstUTF16 < highSurrogateMin || firstUTF16 > highSurrogateMax { + return "", fmt.Errorf("Invalid high surrogate: \\u%04x", firstUTF16) + } + // If the first UTF-16 code unit has a certain value there must be // another succeeding UTF-16 code unit as well backslash, err := j.nextChar() @@ -225,6 +402,13 @@ CoreLoop: if err != nil { return "", err } + + // The second code unit must be a low surrogate. Any other + // value is an invalid pairing that utf16.DecodeRune would + // otherwise silently turn into U+FFFD. + if uEscape < lowSurrogateMin || uEscape > lowSurrogateMax { + return "", fmt.Errorf("Invalid low surrogate: \\u%04x", uEscape) + } rawString.WriteRune(utf16.DecodeRune(firstUTF16, uEscape)) } else { @@ -242,36 +426,60 @@ CoreLoop: continue CoreLoop } } - return "", fmt.Errorf("Unexpected escape: \\%s", string(c)) + return "", fmt.Errorf("Unexpected escape: %q", string([]byte{'\\', c})) } - } else { - // Just an ordinary ASCII character alternatively a UTF-8 byte - // outside of ASCII. + } else if c < 0x80 { + // An ordinary ASCII character. // Note that properly formatted UTF-8 never clashes with ASCII // making byte per byte search for ASCII break characters work // as expected. rawString.WriteByte(c) + } else { + // The lead byte of a multi-byte UTF-8 sequence. RFC 8785 §3.2.4 + // requires the canonical output to be valid UTF-8, so the + // sequence starting here is decoded and validated rather than + // copied through byte for byte: a byte such as 0xFF is not + // valid at any position in UTF-8 and must be rejected, not + // passed along into the output unchanged. + j.index-- + r, size := utf8.DecodeRune(j.jsonData[j.index:]) + if r == utf8.RuneError && size <= 1 { + return "", fmt.Errorf("Invalid UTF-8 sequence at byte 0x%02x", c) + } + rawString.Write(j.jsonData[j.index : j.index+size]) + j.index += size } } return rawString.String(), nil } -func (j *jcsData) parseSimpleType() (string, error) { +func (j *jcsData) parseSimpleType() (node, error) { var token strings.Builder j.index-- - // no condition is needed here. - // if the buffer reaches EOF scan returns an error, or we terminate because the - // json simple type terminates for { - c, err := j.scan() + // End of input terminates a top level literal or number that is not + // followed by any structural character, such as the whole document + // "42". An unterminated array or object is still rejected, because + // the caller goes on to fail on the missing ']' or '}'. + if j.index >= len(j.jsonData) { + break + } + + c, err := j.nextChar() if err != nil { - return "", err + return node{}, err } - if c == ',' || c == ']' || c == '}' { + // A literal or number is terminated by a structural character or by + // whitespace. Using nextChar (rather than scan, which silently skips + // whitespace) and stopping on whitespace here means interior spaces, + // tabs, or newlines are never stripped out of the middle of a token: + // "1 2 3" and "tr ue" are left as ill-formed instead of collapsing + // into "123" and "true". + if c == ',' || c == ']' || c == '}' || j.isWhiteSpace(c) { j.index-- break } @@ -280,12 +488,30 @@ func (j *jcsData) parseSimpleType() (string, error) { } if token.Len() == 0 { - return "", errors.New("Missing argument") + return node{}, errors.New("Missing argument") } - return parseLiteral(token.String()) + text, err := parseLiteral(token.String()) + if err != nil { + return node{}, err + } + + return node{kind: nodeScalar, text: text}, nil } +// numberPattern is the RFC 8259 §6 number grammar: +// +// number = [ "-" ] int [ frac ] [ exp ] +// int = "0" / ( digit1-9 *DIGIT ) +// frac = "." 1*DIGIT +// exp = ("e" / "E") [ "-" / "+" ] 1*DIGIT +// +// strconv.ParseFloat accepts a considerably wider grammar than this (hex +// floating-point literals, a leading '+', leading zeros, digit-separator +// underscores, and a bare leading or trailing '.'), so a token must match +// this pattern before it is handed to ParseFloat. +var numberPattern = regexp.MustCompile(`^-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?$`) + func parseLiteral(value string) (string, error) { // Is it a JSON literal? for _, literal := range literals { @@ -294,10 +520,20 @@ func parseLiteral(value string) (string, error) { } } - // Apparently not so we assume that it is a I-JSON number + // Apparently not a literal, so we assume that it is a I-JSON number. + // Reject anything that is not a well-formed JSON number (and is not one + // of the known literals either) before consulting strconv.ParseFloat. + if !numberPattern.MatchString(value) { + return "", fmt.Errorf("Invalid literal or number: %s", forError(value)) + } + ieeeF64, err := strconv.ParseFloat(value, 64) if err != nil { - return "", err + // The error strconv returns embeds the entire token, so it is + // replaced here with a bounded message. A syntactically valid JSON + // number may be arbitrarily long, and only a value out of range for + // an IEEE 754 double can reach this point. + return "", fmt.Errorf("Number out of range: %s", forError(value)) } value, err = NumberToJSON(ieeeF64) @@ -308,10 +544,10 @@ func parseLiteral(value string) (string, error) { return value, nil } -func (j *jcsData) parseElement() (string, error) { +func (j *jcsData) parseElement() (node, error) { c, err := j.scan() if err != nil { - return "", err + return node{}, err } switch c { @@ -320,9 +556,9 @@ func (j *jcsData) parseElement() (string, error) { case '"': str, err := j.parseQuotedString() if err != nil { - return "", err + return node{}, err } - return j.decorateString(str), nil + return node{kind: nodeScalar, text: j.decorateString(str)}, nil case '[': return j.parseArray() default: @@ -340,16 +576,22 @@ func (j *jcsData) peek() (byte, error) { return c, nil } -func (j *jcsData) parseArray() (string, error) { - var arrayData strings.Builder - var next bool +func (j *jcsData) parseArray() (node, error) { + j.depth++ + defer func() { j.depth-- }() + if j.depth > maxNestingDepth { + return node{}, fmt.Errorf("Maximum nesting depth of %d exceeded", maxNestingDepth) + } - arrayData.WriteByte('[') + // Element order in an array is significant and is never changed, so the + // elements are simply collected in document order. + elements := []node{} + var next bool for { c, err := j.peek() if err != nil { - return "", err + return node{}, err } if c == ']' { @@ -360,62 +602,58 @@ func (j *jcsData) parseArray() (string, error) { if next { err = j.scanFor(',') if err != nil { - return "", err + return node{}, err } - arrayData.WriteByte(',') } else { next = true } element, err := j.parseElement() if err != nil { - return "", err + return node{}, err } - arrayData.WriteString(element) + elements = appendElement(elements, element) } - arrayData.WriteByte(']') - return arrayData.String(), nil + return node{kind: nodeArray, elements: elements}, nil } -func (j *jcsData) lexicographicallyPrecedes(sortKey []uint16, e *list.Element) (bool, error) { - // Find the minimum length of the sortKeys - oldSortKey := e.Value.(nameValueType).sortKey - minLength := len(oldSortKey) - if minLength > len(sortKey) { - minLength = len(sortKey) +// compareSortKeys lexicographically compares two UTF-16 sort keys, returning +// a negative number if a precedes b, zero if they are equal, and a positive +// number if a succeeds b. It is used to sort object members once, in +// O(n log n), rather than the earlier approach of scanning a linked list from +// the front for every new member, which was O(n) per insertion (O(n^2) +// overall) and let an object with many keys already in ascending order (a +// trivially attacker-chosen input) burn CPU quadratically in the number of +// members. +func compareSortKeys(a, b []uint16) int { + minLength := len(a) + if minLength > len(b) { + minLength = len(b) } for q := 0; q < minLength; q++ { - diff := int(sortKey[q]) - int(oldSortKey[q]) - if diff < 0 { - // Smaller => Precedes - return true, nil - } else if diff > 0 { - // Bigger => No match - return false, nil + diff := int(a[q]) - int(b[q]) + if diff != 0 { + return diff } - // Still equal => Continue } - // The sortKeys compared equal up to minLength - if len(sortKey) < len(oldSortKey) { - // Shorter => Precedes - return true, nil - } - if len(sortKey) == len(oldSortKey) { - return false, fmt.Errorf("Duplicate key: %s", e.Value.(nameValueType).name) - } - // Longer => No match - return false, nil + // Equal up to minLength, so the shorter key precedes the longer one. + return len(a) - len(b) } -func (j *jcsData) parseObject() (string, error) { - nameValueList := list.New() +func (j *jcsData) parseObject() (node, error) { + j.depth++ + defer func() { j.depth-- }() + if j.depth > maxNestingDepth { + return node{}, fmt.Errorf("Maximum nesting depth of %d exceeded", maxNestingDepth) + } + + nameValues := []nameValueType{} var next bool = false -CoreLoop: for { c, err := j.peek() if err != nil { - return "", err + return node{}, err } if c == '}' { @@ -427,18 +665,18 @@ CoreLoop: if next { err = j.scanFor(',') if err != nil { - return "", err + return node{}, err } } next = true err = j.scanFor('"') if err != nil { - return "", err + return node{}, err } rawUTF8, err := j.parseQuotedString() if err != nil { - break + return node{}, err } // Sort keys on UTF-16 code units // Since UTF-8 doesn't have endianess this is just a value transformation @@ -446,44 +684,32 @@ CoreLoop: sortKey := utf16.Encode([]rune(rawUTF8)) err = j.scanFor(':') if err != nil { - return "", err + return node{}, err } element, err := j.parseElement() if err != nil { - return "", err + return node{}, err } - nameValue := nameValueType{rawUTF8, sortKey, element} - for e := nameValueList.Front(); e != nil; e = e.Next() { - // Check if the key is smaller than a previous key - if precedes, err := j.lexicographicallyPrecedes(sortKey, e); err != nil { - return "", err - } else if precedes { - // Precedes => Insert before and exit sorting - nameValueList.InsertBefore(nameValue, e) - continue CoreLoop - } - // Continue searching for a possibly succeeding sortKey - // (which is straightforward since the list is ordered) - } - // The sortKey is either the first or is succeeding all previous sortKeys - nameValueList.PushBack(nameValue) + value := element + nameValues = appendMember(nameValues, nameValueType{rawUTF8, sortKey, &value}) } - // Now everything is sorted so we can properly serialize the object - var objectData strings.Builder - objectData.WriteByte('{') - next = false - for e := nameValueList.Front(); e != nil; e = e.Next() { - if next { - objectData.WriteByte(',') + // Sort all members once, in O(n log n), rather than maintaining sorted + // order incrementally as each member is parsed. + sort.Slice(nameValues, func(i, k int) bool { + return compareSortKeys(nameValues[i].sortKey, nameValues[k].sortKey) < 0 + }) + + // A duplicate key sorts adjacent to itself, so a single linear pass over + // the now-sorted members is enough to detect it. + for i := 1; i < len(nameValues); i++ { + if compareSortKeys(nameValues[i-1].sortKey, nameValues[i].sortKey) == 0 { + return node{}, fmt.Errorf("Duplicate key: %s", forError(nameValues[i].name)) } - next = true - nameValue := e.Value.(nameValueType) - objectData.WriteString(j.decorateString(nameValue.name)) - objectData.WriteByte(':') - objectData.WriteString(nameValue.value) } - objectData.WriteByte('}') - return objectData.String(), nil + + // The members are sorted here, at parse time, but they are not written + // out here. Serialization of the whole tree happens in one later pass. + return node{kind: nodeObject, members: nameValues}, nil } diff --git a/vendor/go.yaml.in/yaml/v3/parserc.go b/vendor/go.yaml.in/yaml/v3/parserc.go index 25fe823..f35829d 100644 --- a/vendor/go.yaml.in/yaml/v3/parserc.go +++ b/vendor/go.yaml.in/yaml/v3/parserc.go @@ -226,9 +226,9 @@ func yaml_parser_state_machine(parser *yaml_parser_t, event *yaml_event_t) bool } // Parse the production: -// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END // -// ************ +// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +// ************ func yaml_parser_parse_stream_start(parser *yaml_parser_t, event *yaml_event_t) bool { token := peek_token(parser) if token == nil { @@ -249,13 +249,11 @@ func yaml_parser_parse_stream_start(parser *yaml_parser_t, event *yaml_event_t) } // Parse the productions: -// implicit_document ::= block_node DOCUMENT-END* -// -// * // -// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* -// -// ************************* +// implicit_document ::= block_node DOCUMENT-END* +// * +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// ************************* func yaml_parser_parse_document_start(parser *yaml_parser_t, event *yaml_event_t, implicit bool) bool { token := peek_token(parser) @@ -359,9 +357,9 @@ func yaml_parser_parse_document_start(parser *yaml_parser_t, event *yaml_event_t } // Parse the productions: -// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* // -// *********** +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// *********** func yaml_parser_parse_document_content(parser *yaml_parser_t, event *yaml_event_t) bool { token := peek_token(parser) if token == nil { @@ -382,11 +380,10 @@ func yaml_parser_parse_document_content(parser *yaml_parser_t, event *yaml_event } // Parse the productions: -// implicit_document ::= block_node DOCUMENT-END* -// -// ************* // -// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// implicit_document ::= block_node DOCUMENT-END* +// ************* +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* func yaml_parser_parse_document_end(parser *yaml_parser_t, event *yaml_event_t) bool { token := peek_token(parser) if token == nil { @@ -432,42 +429,32 @@ func yaml_parser_set_event_comments(parser *yaml_parser_t, event *yaml_event_t) } // Parse the productions: -// block_node_or_indentless_sequence ::= -// -// ALIAS -// ***** -// | properties (block_content | indentless_block_sequence)? -// ********** * -// | block_content | indentless_block_sequence -// * -// -// block_node ::= ALIAS -// -// ***** -// | properties block_content? -// ********** * -// | block_content -// * -// -// flow_node ::= ALIAS -// -// ***** -// | properties flow_content? -// ********** * -// | flow_content -// * -// -// properties ::= TAG ANCHOR? | ANCHOR TAG? -// -// ************************* -// -// block_content ::= block_collection | flow_collection | SCALAR -// -// ****** // -// flow_content ::= flow_collection | SCALAR -// -// ****** +// block_node_or_indentless_sequence ::= +// ALIAS +// ***** +// | properties (block_content | indentless_block_sequence)? +// ********** * +// | block_content | indentless_block_sequence +// * +// block_node ::= ALIAS +// ***** +// | properties block_content? +// ********** * +// | block_content +// * +// flow_node ::= ALIAS +// ***** +// | properties flow_content? +// ********** * +// | flow_content +// * +// properties ::= TAG ANCHOR? | ANCHOR TAG? +// ************************* +// block_content ::= block_collection | flow_collection | SCALAR +// ****** +// flow_content ::= flow_collection | SCALAR +// ****** func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, indentless_sequence bool) bool { //defer trace("yaml_parser_parse_node", "block:", block, "indentless_sequence:", indentless_sequence)() @@ -697,9 +684,9 @@ func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, i } // Parse the productions: -// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END // -// ******************** *********** * ********* +// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +// ******************** *********** * ********* func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { if first { token := peek_token(parser) @@ -755,9 +742,9 @@ func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_e } // Parse the productions: -// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ // -// *********** * +// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +// *********** * func yaml_parser_parse_indentless_sequence_entry(parser *yaml_parser_t, event *yaml_event_t) bool { token := peek_token(parser) if token == nil { @@ -821,15 +808,15 @@ func yaml_parser_split_stem_comment(parser *yaml_parser_t, stem_len int) { } // Parse the productions: -// block_mapping ::= BLOCK-MAPPING_START // -// ******************* -// ((KEY block_node_or_indentless_sequence?)? -// *** * -// (VALUE block_node_or_indentless_sequence?)?)* +// block_mapping ::= BLOCK-MAPPING_START +// ******************* +// ((KEY block_node_or_indentless_sequence?)? +// *** * +// (VALUE block_node_or_indentless_sequence?)?)* // -// BLOCK-END -// ********* +// BLOCK-END +// ********* func yaml_parser_parse_block_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { if first { token := peek_token(parser) @@ -896,13 +883,14 @@ func yaml_parser_parse_block_mapping_key(parser *yaml_parser_t, event *yaml_even } // Parse the productions: -// block_mapping ::= BLOCK-MAPPING_START // -// ((KEY block_node_or_indentless_sequence?)? +// block_mapping ::= BLOCK-MAPPING_START +// +// ((KEY block_node_or_indentless_sequence?)? // -// (VALUE block_node_or_indentless_sequence?)?)* -// ***** * -// BLOCK-END +// (VALUE block_node_or_indentless_sequence?)?)* +// ***** * +// BLOCK-END func yaml_parser_parse_block_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { token := peek_token(parser) if token == nil { @@ -929,19 +917,17 @@ func yaml_parser_parse_block_mapping_value(parser *yaml_parser_t, event *yaml_ev } // Parse the productions: -// flow_sequence ::= FLOW-SEQUENCE-START -// -// ******************* -// (flow_sequence_entry FLOW-ENTRY)* -// * ********** -// flow_sequence_entry? -// * -// FLOW-SEQUENCE-END -// ***************** // -// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? -// -// * +// flow_sequence ::= FLOW-SEQUENCE-START +// ******************* +// (flow_sequence_entry FLOW-ENTRY)* +// * ********** +// flow_sequence_entry? +// * +// FLOW-SEQUENCE-END +// ***************** +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * func yaml_parser_parse_flow_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { if first { token := peek_token(parser) @@ -1005,9 +991,9 @@ func yaml_parser_parse_flow_sequence_entry(parser *yaml_parser_t, event *yaml_ev } // Parse the productions: -// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? // -// *** * +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// *** * func yaml_parser_parse_flow_sequence_entry_mapping_key(parser *yaml_parser_t, event *yaml_event_t) bool { token := peek_token(parser) if token == nil { @@ -1026,9 +1012,9 @@ func yaml_parser_parse_flow_sequence_entry_mapping_key(parser *yaml_parser_t, ev } // Parse the productions: -// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? // -// ***** * +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// ***** * func yaml_parser_parse_flow_sequence_entry_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { token := peek_token(parser) if token == nil { @@ -1050,9 +1036,9 @@ func yaml_parser_parse_flow_sequence_entry_mapping_value(parser *yaml_parser_t, } // Parse the productions: -// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? // -// * +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * func yaml_parser_parse_flow_sequence_entry_mapping_end(parser *yaml_parser_t, event *yaml_event_t) bool { token := peek_token(parser) if token == nil { @@ -1068,18 +1054,17 @@ func yaml_parser_parse_flow_sequence_entry_mapping_end(parser *yaml_parser_t, ev } // Parse the productions: -// flow_mapping ::= FLOW-MAPPING-START -// -// ****************** -// (flow_mapping_entry FLOW-ENTRY)* -// * ********** -// flow_mapping_entry? -// ****************** -// FLOW-MAPPING-END -// **************** // -// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? -// - *** * +// flow_mapping ::= FLOW-MAPPING-START +// ****************** +// (flow_mapping_entry FLOW-ENTRY)* +// * ********** +// flow_mapping_entry? +// ****************** +// FLOW-MAPPING-END +// **************** +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * *** * func yaml_parser_parse_flow_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { if first { token := peek_token(parser) @@ -1144,8 +1129,9 @@ func yaml_parser_parse_flow_mapping_key(parser *yaml_parser_t, event *yaml_event } // Parse the productions: -// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? -// - ***** * +// +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * ***** * func yaml_parser_parse_flow_mapping_value(parser *yaml_parser_t, event *yaml_event_t, empty bool) bool { token := peek_token(parser) if token == nil { diff --git a/vendor/go.yaml.in/yaml/v3/yamlh.go b/vendor/go.yaml.in/yaml/v3/yamlh.go index f59aa40..07c4423 100644 --- a/vendor/go.yaml.in/yaml/v3/yamlh.go +++ b/vendor/go.yaml.in/yaml/v3/yamlh.go @@ -433,21 +433,19 @@ type yaml_document_t struct { // The prototype of a read handler. // -// The read handler is called when the parser needs to read more bytes from the -// source. The handler should write not more than size bytes to the buffer. -// The number of written bytes should be set to the size_read variable. +// The read handler is called when the parser needs to read more bytes from the +// source. The handler should write not more than size bytes to the buffer. +// The number of written bytes should be set to the size_read variable. // -// [in,out] data A pointer to an application data specified by +// [in,out] data A pointer to an application data specified by +// yaml_parser_set_input(). +// [out] buffer The buffer to write the data from the source. +// [in] size The size of the buffer. +// [out] size_read The actual number of bytes read from the source. // -// yaml_parser_set_input(). -// -// [out] buffer The buffer to write the data from the source. -// [in] size The size of the buffer. -// [out] size_read The actual number of bytes read from the source. -// -// On success, the handler should return 1. If the handler failed, -// the returned value should be 0. On EOF, the handler should set the -// size_read to 0 and return 1. +// On success, the handler should return 1. If the handler failed, +// the returned value should be 0. On EOF, the handler should set the +// size_read to 0 and return 1. type yaml_read_handler_t func(parser *yaml_parser_t, buffer []byte) (n int, err error) // This structure holds information about a potential simple key. @@ -655,19 +653,17 @@ type yaml_comment_t struct { // The prototype of a write handler. // -// The write handler is called when the emitter needs to flush the accumulated -// characters to the output. The handler should write @a size bytes of the -// @a buffer to the output. -// -// @param[in,out] data A pointer to an application data specified by -// -// yaml_emitter_set_output(). +// The write handler is called when the emitter needs to flush the accumulated +// characters to the output. The handler should write @a size bytes of the +// @a buffer to the output. // -// @param[in] buffer The buffer with bytes to be written. -// @param[in] size The size of the buffer. +// @param[in,out] data A pointer to an application data specified by +// yaml_emitter_set_output(). +// @param[in] buffer The buffer with bytes to be written. +// @param[in] size The size of the buffer. // -// @returns On success, the handler should return @c 1. If the handler failed, -// the returned value should be @c 0. +// @returns On success, the handler should return @c 1. If the handler failed, +// the returned value should be @c 0. type yaml_write_handler_t func(emitter *yaml_emitter_t, buffer []byte) error type yaml_emitter_state_t int diff --git a/vendor/modules.txt b/vendor/modules.txt index 886fbb9..cc538ce 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -27,8 +27,8 @@ github.com/google/cel-go/common/types/traits github.com/google/cel-go/interpreter github.com/google/cel-go/parser github.com/google/cel-go/parser/gen -# github.com/gowebpki/jcs v1.0.1 -## explicit; go 1.15 +# github.com/gowebpki/jcs v1.0.2 +## explicit; go 1.21 github.com/gowebpki/jcs # github.com/ncruces/go-sqlite3 v0.35.2 ## explicit; go 1.25.0 @@ -50,7 +50,7 @@ github.com/ncruces/julianday ## explicit; go 1.21 github.com/santhosh-tekuri/jsonschema/v6 github.com/santhosh-tekuri/jsonschema/v6/kind -# go.yaml.in/yaml/v3 v3.0.4 +# go.yaml.in/yaml/v3 v3.0.5 ## explicit; go 1.16 go.yaml.in/yaml/v3 # golang.org/x/exp v0.0.0-20240823005443-9b4947da3948