mirror of
https://github.com/docker/cli.git
synced 2026-11-13 08:13:40 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5457199aa5 | ||
|
|
cac50c5e62 | ||
|
|
00d843b496 | ||
|
|
64fe50bbea | ||
|
|
999ab16f09 | ||
|
|
bb485bc900 | ||
|
|
86e51b44de | ||
|
|
d9bbcfcb4d | ||
|
|
2e886262b1 | ||
|
|
a4971d1f60 | ||
|
|
bfb5814dd5 | ||
|
|
68e452624a | ||
|
|
160531c55d | ||
|
|
a82e528901 | ||
|
|
e35bc1b6ae | ||
|
|
baf6c13889 | ||
|
|
4051f75f69 | ||
|
|
76321a699d | ||
|
|
b82456c62f | ||
|
|
a98becc3e3 | ||
|
|
dfc520dcc2 | ||
|
|
a3a2bb6118 | ||
|
|
c11295afc4 | ||
|
|
010e125d8b | ||
|
|
5cdf229230 | ||
|
|
66f6797582 | ||
|
|
cb8870a537 | ||
|
|
529f66d2f9 | ||
|
|
4d6ff7d646 | ||
|
|
b997c5b7aa | ||
|
|
1822903c1d | ||
|
|
37013cb262 | ||
|
|
61ee187552 | ||
|
|
433b13a249 | ||
|
|
5c8bfb2844 | ||
|
|
528a2ada2e | ||
|
|
de8f4032ad | ||
|
|
e4078058de | ||
|
|
8da8b00555 | ||
|
|
067d069f0c | ||
|
|
02fbbf3eb0 | ||
|
|
890976a13b | ||
|
|
23c66ccd61 | ||
|
|
3e686a8b59 | ||
|
|
eabc65846b | ||
|
|
b873f57cd0 | ||
|
|
c8a849c2f9 | ||
|
|
f4efc1b596 | ||
|
|
2747e2e3bf | ||
|
|
329cdf1a9d | ||
|
|
0c7e2e7203 | ||
|
|
dfc4efb1e2 | ||
|
|
dba867594c | ||
|
|
d9aefe565a | ||
|
|
9182b351c1 | ||
|
|
0ae55a3e6c | ||
|
|
029a4b21b3 | ||
|
|
e596fea454 | ||
|
|
0c72f456aa | ||
|
|
26f2736a2b | ||
|
|
ac4c4dfabd | ||
|
|
1245054535 | ||
|
|
f72fb0caf0 | ||
|
|
e4af2bf0d3 | ||
|
|
49a1af6aaa | ||
|
|
c2be9ccfc3 | ||
|
|
0da6a5121f | ||
|
|
6b3ca8fc04 | ||
|
|
f47603c290 | ||
|
|
9709c8fe08 | ||
|
|
af45a23fa9 | ||
|
|
bc97f5ac19 | ||
|
|
6d71967120 | ||
|
|
9b51892b80 |
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"config:recommended",
|
||||||
|
// https://docs.renovatebot.com/modules/manager/github-actions/
|
||||||
|
// https://docs.renovatebot.com/presets-helpers/
|
||||||
|
"helpers:pinGitHubActionDigestsToSemver",
|
||||||
|
":gitSignOff"
|
||||||
|
],
|
||||||
|
"gomod": {
|
||||||
|
"managerFilePatterns": ["/(^|/)vendor\\.mod$/"]
|
||||||
|
},
|
||||||
|
"postUpdateOptions": ["gomodTidy"],
|
||||||
|
"minimumReleaseAge": "3 days",
|
||||||
|
"internalChecksFilter": "strict",
|
||||||
|
"packageRules": [
|
||||||
|
{
|
||||||
|
"matchManagers": ["gomod"],
|
||||||
|
"matchFileNames": ["vendor.mod"],
|
||||||
|
"semanticCommits": "disabled",
|
||||||
|
"commitMessagePrefix": "vendor:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matchManagers": ["gomod"],
|
||||||
|
"matchFileNames": ["cmd/docker-trust/go.mod"],
|
||||||
|
// The trust module does not vendor dependencies; vendor/ belongs to the root module.
|
||||||
|
"postUpdateOptions": ["gomodSkipVendor"],
|
||||||
|
"semanticCommits": "disabled",
|
||||||
|
"commitMessagePrefix": "cmd/docker-trust:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matchManagers": ["gomod"],
|
||||||
|
"matchFileNames": ["vendor.mod"],
|
||||||
|
"groupName": "golang.org/x packages",
|
||||||
|
"groupSlug": "golang-x",
|
||||||
|
"matchDepNames": ["/^golang\\.org\\/x\\//"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matchManagers": ["github-actions"],
|
||||||
|
"minimumReleaseAge": "7 days",
|
||||||
|
"addLabels": ["area/ci", "status/2-code-review"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matchManagers": ["github-actions"],
|
||||||
|
"groupName": "docker GitHub Actions",
|
||||||
|
"groupSlug": "docker-actions",
|
||||||
|
"matchDepNames": ["/^docker\\//"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Keep all the google.golang.org/genproto modules at the same version.
|
||||||
|
"matchManagers": ["gomod"],
|
||||||
|
"matchFileNames": ["vendor.mod"],
|
||||||
|
"groupName": "google.golang.org/genproto packages",
|
||||||
|
"groupSlug": "google-genproto",
|
||||||
|
"matchDepNames": ["/^google\\.golang\\.org\\/genproto(\\/|$)/"],
|
||||||
|
"schedule": ["every month"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -65,7 +65,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4.4.0
|
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
||||||
-
|
-
|
||||||
name: Build
|
name: Build
|
||||||
uses: docker/bake-action@018cb6412ab401ebaa809aa5f85966b74628600f # v7.4.0
|
uses: docker/bake-action@018cb6412ab401ebaa809aa5f85966b74628600f # v7.4.0
|
||||||
@@ -91,7 +91,7 @@ jobs:
|
|||||||
PLATFORM: ${{ matrix.platform }}
|
PLATFORM: ${{ matrix.platform }}
|
||||||
-
|
-
|
||||||
name: Upload artifacts
|
name: Upload artifacts
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: ${{ env.ARTIFACT_NAME }}
|
name: ${{ env.ARTIFACT_NAME }}
|
||||||
path: /tmp/out/*
|
path: /tmp/out/*
|
||||||
@@ -156,7 +156,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4.4.0
|
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
||||||
-
|
-
|
||||||
name: Build
|
name: Build
|
||||||
uses: docker/bake-action@018cb6412ab401ebaa809aa5f85966b74628600f # v7.4.0
|
uses: docker/bake-action@018cb6412ab401ebaa809aa5f85966b74628600f # v7.4.0
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ jobs:
|
|||||||
name: Update Go
|
name: Update Go
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.26.9"
|
go-version: "1.26.10"
|
||||||
cache: false
|
cache: false
|
||||||
-
|
-
|
||||||
name: Initialize CodeQL
|
name: Initialize CodeQL
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
docker info
|
docker info
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4.4.0
|
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
||||||
-
|
-
|
||||||
name: Run ${{ matrix.target }}
|
name: Run ${{ matrix.target }}
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Upload context
|
- name: Upload context
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: pr-review-context
|
name: pr-review-context
|
||||||
path: context/
|
path: context/
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4.4.0
|
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
||||||
-
|
-
|
||||||
name: Test
|
name: Test
|
||||||
uses: docker/bake-action@018cb6412ab401ebaa809aa5f85966b74628600f # v7.4.0
|
uses: docker/bake-action@018cb6412ab401ebaa809aa5f85966b74628600f # v7.4.0
|
||||||
@@ -68,7 +68,7 @@ jobs:
|
|||||||
name: Set up Go
|
name: Set up Go
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.26.9"
|
go-version: "1.26.10"
|
||||||
cache: false
|
cache: false
|
||||||
-
|
-
|
||||||
name: Test
|
name: Test
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ jobs:
|
|||||||
name: Set up Go
|
name: Set up Go
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.26.9"
|
go-version: "1.26.10"
|
||||||
cache: false
|
cache: false
|
||||||
-
|
-
|
||||||
name: Run gocompat check
|
name: Run gocompat check
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ run:
|
|||||||
# which causes it to fallback to go1.17 semantics.
|
# which causes it to fallback to go1.17 semantics.
|
||||||
#
|
#
|
||||||
# TODO(thaJeztah): update "usetesting" settings to enable go1.24 features once our minimum version is go1.24
|
# TODO(thaJeztah): update "usetesting" settings to enable go1.24 features once our minimum version is go1.24
|
||||||
go: "1.26.9"
|
go: "1.26.10"
|
||||||
|
|
||||||
timeout: 5m
|
timeout: 5m
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -8,11 +8,11 @@ ARG BASE_VARIANT=alpine
|
|||||||
ARG ALPINE_VERSION=3.23
|
ARG ALPINE_VERSION=3.23
|
||||||
ARG BASE_DEBIAN_DISTRO=bookworm
|
ARG BASE_DEBIAN_DISTRO=bookworm
|
||||||
|
|
||||||
ARG GO_VERSION=1.26.9
|
ARG GO_VERSION=1.26.10
|
||||||
|
|
||||||
# XX_VERSION specifies the version of the xx utility to use.
|
# XX_VERSION specifies the version of the xx utility to use.
|
||||||
# It must be a valid tag in the docker.io/tonistiigi/xx image repository.
|
# It must be a valid tag in the docker.io/tonistiigi/xx image repository.
|
||||||
ARG XX_VERSION=1.9.0
|
ARG XX_VERSION=1.10.0
|
||||||
|
|
||||||
# GOVERSIONINFO_VERSION is the version of GoVersionInfo to install.
|
# GOVERSIONINFO_VERSION is the version of GoVersionInfo to install.
|
||||||
# It must be a valid tag from https://github.com/josephspurrier/goversioninfo
|
# It must be a valid tag from https://github.com/josephspurrier/goversioninfo
|
||||||
@@ -25,12 +25,12 @@ ARG GOTESTSUM_VERSION=v1.13.0
|
|||||||
# BUILDX_VERSION sets the version of buildx to use for the e2e tests.
|
# BUILDX_VERSION sets the version of buildx to use for the e2e tests.
|
||||||
# It must be a tag in the docker.io/docker/buildx-bin image repository
|
# It must be a tag in the docker.io/docker/buildx-bin image repository
|
||||||
# on Docker Hub.
|
# on Docker Hub.
|
||||||
ARG BUILDX_VERSION=0.34.1
|
ARG BUILDX_VERSION=0.38.0
|
||||||
|
|
||||||
# COMPOSE_VERSION is the version of compose to install in the dev container.
|
# COMPOSE_VERSION is the version of compose to install in the dev container.
|
||||||
# It must be a tag in the docker.io/docker/compose-bin image repository
|
# It must be a tag in the docker.io/docker/compose-bin image repository
|
||||||
# on Docker Hub.
|
# on Docker Hub.
|
||||||
ARG COMPOSE_VERSION=v5.1.3
|
ARG COMPOSE_VERSION=v5.6.0
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,11 @@ package hooks
|
|||||||
type ResponseType int
|
type ResponseType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// NextSteps renders suggestions under a shared "What's next:" header.
|
||||||
NextSteps ResponseType = 0
|
NextSteps ResponseType = 0
|
||||||
|
// GenericMessage renders before NextSteps, without a header or indentation.
|
||||||
|
// CLI versions that do not recognize this type ignore the response.
|
||||||
|
GenericMessage ResponseType = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
// Request is the type representing the information
|
// Request is the type representing the information
|
||||||
|
|||||||
@@ -7,17 +7,42 @@ const (
|
|||||||
indent = " "
|
indent = " "
|
||||||
)
|
)
|
||||||
|
|
||||||
// PrintNextSteps renders list of [NextSteps] messages and writes them
|
// PrintMessages groups messages by type and writes them to out. Generic messages
|
||||||
// to out. It is a no-op if messages is empty.
|
// appear first, without a header or indentation, followed by next steps under a
|
||||||
func PrintNextSteps(out io.Writer, messages []string) {
|
// shared "What's next:" header. Order within each type is preserved.
|
||||||
if len(messages) == 0 {
|
// Messages with no lines or an unknown type produce no output.
|
||||||
return
|
func PrintMessages(out io.Writer, messages []Response, evaluate func(Response) []string) {
|
||||||
|
grouped := make(map[ResponseType][]string)
|
||||||
|
for _, message := range messages {
|
||||||
|
lines := evaluate(message)
|
||||||
|
grouped[message.Type] = append(grouped[message.Type], lines...)
|
||||||
}
|
}
|
||||||
|
for _, responseType := range []ResponseType{GenericMessage, NextSteps} {
|
||||||
|
lines := grouped[responseType]
|
||||||
|
if len(lines) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
_, _ = io.WriteString(out, whatsNext)
|
header, prefix := "\n", ""
|
||||||
for _, msg := range messages {
|
if responseType == NextSteps {
|
||||||
_, _ = io.WriteString(out, indent)
|
header, prefix = whatsNext, indent
|
||||||
_, _ = io.WriteString(out, msg)
|
}
|
||||||
_, _ = io.WriteString(out, "\n")
|
|
||||||
|
_, _ = io.WriteString(out, header)
|
||||||
|
for _, line := range lines {
|
||||||
|
_, _ = io.WriteString(out, prefix)
|
||||||
|
_, _ = io.WriteString(out, line)
|
||||||
|
_, _ = io.WriteString(out, "\n")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PrintNextSteps renders list of [NextSteps] messages and writes them
|
||||||
|
// to out. It is a no-op if messages is empty.
|
||||||
|
//
|
||||||
|
// Deprecated: use [PrintMessages] instead.
|
||||||
|
func PrintNextSteps(out io.Writer, messages []string) {
|
||||||
|
PrintMessages(out, []Response{{Type: NextSteps}}, func(Response) []string {
|
||||||
|
return messages
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,85 @@ import (
|
|||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPrintHookMessages(t *testing.T) {
|
func TestPrintMessages(t *testing.T) {
|
||||||
|
const header = "\n\x1b[1mWhat's next:\x1b[0m\n"
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
doc string
|
||||||
|
messages []hooks.Response
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{doc: "no messages"},
|
||||||
|
{
|
||||||
|
doc: "no lines",
|
||||||
|
messages: []hooks.Response{
|
||||||
|
{Type: hooks.GenericMessage},
|
||||||
|
{Type: hooks.NextSteps},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "generic message",
|
||||||
|
messages: []hooks.Response{
|
||||||
|
{Type: hooks.GenericMessage, Template: "Session active"},
|
||||||
|
},
|
||||||
|
expected: "\nSession active\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "preserve message formatting",
|
||||||
|
messages: []hooks.Response{
|
||||||
|
{Type: hooks.GenericMessage, Template: "Session active\n\n Details"},
|
||||||
|
},
|
||||||
|
expected: "\nSession active\n\n Details\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "next steps",
|
||||||
|
messages: []hooks.Response{
|
||||||
|
{Type: hooks.NextSteps, Template: "Try another command"},
|
||||||
|
},
|
||||||
|
expected: header + " Try another command\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "group interleaved types and preserve order within each type",
|
||||||
|
messages: []hooks.Response{
|
||||||
|
{Type: hooks.NextSteps, Template: "First suggestion"},
|
||||||
|
{Type: hooks.GenericMessage, Template: "Session active"},
|
||||||
|
{Type: hooks.NextSteps, Template: "Second suggestion\nThird suggestion"},
|
||||||
|
{Type: hooks.GenericMessage, Template: "Another status"},
|
||||||
|
},
|
||||||
|
expected: "\nSession active\nAnother status\n" + header + " First suggestion\n Second suggestion\n Third suggestion\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "unknown type",
|
||||||
|
messages: []hooks.Response{
|
||||||
|
{Type: hooks.ResponseType(99), Template: "Ignored"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "unknown type does not affect known messages",
|
||||||
|
messages: []hooks.Response{
|
||||||
|
{Type: hooks.ResponseType(99), Template: "Ignored"},
|
||||||
|
{Type: hooks.GenericMessage, Template: "Session active"},
|
||||||
|
{Type: hooks.NextSteps, Template: "Try another command"},
|
||||||
|
},
|
||||||
|
expected: "\nSession active\n" + header + " Try another command\n",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
|
var w strings.Builder
|
||||||
|
hooks.PrintMessages(&w, tc.messages, func(message hooks.Response) []string {
|
||||||
|
if message.Template == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
lines, err := hooks.ParseTemplate(message.Template, nil)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
return lines
|
||||||
|
})
|
||||||
|
assert.Equal(t, w.String(), tc.expected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintNextSteps(t *testing.T) {
|
||||||
const header = "\n\x1b[1mWhat's next:\x1b[0m\n"
|
const header = "\n\x1b[1mWhat's next:\x1b[0m\n"
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -34,6 +112,11 @@ func TestPrintHookMessages(t *testing.T) {
|
|||||||
" Foo\n" +
|
" Foo\n" +
|
||||||
" bar\n",
|
" bar\n",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
doc: "preserve empty and indented lines",
|
||||||
|
messages: []string{"", " Details", ""},
|
||||||
|
expectedOutput: header + " \n Details\n \n",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.doc, func(t *testing.T) {
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
@@ -43,7 +44,16 @@ func ParseTemplate(hookTemplate string, cmd *cobra.Command) ([]string, error) {
|
|||||||
if n := strings.Count(out, "\n"); n > maxMessages {
|
if n := strings.Count(out, "\n"); n > maxMessages {
|
||||||
return nil, fmt.Errorf("hook template contains too many messages (%d): maximum is %d", n, maxMessages)
|
return nil, fmt.Errorf("hook template contains too many messages (%d): maximum is %d", n, maxMessages)
|
||||||
}
|
}
|
||||||
return strings.SplitN(out, "\n", maxMessages), nil
|
lines := strings.SplitN(out, "\n", maxMessages)
|
||||||
|
|
||||||
|
// Skip messages containing only whitespace, but preserve empty lines
|
||||||
|
// in messages that have content (e.g., for paragraph spacing).
|
||||||
|
if !slices.ContainsFunc(lines, func(line string) bool {
|
||||||
|
return strings.TrimSpace(line) != ""
|
||||||
|
}) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return lines, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrHookTemplateParse = errors.New("failed to parse hook template")
|
var ErrHookTemplateParse = errors.New("failed to parse hook template")
|
||||||
|
|||||||
@@ -33,7 +33,17 @@ func TestParseTemplate(t *testing.T) {
|
|||||||
{
|
{
|
||||||
doc: "empty template",
|
doc: "empty template",
|
||||||
template: "",
|
template: "",
|
||||||
expectedOutput: []string{""},
|
expectedOutput: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "only empty lines",
|
||||||
|
template: "\n\n",
|
||||||
|
expectedOutput: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "whitespace-only lines",
|
||||||
|
template: " \n\t\n ",
|
||||||
|
expectedOutput: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
doc: "plain message",
|
doc: "plain message",
|
||||||
|
|||||||
@@ -49,11 +49,18 @@ func RunPluginHooks(ctx context.Context, dockerCLI config.Provider, rootCmd, sub
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runHooks(ctx context.Context, cfg *configfile.ConfigFile, rootCmd, subCommand *cobra.Command, invokedCommand string, flags map[string]string, cmdErrorMessage string) {
|
func runHooks(ctx context.Context, cfg *configfile.ConfigFile, rootCmd, subCommand *cobra.Command, invokedCommand string, flags map[string]string, cmdErrorMessage string) {
|
||||||
nextSteps := invokeAndCollectHooks(ctx, cfg, rootCmd, subCommand, invokedCommand, flags, cmdErrorMessage)
|
messages := invokeAndCollectHooks(ctx, cfg, rootCmd, invokedCommand, flags, cmdErrorMessage)
|
||||||
hooks.PrintNextSteps(subCommand.ErrOrStderr(), nextSteps)
|
hooks.PrintMessages(subCommand.ErrOrStderr(), messages, func(message hooks.Response) []string {
|
||||||
|
lines, err := hooks.ParseTemplate(message.Template, subCommand)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Debug("Failed to evaluate hook template")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return lines
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func invokeAndCollectHooks(ctx context.Context, cfg *configfile.ConfigFile, rootCmd, subCmd *cobra.Command, subCmdStr string, flags map[string]string, cmdErrorMessage string) []string {
|
func invokeAndCollectHooks(ctx context.Context, cfg *configfile.ConfigFile, rootCmd *cobra.Command, subCmdStr string, flags map[string]string, cmdErrorMessage string) []hooks.Response {
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -64,17 +71,17 @@ func invokeAndCollectHooks(ctx context.Context, cfg *configfile.ConfigFile, root
|
|||||||
}
|
}
|
||||||
|
|
||||||
pluginDirs := getPluginDirs(cfg)
|
pluginDirs := getPluginDirs(cfg)
|
||||||
nextSteps := make([]string, 0, len(pluginsCfg))
|
var collected []hooks.Response
|
||||||
|
|
||||||
tryInvokeHook := func(pluginName string, pluginCfg map[string]string) (messages []string, ok bool, err error) {
|
tryInvokeHook := func(pluginName string, pluginCfg map[string]string) (hooks.Response, error) {
|
||||||
match, matched := pluginMatch(pluginCfg, subCmdStr, cmdErrorMessage)
|
match, matched := pluginMatch(pluginCfg, subCmdStr, cmdErrorMessage)
|
||||||
if !matched {
|
if !matched {
|
||||||
return nil, false, nil
|
return hooks.Response{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
p, err := getPlugin(pluginName, pluginDirs, rootCmd)
|
p, err := getPlugin(pluginName, pluginDirs, rootCmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return hooks.Response{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := p.RunHook(ctx, hooks.Request{
|
resp, err := p.RunHook(ctx, hooks.Request{
|
||||||
@@ -83,29 +90,23 @@ func invokeAndCollectHooks(ctx context.Context, cfg *configfile.ConfigFile, root
|
|||||||
CommandError: cmdErrorMessage,
|
CommandError: cmdErrorMessage,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return hooks.Response{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var message hooks.Response
|
var message hooks.Response
|
||||||
if err := json.Unmarshal(resp, &message); err != nil {
|
if err := json.Unmarshal(resp, &message); err != nil {
|
||||||
return nil, false, fmt.Errorf("failed to unmarshal hook response (%q): %w", string(resp), err)
|
return hooks.Response{}, fmt.Errorf("failed to unmarshal hook response (%q): %w", string(resp), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// currently the only hook type
|
if message.Type != hooks.NextSteps && message.Type != hooks.GenericMessage {
|
||||||
if message.Type != hooks.NextSteps {
|
return hooks.Response{}, errors.New("unexpected hook response type: " + strconv.Itoa(int(message.Type)))
|
||||||
return nil, false, errors.New("unexpected hook response type: " + strconv.Itoa(int(message.Type)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
messages, err = hooks.ParseTemplate(message.Template, subCmd)
|
return message, nil
|
||||||
if err != nil {
|
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return messages, true, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for pluginName, pluginCfg := range pluginsCfg {
|
for pluginName, pluginCfg := range pluginsCfg {
|
||||||
messages, ok, err := tryInvokeHook(pluginName, pluginCfg)
|
message, err := tryInvokeHook(pluginName, pluginCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// skip misbehaving plugins, but don't halt execution
|
// skip misbehaving plugins, but don't halt execution
|
||||||
logrus.WithFields(logrus.Fields{
|
logrus.WithFields(logrus.Fields{
|
||||||
@@ -114,38 +115,13 @@ func invokeAndCollectHooks(ctx context.Context, cfg *configfile.ConfigFile, root
|
|||||||
}).Debug("Plugin hook invocation failed")
|
}).Debug("Plugin hook invocation failed")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !ok {
|
if message.Template == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var appended bool
|
collected = append(collected, message)
|
||||||
nextSteps, appended = appendNextSteps(nextSteps, messages)
|
|
||||||
if !appended {
|
|
||||||
logrus.WithFields(logrus.Fields{
|
|
||||||
"plugin": pluginName,
|
|
||||||
}).Debug("Plugin responded with an empty hook message; ignoring")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nextSteps
|
return collected
|
||||||
}
|
|
||||||
|
|
||||||
// appendNextSteps appends the processed hook output to the nextSteps slice.
|
|
||||||
// If the processed hook output is empty, it is not appended.
|
|
||||||
// Empty lines are not stripped if there's at least one non-empty line.
|
|
||||||
func appendNextSteps(nextSteps []string, processed []string) ([]string, bool) {
|
|
||||||
empty := true
|
|
||||||
for _, l := range processed {
|
|
||||||
if strings.TrimSpace(l) != "" {
|
|
||||||
empty = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if empty {
|
|
||||||
return nextSteps, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(nextSteps, processed...), true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pluginMatch takes a plugin configuration and a string representing the
|
// pluginMatch takes a plugin configuration and a string representing the
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ package manager
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/config/configfile"
|
"github.com/docker/cli/cli/config/configfile"
|
||||||
@@ -258,38 +263,6 @@ func TestMatchHookConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAppendNextSteps(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
processed []string
|
|
||||||
expectedOut []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
processed: []string{},
|
|
||||||
expectedOut: []string{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
processed: []string{"", ""},
|
|
||||||
expectedOut: []string{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
processed: []string{"Some hint", "", "Some other hint"},
|
|
||||||
expectedOut: []string{"Some hint", "", "Some other hint"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
processed: []string{"Hint 1", "Hint 2"},
|
|
||||||
expectedOut: []string{"Hint 1", "Hint 2"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run("", func(t *testing.T) {
|
|
||||||
got, appended := appendNextSteps([]string{}, tc.processed)
|
|
||||||
assert.Check(t, is.DeepEqual(got, tc.expectedOut))
|
|
||||||
assert.Check(t, is.Equal(appended, len(got) > 0))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunPluginHooksPassesErrorMessage(t *testing.T) {
|
func TestRunPluginHooksPassesErrorMessage(t *testing.T) {
|
||||||
cfg := configfile.New("")
|
cfg := configfile.New("")
|
||||||
cfg.Plugins = map[string]map[string]string{
|
cfg.Plugins = map[string]map[string]string{
|
||||||
@@ -336,7 +309,7 @@ func TestInvokeAndCollectHooksErrorHooksSkippedOnSuccess(t *testing.T) {
|
|||||||
// On success, error-hooks should not match, so the plugin
|
// On success, error-hooks should not match, so the plugin
|
||||||
// binary is never looked up and no results are returned.
|
// binary is never looked up and no results are returned.
|
||||||
result := invokeAndCollectHooks(
|
result := invokeAndCollectHooks(
|
||||||
context.Background(), cfg, root, sub,
|
context.Background(), cfg, root,
|
||||||
"build", map[string]string{}, "",
|
"build", map[string]string{}, "",
|
||||||
)
|
)
|
||||||
assert.Check(t, is.Len(result, 0))
|
assert.Check(t, is.Len(result, 0))
|
||||||
@@ -349,7 +322,7 @@ func TestInvokeAndCollectHooksNoPlugins(t *testing.T) {
|
|||||||
root.AddCommand(sub)
|
root.AddCommand(sub)
|
||||||
|
|
||||||
result := invokeAndCollectHooks(
|
result := invokeAndCollectHooks(
|
||||||
context.Background(), cfg, root, sub,
|
context.Background(), cfg, root,
|
||||||
"build", map[string]string{}, "some error",
|
"build", map[string]string{}, "some error",
|
||||||
)
|
)
|
||||||
assert.Check(t, is.Len(result, 0))
|
assert.Check(t, is.Len(result, 0))
|
||||||
@@ -368,8 +341,108 @@ func TestInvokeAndCollectHooksCancelledContext(t *testing.T) {
|
|||||||
cancel() // cancel immediately
|
cancel() // cancel immediately
|
||||||
|
|
||||||
result := invokeAndCollectHooks(
|
result := invokeAndCollectHooks(
|
||||||
ctx, cfg, root, sub,
|
ctx, cfg, root,
|
||||||
"build", map[string]string{}, "exit status 1",
|
"build", map[string]string{}, "exit status 1",
|
||||||
)
|
)
|
||||||
assert.Check(t, is.Nil(result))
|
assert.Check(t, is.Nil(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunHooksMessages(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("test plugins require a POSIX shell")
|
||||||
|
}
|
||||||
|
|
||||||
|
const header = "\n\x1b[1mWhat's next:\x1b[0m\n"
|
||||||
|
for _, tc := range []struct {
|
||||||
|
doc string
|
||||||
|
responses map[string]string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
doc: "generic message preserves formatting and expands templates",
|
||||||
|
responses: map[string]string{
|
||||||
|
"status": `{"Type":1,"Template":"Status for {{command}}\n\n Details"}`,
|
||||||
|
},
|
||||||
|
expected: "\nStatus for build\n\n Details\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "omitted type defaults to next steps",
|
||||||
|
responses: map[string]string{
|
||||||
|
"suggestion": `{"Template":"Try another command"}`,
|
||||||
|
},
|
||||||
|
expected: header + " Try another command\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "generic messages precede shared next steps header",
|
||||||
|
responses: map[string]string{
|
||||||
|
"statusone": `{"Type":1,"Template":"Session active"}`,
|
||||||
|
"statustwo": `{"Type":1,"Template":"Session active"}`,
|
||||||
|
"suggestionone": `{"Type":0,"Template":"Try another command"}`,
|
||||||
|
"suggestiontwo": `{"Type":0,"Template":"Try another command"}`,
|
||||||
|
},
|
||||||
|
expected: "\nSession active\nSession active\n" + header + " Try another command\n Try another command\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "empty responses produce no output",
|
||||||
|
responses: map[string]string{
|
||||||
|
"status": `{"Type":1,"Template":" \n\t"}`,
|
||||||
|
"suggestion": `{"Type":0,"Template":""}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "empty generic message does not affect next steps",
|
||||||
|
responses: map[string]string{
|
||||||
|
"status": `{"Type":1,"Template":""}`,
|
||||||
|
"suggestion": `{"Type":0,"Template":"Try another command"}`,
|
||||||
|
},
|
||||||
|
expected: header + " Try another command\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "invalid responses do not affect other plugins",
|
||||||
|
responses: map[string]string{
|
||||||
|
"unknown": `{"Type":99,"Template":"Ignored"}`,
|
||||||
|
"invalid": `{"Type":1,"Template":"{{"}`,
|
||||||
|
"status": `{"Type":1,"Template":"Session active"}`,
|
||||||
|
"suggestion": `{"Type":0,"Template":"Try another command"}`,
|
||||||
|
},
|
||||||
|
expected: "\nSession active\n" + header + " Try another command\n",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
|
pluginDir := t.TempDir()
|
||||||
|
cfg := configfile.New("")
|
||||||
|
cfg.CLIPluginsExtraDirs = []string{pluginDir}
|
||||||
|
cfg.Plugins = make(map[string]map[string]string)
|
||||||
|
for name, response := range tc.responses {
|
||||||
|
plugin := fmt.Sprintf(`#!/bin/sh
|
||||||
|
case "$1" in
|
||||||
|
docker-cli-plugin-metadata)
|
||||||
|
printf '%%s\n' '{"SchemaVersion":"0.1.0","Vendor":"test"}'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
test "$2" = docker-cli-plugin-hooks || exit 1
|
||||||
|
printf 'called\n' >> "$0.calls"
|
||||||
|
printf '%%s\n' '%s'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
`, response)
|
||||||
|
assert.NilError(t, os.WriteFile(filepath.Join(pluginDir, "docker-"+name), []byte(plugin), 0o755))
|
||||||
|
cfg.Plugins[name] = map[string]string{"hooks": "build"}
|
||||||
|
}
|
||||||
|
|
||||||
|
root := &cobra.Command{Use: "docker"}
|
||||||
|
sub := &cobra.Command{Use: "build"}
|
||||||
|
root.AddCommand(sub)
|
||||||
|
var output strings.Builder
|
||||||
|
sub.SetErr(&output)
|
||||||
|
|
||||||
|
runHooks(context.Background(), cfg, root, sub, "build", nil, "")
|
||||||
|
assert.Equal(t, output.String(), tc.expected)
|
||||||
|
for name := range tc.responses {
|
||||||
|
calls, err := os.ReadFile(filepath.Join(pluginDir, "docker-"+name+".calls"))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, string(calls), "called\n", "each plugin hook must run exactly once")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -260,8 +260,11 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
|
|||||||
contextDir = tempDir
|
contextDir = tempDir
|
||||||
case build.ContextTypeRemote:
|
case build.ContextTypeRemote:
|
||||||
buildCtx, relDockerfile, err = build.GetContextFromURL(progBuff, options.context, options.dockerfileName)
|
buildCtx, relDockerfile, err = build.GetContextFromURL(progBuff, options.context, options.dockerfileName)
|
||||||
if err != nil && options.quiet {
|
if err != nil {
|
||||||
_, _ = fmt.Fprintln(dockerCli.Err(), progBuff)
|
if options.quiet {
|
||||||
|
_, _ = fmt.Fprintln(dockerCli.Err(), progBuff)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unable to prepare context: path %q not found", options.context)
|
return fmt.Errorf("unable to prepare context: path %q not found", options.context)
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
@@ -172,6 +174,35 @@ RUN echo hello world
|
|||||||
assert.DeepEqual(t, fakeBuild.filenames(t), []string{"Dockerfile"})
|
assert.DeepEqual(t, fakeBuild.filenames(t), []string{"Dockerfile"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestRunBuildRemoteContextDownloadError verifies that remote context
|
||||||
|
// download errors are propagated.
|
||||||
|
//
|
||||||
|
// regression test for https://github.com/docker/cli/issues/7372
|
||||||
|
func TestRunBuildRemoteContextDownloadError(t *testing.T) {
|
||||||
|
t.Setenv("DOCKER_BUILDKIT", "0")
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Error(w, "not found", http.StatusNotFound)
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
imageBuildCalled := false
|
||||||
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
|
imageBuildFunc: func(ctx context.Context, buildContext io.Reader, options client.ImageBuildOptions) (client.ImageBuildResult, error) {
|
||||||
|
imageBuildCalled = true
|
||||||
|
return client.ImageBuildResult{Body: io.NopCloser(bytes.NewReader(nil))}, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
options := newBuildOptions()
|
||||||
|
options.context = server.URL
|
||||||
|
|
||||||
|
err := runBuild(context.Background(), cli, options)
|
||||||
|
|
||||||
|
assert.ErrorContains(t, err, "unable to download remote context")
|
||||||
|
assert.Assert(t, !imageBuildCalled, "ImageBuild should not be called when downloading the remote context fails")
|
||||||
|
}
|
||||||
|
|
||||||
type fakeBuild struct {
|
type fakeBuild struct {
|
||||||
context *tar.Reader
|
context *tar.Reader
|
||||||
options client.ImageBuildOptions
|
options client.ImageBuildOptions
|
||||||
|
|||||||
@@ -571,11 +571,15 @@ func runDocker(ctx context.Context, dockerCli *command.DockerCli) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if !errdefs.IsNotFound(err) {
|
if !errdefs.IsNotFound(err) {
|
||||||
// For plugin not found we fall through to
|
|
||||||
// cmd.Execute() which deals with reporting
|
|
||||||
// "command not found" in a consistent way.
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if args[0] == "offload" {
|
||||||
|
// Report this before cmd.ExecuteContext(): plugin-specific flags such as
|
||||||
|
// --cpus otherwise fail parsing before the root command's RunE.
|
||||||
|
return errors.New("docker: to use 'docker offload', install the plugin.\nSee https://docs.docker.com/go/cloud-flag/")
|
||||||
|
}
|
||||||
|
// Other missing or invalid plugins fall through to cmd.ExecuteContext(),
|
||||||
|
// which reports the standard unknown-command error.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
ARG GO_VERSION=1.26.9
|
ARG GO_VERSION=1.26.10
|
||||||
|
|
||||||
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
||||||
# It must be a supported tag in the docker.io/library/alpine image repository
|
# It must be a supported tag in the docker.io/library/alpine image repository
|
||||||
@@ -10,7 +10,7 @@ ARG ALPINE_VERSION=3.23
|
|||||||
# BUILDX_VERSION sets the version of buildx to install in the dev container.
|
# BUILDX_VERSION sets the version of buildx to install in the dev container.
|
||||||
# It must be a valid tag in the docker.io/docker/buildx-bin image repository
|
# It must be a valid tag in the docker.io/docker/buildx-bin image repository
|
||||||
# on Docker Hub.
|
# on Docker Hub.
|
||||||
ARG BUILDX_VERSION=0.34.1
|
ARG BUILDX_VERSION=0.38.0
|
||||||
FROM docker/buildx-bin:${BUILDX_VERSION} AS buildx
|
FROM docker/buildx-bin:${BUILDX_VERSION} AS buildx
|
||||||
|
|
||||||
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS golang
|
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS golang
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
ARG GO_VERSION=1.26.9
|
ARG GO_VERSION=1.26.10
|
||||||
|
|
||||||
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
||||||
# It must be a supported tag in the docker.io/library/alpine image repository
|
# It must be a supported tag in the docker.io/library/alpine image repository
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
ARG GO_VERSION=1.26.9
|
ARG GO_VERSION=1.26.10
|
||||||
|
|
||||||
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
||||||
# It must be a supported tag in the docker.io/library/alpine image repository
|
# It must be a supported tag in the docker.io/library/alpine image repository
|
||||||
|
|||||||
+6
-6
@@ -24,8 +24,8 @@ require (
|
|||||||
github.com/docker/docker-credential-helpers v0.9.9
|
github.com/docker/docker-credential-helpers v0.9.9
|
||||||
github.com/docker/go-connections v0.8.2
|
github.com/docker/go-connections v0.8.2
|
||||||
github.com/docker/go-units v0.5.0
|
github.com/docker/go-units v0.5.0
|
||||||
github.com/fvbommel/sortorder v1.2.0
|
github.com/fvbommel/sortorder v1.2.1
|
||||||
github.com/go-jose/go-jose/v4 v4.1.5
|
github.com/go-jose/go-jose/v4 v4.1.7
|
||||||
github.com/go-viper/mapstructure/v2 v2.5.0
|
github.com/go-viper/mapstructure/v2 v2.5.0
|
||||||
github.com/gogo/protobuf v1.3.2
|
github.com/gogo/protobuf v1.3.2
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.0
|
||||||
@@ -36,7 +36,7 @@ require (
|
|||||||
github.com/moby/moby/api v1.56.1
|
github.com/moby/moby/api v1.56.1
|
||||||
github.com/moby/moby/client v0.6.2
|
github.com/moby/moby/client v0.6.2
|
||||||
github.com/moby/patternmatcher v0.6.1
|
github.com/moby/patternmatcher v0.6.1
|
||||||
github.com/moby/swarmkit/v2 v2.1.2
|
github.com/moby/swarmkit/v2 v2.1.3
|
||||||
github.com/moby/sys/atomicwriter v0.1.0
|
github.com/moby/sys/atomicwriter v0.1.0
|
||||||
github.com/moby/sys/capability v0.4.0
|
github.com/moby/sys/capability v0.4.0
|
||||||
github.com/moby/sys/sequential v0.7.0
|
github.com/moby/sys/sequential v0.7.0
|
||||||
@@ -60,7 +60,7 @@ require (
|
|||||||
go.opentelemetry.io/otel/sdk v1.46.0
|
go.opentelemetry.io/otel/sdk v1.46.0
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.46.0
|
go.opentelemetry.io/otel/sdk/metric v1.46.0
|
||||||
go.opentelemetry.io/otel/trace v1.46.0
|
go.opentelemetry.io/otel/trace v1.46.0
|
||||||
go.yaml.in/yaml/v3 v3.0.5
|
go.yaml.in/yaml/v3 v3.0.6
|
||||||
golang.org/x/sync v0.23.0
|
golang.org/x/sync v0.23.0
|
||||||
golang.org/x/sys v0.48.0
|
golang.org/x/sys v0.48.0
|
||||||
golang.org/x/term v0.46.0
|
golang.org/x/term v0.46.0
|
||||||
@@ -71,7 +71,7 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20261006220739-8c912ac31dd4 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20261006220739-8c912ac31dd4 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.3-0.20260930231756-f19d9717deb0 // indirect; see https://github.com/microsoft/hcsshim/pull/2545
|
github.com/Microsoft/go-winio v0.6.3 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
@@ -87,7 +87,7 @@ require (
|
|||||||
github.com/gorilla/mux v1.8.1 // indirect
|
github.com/gorilla/mux v1.8.1 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/klauspost/compress v1.20.1 // indirect
|
github.com/klauspost/compress v1.20.2 // indirect
|
||||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
github.com/moby/sys/user v0.4.1 // indirect
|
github.com/moby/sys/user v0.4.1 // indirect
|
||||||
github.com/moby/sys/userns v0.2.1 // indirect
|
github.com/moby/sys/userns v0.2.1 // indirect
|
||||||
|
|||||||
+12
-12
@@ -4,8 +4,8 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8af
|
|||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20261006220739-8c912ac31dd4 h1:m2xcjRABtG288vnlV3U/PKh/HnPURxZp50RKNUwBxM8=
|
github.com/Azure/go-ansiterm v0.0.0-20261006220739-8c912ac31dd4 h1:m2xcjRABtG288vnlV3U/PKh/HnPURxZp50RKNUwBxM8=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20261006220739-8c912ac31dd4/go.mod h1:MTUE7ZuZO89FAtAwozGg6AWD83pcdefnEq5gF82MB8s=
|
github.com/Azure/go-ansiterm v0.0.0-20261006220739-8c912ac31dd4/go.mod h1:MTUE7ZuZO89FAtAwozGg6AWD83pcdefnEq5gF82MB8s=
|
||||||
github.com/Microsoft/go-winio v0.6.3-0.20260930231756-f19d9717deb0 h1:71VU8kW/LCxJkx41+YYys1EgQO+AUnPa1TrxTSyYUCU=
|
github.com/Microsoft/go-winio v0.6.3 h1:M/52tsoyTfwaxajLnXZm7PqkbMGRH8iSWP+8fcg9MQ4=
|
||||||
github.com/Microsoft/go-winio v0.6.3-0.20260930231756-f19d9717deb0/go.mod h1:ma0QpxizD4fyyzJ1C7MIblymTD2Nxz5Mho/3CepwIZg=
|
github.com/Microsoft/go-winio v0.6.3/go.mod h1:ma0QpxizD4fyyzJ1C7MIblymTD2Nxz5Mho/3CepwIZg=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||||
@@ -49,10 +49,10 @@ github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU
|
|||||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||||
github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc=
|
github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc=
|
||||||
github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE=
|
github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE=
|
||||||
github.com/fvbommel/sortorder v1.2.0 h1:TRIiRiGX+djh3Yf4FVxmWmAcYfIr5dH0NbzJWOSAWZk=
|
github.com/fvbommel/sortorder v1.2.1 h1:ASc85RbthwLEqzuiCk7WQQiopfyGWQiEYZVJ3Qa4A+U=
|
||||||
github.com/fvbommel/sortorder v1.2.0/go.mod h1:LbhO04ijZIeUuvz9B9BkI/qYrpZZEn1gWhxv4QjUKVs=
|
github.com/fvbommel/sortorder v1.2.1/go.mod h1:LbhO04ijZIeUuvz9B9BkI/qYrpZZEn1gWhxv4QjUKVs=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.5 h1:RjgjO2LOtWOJKUC5wpwY9LR3B3vwVAz6JS2YHfYU6eA=
|
github.com/go-jose/go-jose/v4 v4.1.7 h1:Nac/98uT5LPkfbiFuYa8ktf62a2VxwQ4xbq1GK8pV5E=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||||
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
@@ -78,8 +78,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
|
|||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ=
|
github.com/klauspost/compress v1.20.2 h1:v5tiZvm1KGLyg5d5oXdXe3lsSPrbEUiKoXGia//I7Qo=
|
||||||
github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
github.com/klauspost/compress v1.20.2/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/mattn/go-runewidth v0.0.31 h1:1oCV4ReTlosDBc6rYRw5cL8S/4y8iv5oRl4SJ5IBgwo=
|
github.com/mattn/go-runewidth v0.0.31 h1:1oCV4ReTlosDBc6rYRw5cL8S/4y8iv5oRl4SJ5IBgwo=
|
||||||
@@ -94,8 +94,8 @@ github.com/moby/moby/client v0.6.2 h1:ekSPlMccrZGcw6Scb8T9D39qE0mB+XupU/yS0zNw9N
|
|||||||
github.com/moby/moby/client v0.6.2/go.mod h1:DGcnO7qmPJrqobx/ggQgQ/AUk+2wgVdQv7uxB0/mEsc=
|
github.com/moby/moby/client v0.6.2/go.mod h1:DGcnO7qmPJrqobx/ggQgQ/AUk+2wgVdQv7uxB0/mEsc=
|
||||||
github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U=
|
github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U=
|
||||||
github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||||
github.com/moby/swarmkit/v2 v2.1.2 h1:1WDZAI6HVYNKdCG4zlXnTAPyLsLwuhRGWlHoOUf5Z6I=
|
github.com/moby/swarmkit/v2 v2.1.3 h1:UcwtwkIRjr75qSgUWCTIuzKUzAXlidxsiqVMnyxSF1w=
|
||||||
github.com/moby/swarmkit/v2 v2.1.2/go.mod h1:GQ6T0ij2oBbWX10OHwpvK449xfXkQMZ8J+B+eQ4mgp4=
|
github.com/moby/swarmkit/v2 v2.1.3/go.mod h1:KhkxT93Hm4wHoNUPJ4JRD384pS7OODFGzJWoDRj9bLE=
|
||||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||||
github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk=
|
github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk=
|
||||||
@@ -180,8 +180,8 @@ go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT7
|
|||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
|
go.yaml.in/yaml/v3 v3.0.6 h1:zcDfvjXEqTQmpjniUBLc91xYJMB2c4QOFxEF/LCEckM=
|
||||||
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
go.yaml.in/yaml/v3 v3.0.6/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
|||||||
+25
-7
@@ -3,8 +3,9 @@
|
|||||||
package winio
|
package winio
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -20,16 +21,33 @@ import (
|
|||||||
//sys setFileCompletionNotificationModes(h windows.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
|
//sys setFileCompletionNotificationModes(h windows.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
|
||||||
//sys wsaGetOverlappedResult(h windows.Handle, o *windows.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
|
//sys wsaGetOverlappedResult(h windows.Handle, o *windows.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
|
||||||
|
|
||||||
var (
|
// ErrFileClosed is returned when an operation is performed on a closed file.
|
||||||
ErrFileClosed = errors.New("file has already been closed")
|
//
|
||||||
ErrTimeout = &timeoutError{}
|
// Deprecated: use [os.ErrClosed] instead.
|
||||||
)
|
var ErrFileClosed = os.ErrClosed
|
||||||
|
|
||||||
|
// ErrTimeout is returned when an I/O operation exceeds its deadline.
|
||||||
|
//
|
||||||
|
// It matches both [context.DeadlineExceeded] and [os.ErrDeadlineExceeded]
|
||||||
|
// with [errors.Is].
|
||||||
|
var ErrTimeout error = &timeoutError{}
|
||||||
|
|
||||||
|
// timeoutError preserves the historical "i/o timeout" error while matching
|
||||||
|
// both context.DeadlineExceeded and os.ErrDeadlineExceeded through errors.Is.
|
||||||
|
// This mirrors the behavior of net.errTimeout:
|
||||||
|
// https://github.com/golang/go/blob/go1.27.1/src/net/net.go
|
||||||
|
//
|
||||||
|
// TODO: consider replacing ErrTimeout with os.ErrDeadlineExceeded if os.ErrDeadlineExceeded is changed to match context.DeadlineExceeded.
|
||||||
type timeoutError struct{}
|
type timeoutError struct{}
|
||||||
|
|
||||||
func (*timeoutError) Error() string { return "i/o timeout" }
|
func (*timeoutError) Error() string { return "i/o timeout" }
|
||||||
func (*timeoutError) Timeout() bool { return true }
|
func (*timeoutError) Timeout() bool { return true }
|
||||||
func (*timeoutError) Temporary() bool { return true }
|
func (*timeoutError) Temporary() bool { return true }
|
||||||
|
func (*timeoutError) Unwrap() error { return os.ErrDeadlineExceeded }
|
||||||
|
|
||||||
|
func (*timeoutError) Is(err error) bool {
|
||||||
|
return err == context.DeadlineExceeded
|
||||||
|
}
|
||||||
|
|
||||||
type timeoutChan chan struct{}
|
type timeoutChan chan struct{}
|
||||||
|
|
||||||
@@ -143,7 +161,7 @@ func (f *win32File) prepareIO() (*ioOperation, error) {
|
|||||||
f.wgLock.RLock()
|
f.wgLock.RLock()
|
||||||
if f.closing.Load() {
|
if f.closing.Load() {
|
||||||
f.wgLock.RUnlock()
|
f.wgLock.RUnlock()
|
||||||
return nil, ErrFileClosed
|
return nil, os.ErrClosed
|
||||||
}
|
}
|
||||||
f.wg.Add(1)
|
f.wg.Add(1)
|
||||||
f.wgLock.RUnlock()
|
f.wgLock.RUnlock()
|
||||||
@@ -192,7 +210,7 @@ func (f *win32File) asyncIO(c *ioOperation, d *deadlineHandler, bytes uint32, er
|
|||||||
err = r.err
|
err = r.err
|
||||||
if err == windows.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno
|
if err == windows.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno
|
||||||
if f.closing.Load() {
|
if f.closing.Load() {
|
||||||
err = ErrFileClosed
|
err = os.ErrClosed
|
||||||
}
|
}
|
||||||
} else if err != nil && f.socket {
|
} else if err != nil && f.socket {
|
||||||
// err is from Win32. Query the overlapped structure to get the winsock error.
|
// err is from Win32. Query the overlapped structure to get the winsock error.
|
||||||
|
|||||||
+1
-1
@@ -441,7 +441,7 @@ func canRedial(err error) bool {
|
|||||||
|
|
||||||
func (conn *HvsockConn) opErr(op string, err error) error {
|
func (conn *HvsockConn) opErr(op string, err error) error {
|
||||||
// translate from "file closed" to "socket closed"
|
// translate from "file closed" to "socket closed"
|
||||||
if errors.Is(err, ErrFileClosed) {
|
if errors.Is(err, os.ErrClosed) {
|
||||||
err = socket.ErrSocketClosed
|
err = socket.ErrSocketClosed
|
||||||
}
|
}
|
||||||
return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err}
|
return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err}
|
||||||
|
|||||||
+11
-12
@@ -102,12 +102,10 @@ func (status ntStatus) Err() error {
|
|||||||
return rtlNtStatusToDosError(status)
|
return rtlNtStatusToDosError(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
// ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed.
|
||||||
// ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed.
|
//
|
||||||
ErrPipeListenerClosed = net.ErrClosed
|
// Deprecated: use [net.ErrClosed] instead.
|
||||||
|
var ErrPipeListenerClosed = net.ErrClosed
|
||||||
errPipeWriteClosed = errors.New("pipe has been closed for write")
|
|
||||||
)
|
|
||||||
|
|
||||||
type win32Pipe struct {
|
type win32Pipe struct {
|
||||||
*win32File
|
*win32File
|
||||||
@@ -144,9 +142,10 @@ func (f *win32Pipe) Disconnect() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CloseWrite closes the write side of a message pipe in byte mode.
|
// CloseWrite closes the write side of a message pipe in byte mode.
|
||||||
|
// It returns [io.ErrClosedPipe] if the write side is already closed.
|
||||||
func (f *win32MessageBytePipe) CloseWrite() error {
|
func (f *win32MessageBytePipe) CloseWrite() error {
|
||||||
if f.writeClosed {
|
if f.writeClosed {
|
||||||
return errPipeWriteClosed
|
return io.ErrClosedPipe
|
||||||
}
|
}
|
||||||
err := f.win32File.Flush()
|
err := f.win32File.Flush()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -161,10 +160,10 @@ func (f *win32MessageBytePipe) CloseWrite() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since
|
// Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since
|
||||||
// they are used to implement CloseWrite().
|
// they are used to implement CloseWrite. It returns [io.ErrClosedPipe] if the write side is closed.
|
||||||
func (f *win32MessageBytePipe) Write(b []byte) (int, error) {
|
func (f *win32MessageBytePipe) Write(b []byte) (int, error) {
|
||||||
if f.writeClosed {
|
if f.writeClosed {
|
||||||
return 0, errPipeWriteClosed
|
return 0, io.ErrClosedPipe
|
||||||
}
|
}
|
||||||
if len(b) == 0 {
|
if len(b) == 0 {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
@@ -454,7 +453,7 @@ func (l *win32PipeListener) makeConnectedServerPipe() (*win32File, error) {
|
|||||||
_ = p.Close()
|
_ = p.Close()
|
||||||
p = nil
|
p = nil
|
||||||
<-ch
|
<-ch
|
||||||
err = ErrPipeListenerClosed
|
err = net.ErrClosed
|
||||||
}
|
}
|
||||||
return p, err
|
return p, err
|
||||||
}
|
}
|
||||||
@@ -479,7 +478,7 @@ func (l *win32PipeListener) listenerRoutine() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
responseCh <- acceptResponse{p, err}
|
responseCh <- acceptResponse{p, err}
|
||||||
closed = err == ErrPipeListenerClosed //nolint:errorlint // err is Errno
|
closed = err == net.ErrClosed //nolint:errorlint // err is Errno
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
windows.Close(l.firstHandle)
|
windows.Close(l.firstHandle)
|
||||||
@@ -571,7 +570,7 @@ func (l *win32PipeListener) Accept() (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
return &win32Pipe{win32File: response.f, path: l.path}, nil
|
return &win32Pipe{win32File: response.f, path: l.path}, nil
|
||||||
case <-l.doneCh:
|
case <-l.doneCh:
|
||||||
return nil, ErrPipeListenerClosed
|
return nil, net.ErrClosed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+33
-3
@@ -408,7 +408,10 @@ func (ctx *genericEncrypter) EncryptWithAuthData(plaintext, aad []byte) (*JSONWe
|
|||||||
(*obj.protected)[k] = makeRawMessage(b)
|
(*obj.protected)[k] = makeRawMessage(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
authData := obj.computeAuthData()
|
authData, err := obj.computeAuthData()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
parts, err := ctx.cipher.encrypt(cek, authData, plaintext)
|
parts, err := ctx.cipher.encrypt(cek, authData, plaintext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -428,6 +431,13 @@ func (ctx *genericEncrypter) Options() EncrypterOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateContentEncryptionKeySize(cek []byte, cipher contentCipher) error {
|
||||||
|
if len(cek) != cipher.keySize() {
|
||||||
|
return ErrInvalidKeySize
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Decrypt and validate the object and return the plaintext. This
|
// Decrypt and validate the object and return the plaintext. This
|
||||||
// function does not support multi-recipient. If you desire multi-recipient
|
// function does not support multi-recipient. If you desire multi-recipient
|
||||||
// decryption use DecryptMulti instead.
|
// decryption use DecryptMulti instead.
|
||||||
@@ -485,7 +495,10 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
|
|||||||
tag: obj.tag,
|
tag: obj.tag,
|
||||||
}
|
}
|
||||||
|
|
||||||
authData := obj.computeAuthData()
|
authData, err := obj.computeAuthData()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var plaintext []byte
|
var plaintext []byte
|
||||||
recipient := obj.recipients[0]
|
recipient := obj.recipients[0]
|
||||||
@@ -495,6 +508,10 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrCryptoFailure
|
return nil, ErrCryptoFailure
|
||||||
}
|
}
|
||||||
|
err = validateContentEncryptionKeySize(cek, cipher)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrCryptoFailure
|
||||||
|
}
|
||||||
|
|
||||||
// Found a valid CEK -- let's try to decrypt.
|
// Found a valid CEK -- let's try to decrypt.
|
||||||
plaintext, err = cipher.decrypt(cek, authData, parts)
|
plaintext, err = cipher.decrypt(cek, authData, parts)
|
||||||
@@ -520,6 +537,12 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
|
|||||||
//
|
//
|
||||||
// Automatically decompresses plaintext, but returns an error if the decompressed
|
// Automatically decompresses plaintext, but returns an error if the decompressed
|
||||||
// data would be >250kB or >3x the size of the compressed data, whichever is larger.
|
// data would be >250kB or >3x the size of the compressed data, whichever is larger.
|
||||||
|
//
|
||||||
|
// Calling DecryptMulti() with PBES2 enabled can result in excessive CPU work.
|
||||||
|
// The PBES2 key unwrapping algorithms are intentionally expensive, and have to
|
||||||
|
// be run for each recipient. To be safe: don't allow PBES2 algorithms in calls
|
||||||
|
// to ParseEncrypted / ParseEncryptedJSON. If your application requires PBES2,
|
||||||
|
// prefer Decrypt(), which will only process single-recipient messages.
|
||||||
func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Header, []byte, error) {
|
func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Header, []byte, error) {
|
||||||
globalHeaders := obj.mergedHeaders(nil)
|
globalHeaders := obj.mergedHeaders(nil)
|
||||||
|
|
||||||
@@ -553,7 +576,10 @@ func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Heade
|
|||||||
tag: obj.tag,
|
tag: obj.tag,
|
||||||
}
|
}
|
||||||
|
|
||||||
authData := obj.computeAuthData()
|
authData, err := obj.computeAuthData()
|
||||||
|
if err != nil {
|
||||||
|
return -1, Header{}, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
index := -1
|
index := -1
|
||||||
var plaintext []byte
|
var plaintext []byte
|
||||||
@@ -572,6 +598,10 @@ func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Heade
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
err = validateContentEncryptionKeySize(cek, cipher)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Found a valid CEK -- let's try to decrypt.
|
// Found a valid CEK -- let's try to decrypt.
|
||||||
plaintext, err = cipher.decrypt(cek, authData, parts)
|
plaintext, err = cipher.decrypt(cek, authData, parts)
|
||||||
|
|||||||
+6
-32
@@ -30,30 +30,6 @@ import (
|
|||||||
"github.com/go-jose/go-jose/v4/json"
|
"github.com/go-jose/go-jose/v4/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Helper function to serialize known-good objects.
|
|
||||||
// Precondition: value is not a nil pointer.
|
|
||||||
func mustSerializeJSON(value interface{}) []byte {
|
|
||||||
out, err := json.Marshal(value)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
// We never want to serialize the top-level value "null," since it's not a
|
|
||||||
// valid JOSE message. But if a caller passes in a nil pointer to this method,
|
|
||||||
// MarshalJSON will happily serialize it as the top-level value "null". If
|
|
||||||
// that value is then embedded in another operation, for instance by being
|
|
||||||
// base64-encoded and fed as input to a signing algorithm
|
|
||||||
// (https://github.com/go-jose/go-jose/issues/22), the result will be
|
|
||||||
// incorrect. Because this method is intended for known-good objects, and a nil
|
|
||||||
// pointer is not a known-good object, we are free to panic in this case.
|
|
||||||
// Note: It's not possible to directly check whether the data pointed at by an
|
|
||||||
// interface is a nil pointer, so we do this hacky workaround.
|
|
||||||
// https://groups.google.com/forum/#!topic/golang-nuts/wnH302gBa4I
|
|
||||||
if string(out) == "null" {
|
|
||||||
panic("Tried to serialize a nil pointer.")
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strip all newlines and whitespace
|
// Strip all newlines and whitespace
|
||||||
func stripWhitespace(data string) string {
|
func stripWhitespace(data string) string {
|
||||||
buf := strings.Builder{}
|
buf := strings.Builder{}
|
||||||
@@ -122,6 +98,8 @@ func inflate(input []byte) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// byteBuffer represents a slice of bytes that can be serialized to url-safe base64.
|
// byteBuffer represents a slice of bytes that can be serialized to url-safe base64.
|
||||||
|
//
|
||||||
|
// A nil byteBuffer represents an empty slice of bytes.
|
||||||
type byteBuffer struct {
|
type byteBuffer struct {
|
||||||
data []byte
|
data []byte
|
||||||
}
|
}
|
||||||
@@ -169,13 +147,13 @@ func (b *byteBuffer) UnmarshalJSON(data []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
*b = *newBuffer(decoded)
|
b.data = decoded
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *byteBuffer) base64() string {
|
func (b *byteBuffer) base64() string {
|
||||||
return base64.RawURLEncoding.EncodeToString(b.data)
|
return base64.RawURLEncoding.EncodeToString(b.bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *byteBuffer) bytes() []byte {
|
func (b *byteBuffer) bytes() []byte {
|
||||||
@@ -186,12 +164,8 @@ func (b *byteBuffer) bytes() []byte {
|
|||||||
return b.data
|
return b.data
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b byteBuffer) bigInt() *big.Int {
|
func (b *byteBuffer) bigInt() *big.Int {
|
||||||
return new(big.Int).SetBytes(b.data)
|
return new(big.Int).SetBytes(b.bytes())
|
||||||
}
|
|
||||||
|
|
||||||
func (b byteBuffer) toInt() int {
|
|
||||||
return int(b.bigInt().Int64())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func base64EncodeLen(sl []byte) int {
|
func base64EncodeLen(sl []byte) int {
|
||||||
|
|||||||
+13
@@ -671,6 +671,19 @@ func (d *decodeState) object(v reflect.Value) {
|
|||||||
for _, i := range f.index {
|
for _, i := range f.index {
|
||||||
if subv.Kind() == reflect.Ptr {
|
if subv.Kind() == reflect.Ptr {
|
||||||
if subv.IsNil() {
|
if subv.IsNil() {
|
||||||
|
// If a struct embeds a pointer to an unexported type,
|
||||||
|
// it is not possible to set a newly allocated value
|
||||||
|
// since the field is unexported.
|
||||||
|
//
|
||||||
|
// See https://golang.org/issue/21357
|
||||||
|
if !subv.CanSet() {
|
||||||
|
d.saveError(fmt.Errorf("json: cannot set embedded pointer to unexported struct: %v", subv.Type().Elem()))
|
||||||
|
// Invalidate subv to ensure d.value(subv) skips over
|
||||||
|
// the JSON value without assigning it to subv.
|
||||||
|
subv = reflect.Value{}
|
||||||
|
destring = false
|
||||||
|
break
|
||||||
|
}
|
||||||
subv.Set(reflect.New(subv.Type().Elem()))
|
subv.Set(reflect.New(subv.Type().Elem()))
|
||||||
}
|
}
|
||||||
subv = subv.Elem()
|
subv = subv.Elem()
|
||||||
|
|||||||
+13
-1
@@ -1025,7 +1025,19 @@ func typeFields(t reflect.Type) []field {
|
|||||||
// Scan f.typ for fields to include.
|
// Scan f.typ for fields to include.
|
||||||
for i := 0; i < f.typ.NumField(); i++ {
|
for i := 0; i < f.typ.NumField(); i++ {
|
||||||
sf := f.typ.Field(i)
|
sf := f.typ.Field(i)
|
||||||
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
if sf.Anonymous {
|
||||||
|
t := sf.Type
|
||||||
|
if t.Kind() == reflect.Pointer {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
if !sf.IsExported() && t.Kind() != reflect.Struct {
|
||||||
|
// Ignore embedded fields of unexported non-struct types.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Do not ignore embedded fields of unexported struct types
|
||||||
|
// since they may have exported fields.
|
||||||
|
} else if !sf.IsExported() {
|
||||||
|
// Ignore unexported non-embedded fields.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
tag := sf.Tag.Get("json")
|
tag := sf.Tag.Get("json")
|
||||||
|
|||||||
+59
-25
@@ -25,6 +25,12 @@ import (
|
|||||||
"github.com/go-jose/go-jose/v4/json"
|
"github.com/go-jose/go-jose/v4/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// MaxRecipients is the maximum number of recipients of a JWE at parse time.
|
||||||
|
// Higher values mean more potential work done across untrusted input.
|
||||||
|
//
|
||||||
|
// If modifying MaxRecipients, do it before any parsing occurs.
|
||||||
|
var MaxRecipients = 32
|
||||||
|
|
||||||
// rawJSONWebEncryption represents a raw JWE JSON object. Used for parsing/serializing.
|
// rawJSONWebEncryption represents a raw JWE JSON object. Used for parsing/serializing.
|
||||||
type rawJSONWebEncryption struct {
|
type rawJSONWebEncryption struct {
|
||||||
Protected *byteBuffer `json:"protected,omitempty"`
|
Protected *byteBuffer `json:"protected,omitempty"`
|
||||||
@@ -84,14 +90,18 @@ func (obj JSONWebEncryption) mergedHeaders(recipient *recipientInfo) rawHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the additional authenticated data from a JWE object.
|
// Get the additional authenticated data from a JWE object.
|
||||||
func (obj JSONWebEncryption) computeAuthData() []byte {
|
func (obj JSONWebEncryption) computeAuthData() ([]byte, error) {
|
||||||
var protected string
|
var protected string
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case obj.original != nil && obj.original.Protected != nil:
|
case obj.original != nil && obj.original.Protected != nil:
|
||||||
protected = obj.original.Protected.base64()
|
protected = obj.original.Protected.base64()
|
||||||
case obj.protected != nil:
|
case obj.protected != nil:
|
||||||
protected = base64.RawURLEncoding.EncodeToString(mustSerializeJSON((obj.protected)))
|
serializedProtected, err := json.Marshal(obj.protected)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
protected = base64.RawURLEncoding.EncodeToString(serializedProtected)
|
||||||
default:
|
default:
|
||||||
protected = ""
|
protected = ""
|
||||||
}
|
}
|
||||||
@@ -102,7 +112,7 @@ func (obj JSONWebEncryption) computeAuthData() []byte {
|
|||||||
output = append(output, []byte(base64.RawURLEncoding.EncodeToString(obj.aad))...)
|
output = append(output, []byte(base64.RawURLEncoding.EncodeToString(obj.aad))...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return output
|
return output, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func containsKeyAlgorithm(haystack []KeyAlgorithm, needle KeyAlgorithm) bool {
|
func containsKeyAlgorithm(haystack []KeyAlgorithm, needle KeyAlgorithm) bool {
|
||||||
@@ -128,11 +138,10 @@ func containsContentEncryption(haystack []ContentEncryption, needle ContentEncry
|
|||||||
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.1
|
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.1
|
||||||
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.2
|
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.2
|
||||||
//
|
//
|
||||||
// The keyAlgorithms and contentEncryption parameters are used to validate the "alg" and "enc"
|
// The keyAlgorithms and contentEncryption parameters contain the allowable algorithms in the "alg" and "enc"
|
||||||
// header parameters respectively. They must be nonempty, and each "alg" or "enc" header in
|
// header parameters respectively. They must be non-empty. Non-matching algorithms cause a parse error.
|
||||||
// parsed data must contain a value that is present in the corresponding parameter. That
|
//
|
||||||
// includes the protected and unprotected headers as well as all recipients. To accept
|
// Deprecated: use ParseEncryptedJSON or ParseEncryptedCompact depending on which serialization you expect.
|
||||||
// multiple algorithms, pass a slice of all the algorithms you want to accept.
|
|
||||||
func ParseEncrypted(input string,
|
func ParseEncrypted(input string,
|
||||||
keyEncryptionAlgorithms []KeyAlgorithm,
|
keyEncryptionAlgorithms []KeyAlgorithm,
|
||||||
contentEncryption []ContentEncryption,
|
contentEncryption []ContentEncryption,
|
||||||
@@ -145,9 +154,13 @@ func ParseEncrypted(input string,
|
|||||||
return ParseEncryptedCompact(input, keyEncryptionAlgorithms, contentEncryption)
|
return ParseEncryptedCompact(input, keyEncryptionAlgorithms, contentEncryption)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseEncryptedJSON parses a message in JWE JSON Serialization.
|
// ParseEncryptedJSON parses a message in JWE JSON Serialization. Messages with
|
||||||
|
// more than [MaxRecipients] recipients are rejected.
|
||||||
//
|
//
|
||||||
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.2
|
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.2
|
||||||
|
//
|
||||||
|
// The keyAlgorithms and contentEncryption parameters contain the allowable algorithms in the "alg" and "enc"
|
||||||
|
// header parameters respectively. They must be non-empty. Any non-allowed algorithms will cause parsing to fail.
|
||||||
func ParseEncryptedJSON(
|
func ParseEncryptedJSON(
|
||||||
input string,
|
input string,
|
||||||
keyEncryptionAlgorithms []KeyAlgorithm,
|
keyEncryptionAlgorithms []KeyAlgorithm,
|
||||||
@@ -177,6 +190,9 @@ func (parsed *rawJSONWebEncryption) sanitized(
|
|||||||
if len(contentEncryption) == 0 {
|
if len(contentEncryption) == 0 {
|
||||||
return nil, errors.New("go-jose/go-jose: no content encryption algorithms provided")
|
return nil, errors.New("go-jose/go-jose: no content encryption algorithms provided")
|
||||||
}
|
}
|
||||||
|
if len(parsed.Recipients) > MaxRecipients {
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: too many recipients in JWE; maximum is %d", MaxRecipients)
|
||||||
|
}
|
||||||
|
|
||||||
obj := &JSONWebEncryption{
|
obj := &JSONWebEncryption{
|
||||||
original: parsed,
|
original: parsed,
|
||||||
@@ -195,21 +211,11 @@ func (parsed *rawJSONWebEncryption) sanitized(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 {
|
protected, err := parseProtectedHeader(parsed.Protected)
|
||||||
err := json.Unmarshal(parsed.Protected.bytes(), &obj.protected)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("go-jose/go-jose: invalid protected header: %s, %s", err, parsed.Protected.base64())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: this must be called _after_ we parse the protected header,
|
|
||||||
// otherwise fields from the protected header will not get picked up.
|
|
||||||
var err error
|
|
||||||
mergedHeaders := obj.mergedHeaders(nil)
|
|
||||||
obj.Header, err = mergedHeaders.sanitized()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("go-jose/go-jose: cannot sanitize merged headers: %v (%v)", err, mergedHeaders)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
obj.protected = protected
|
||||||
|
|
||||||
if len(parsed.Recipients) == 0 {
|
if len(parsed.Recipients) == 0 {
|
||||||
obj.recipients = []recipientInfo{
|
obj.recipients = []recipientInfo{
|
||||||
@@ -264,6 +270,14 @@ func (parsed *rawJSONWebEncryption) sanitized(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: this must be called _after_ we parse the protected header,
|
||||||
|
// otherwise fields from the protected header will not get picked up.
|
||||||
|
mergedHeaders := obj.mergedHeaders(nil)
|
||||||
|
obj.Header, err = mergedHeaders.sanitized()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: cannot sanitize merged headers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
obj.iv = parsed.Iv.bytes()
|
obj.iv = parsed.Iv.bytes()
|
||||||
obj.ciphertext = parsed.Ciphertext.bytes()
|
obj.ciphertext = parsed.Ciphertext.bytes()
|
||||||
obj.tag = parsed.Tag.bytes()
|
obj.tag = parsed.Tag.bytes()
|
||||||
@@ -287,6 +301,9 @@ func validateAlgEnc(headers rawHeader, keyAlgorithms []KeyAlgorithm, contentEncr
|
|||||||
// ParseEncryptedCompact parses a message in JWE Compact Serialization.
|
// ParseEncryptedCompact parses a message in JWE Compact Serialization.
|
||||||
//
|
//
|
||||||
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.1
|
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.1
|
||||||
|
//
|
||||||
|
// The keyAlgorithms and contentEncryption parameters contain the allowable algorithms in the "alg" and "enc"
|
||||||
|
// header parameters respectively. They must be non-empty. Any non-allowed algorithms will cause parsing to fail.
|
||||||
func ParseEncryptedCompact(
|
func ParseEncryptedCompact(
|
||||||
input string,
|
input string,
|
||||||
keyAlgorithms []KeyAlgorithm,
|
keyAlgorithms []KeyAlgorithm,
|
||||||
@@ -354,7 +371,10 @@ func (obj JSONWebEncryption) CompactSerialize() (string, error) {
|
|||||||
return "", ErrNotSupported
|
return "", ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
serializedProtected := mustSerializeJSON(obj.protected)
|
serializedProtected, err := json.Marshal(obj.protected)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
return base64JoinWithDots(
|
return base64JoinWithDots(
|
||||||
serializedProtected,
|
serializedProtected,
|
||||||
@@ -366,7 +386,13 @@ func (obj JSONWebEncryption) CompactSerialize() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FullSerialize serializes an object using the full JSON serialization format.
|
// FullSerialize serializes an object using the full JSON serialization format.
|
||||||
|
//
|
||||||
|
// It returns "" if obj cannot be serialized, for example if it has no recipients.
|
||||||
func (obj JSONWebEncryption) FullSerialize() string {
|
func (obj JSONWebEncryption) FullSerialize() string {
|
||||||
|
if len(obj.recipients) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
raw := rawJSONWebEncryption{
|
raw := rawJSONWebEncryption{
|
||||||
Unprotected: obj.unprotected,
|
Unprotected: obj.unprotected,
|
||||||
Iv: newBuffer(obj.iv),
|
Iv: newBuffer(obj.iv),
|
||||||
@@ -392,8 +418,16 @@ func (obj JSONWebEncryption) FullSerialize() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if obj.protected != nil {
|
if obj.protected != nil {
|
||||||
raw.Protected = newBuffer(mustSerializeJSON(obj.protected))
|
serializedProtected, err := json.Marshal(obj.protected)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
raw.Protected = newBuffer(serializedProtected)
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(mustSerializeJSON(raw))
|
out, err := json.Marshal(raw)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(out)
|
||||||
}
|
}
|
||||||
|
|||||||
+74
-24
@@ -30,6 +30,7 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -177,13 +178,29 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) {
|
|||||||
// UnmarshalJSON reads a key from its JSON representation.
|
// UnmarshalJSON reads a key from its JSON representation.
|
||||||
//
|
//
|
||||||
// Returns ErrUnsupportedKeyType for unrecognized or unsupported "kty" header values.
|
// Returns ErrUnsupportedKeyType for unrecognized or unsupported "kty" header values.
|
||||||
func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
|
//
|
||||||
|
// Directly unmarshaling a JWK is not safe for untrusted input.
|
||||||
|
func (k *JSONWebKey) UnmarshalJSON(data []byte) error {
|
||||||
|
return k.unmarshalJSON(data, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unmarshalJSON reads a key from its JSON representation.
|
||||||
|
//
|
||||||
|
// If allowPrivate is false, a JWK with private key material is rejected and the
|
||||||
|
// function is safe for use against untrusted input.
|
||||||
|
func (k *JSONWebKey) unmarshalJSON(data []byte, allowPrivate bool) (err error) {
|
||||||
var raw rawJSONWebKey
|
var raw rawJSONWebKey
|
||||||
err = json.Unmarshal(data, &raw)
|
err = json.Unmarshal(data, &raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !allowPrivate &&
|
||||||
|
(raw.K != nil || raw.D != nil || raw.P != nil || raw.Q != nil ||
|
||||||
|
raw.Dp != nil || raw.Dq != nil || raw.Qi != nil) {
|
||||||
|
return errors.New("go-jose/go-jose: JWK contains private parameters")
|
||||||
|
}
|
||||||
|
|
||||||
certs, err := parseCertificateChain(raw.X5c)
|
certs, err := parseCertificateChain(raw.X5c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("go-jose/go-jose: failed to unmarshal x5c field: %s", err)
|
return fmt.Errorf("go-jose/go-jose: failed to unmarshal x5c field: %s", err)
|
||||||
@@ -233,7 +250,7 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
|
|||||||
keyPub = key
|
keyPub = key
|
||||||
}
|
}
|
||||||
case "oct":
|
case "oct":
|
||||||
if certPub != nil {
|
if len(certs) > 0 {
|
||||||
return errors.New("go-jose/go-jose: invalid JWK, found 'oct' (symmetric) key with cert chain")
|
return errors.New("go-jose/go-jose: invalid JWK, found 'oct' (symmetric) key with cert chain")
|
||||||
}
|
}
|
||||||
key, err = raw.symmetricKey()
|
key, err = raw.symmetricKey()
|
||||||
@@ -272,10 +289,8 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
|
|||||||
return ErrUnsupportedKeyType
|
return ErrUnsupportedKeyType
|
||||||
}
|
}
|
||||||
|
|
||||||
if certPub != nil && keyPub != nil {
|
if len(certs) > 0 && (certPub == nil || !reflect.DeepEqual(certPub, keyPub)) {
|
||||||
if !reflect.DeepEqual(certPub, keyPub) {
|
return errors.New("go-jose/go-jose: invalid JWK, public keys in key and x5c fields do not match")
|
||||||
return errors.New("go-jose/go-jose: invalid JWK, public keys in key and x5c fields do not match")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*k = JSONWebKey{Key: key, KeyID: raw.Kid, Algorithm: raw.Alg, Use: raw.Use, Certificates: certs}
|
*k = JSONWebKey{Key: key, KeyID: raw.Kid, Algorithm: raw.Alg, Use: raw.Use, Certificates: certs}
|
||||||
@@ -392,6 +407,9 @@ func ecThumbprintInput(curve elliptic.Curve, x, y *big.Int) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func rsaThumbprintInput(n *big.Int, e int) (string, error) {
|
func rsaThumbprintInput(n *big.Int, e int) (string, error) {
|
||||||
|
if n == nil || e <= 0 {
|
||||||
|
return "", errors.New("go-jose/go-jose: invalid RSA key")
|
||||||
|
}
|
||||||
return fmt.Sprintf(rsaThumbprintTemplate,
|
return fmt.Sprintf(rsaThumbprintTemplate,
|
||||||
newBufferFromInt(uint64(e)).base64(),
|
newBufferFromInt(uint64(e)).base64(),
|
||||||
newBuffer(n.Bytes()).base64()), nil
|
newBuffer(n.Bytes()).base64()), nil
|
||||||
@@ -505,14 +523,41 @@ func (k *JSONWebKey) Valid() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MaxRSAModulusBits is the largest RSA modulus size, in bits, that will be
|
||||||
|
// accepted when parsing a JWK or verifying an x5c certificate chain with
|
||||||
|
// [Header.Certificates]. Applications that need larger keys can raise this
|
||||||
|
// limit.
|
||||||
|
//
|
||||||
|
// If modifying MaxRSAModulusBits, do it before any parsing occurs.
|
||||||
|
var MaxRSAModulusBits = 4096
|
||||||
|
|
||||||
func (key rawJSONWebKey) rsaPublicKey() (*rsa.PublicKey, error) {
|
func (key rawJSONWebKey) rsaPublicKey() (*rsa.PublicKey, error) {
|
||||||
if key.N == nil || key.E == nil {
|
return rsaPublicKey(key.N, key.E)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rsaPublicKey(N, E *byteBuffer) (*rsa.PublicKey, error) {
|
||||||
|
if N == nil || E == nil {
|
||||||
return nil, fmt.Errorf("go-jose/go-jose: invalid RSA key, missing n/e values")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid RSA key, missing n/e values")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
n := N.bigInt()
|
||||||
|
if n.BitLen() > MaxRSAModulusBits {
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: invalid RSA key, modulus too large")
|
||||||
|
}
|
||||||
|
|
||||||
|
e := new(big.Int).SetBytes(E.bytes())
|
||||||
|
if !e.IsInt64() {
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: invalid RSA key, bad e value")
|
||||||
|
}
|
||||||
|
|
||||||
|
eInt64 := e.Int64()
|
||||||
|
if eInt64 < 2 || eInt64 > math.MaxInt {
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: invalid RSA key, bad e value")
|
||||||
|
}
|
||||||
|
|
||||||
return &rsa.PublicKey{
|
return &rsa.PublicKey{
|
||||||
N: key.N.bigInt(),
|
N: n,
|
||||||
E: key.E.toInt(),
|
E: int(eInt64),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,11 +601,11 @@ func (key rawJSONWebKey) ecPublicKey() (*ecdsa.PublicKey, error) {
|
|||||||
// The length of this octet string MUST be the full size of a coordinate for
|
// The length of this octet string MUST be the full size of a coordinate for
|
||||||
// the curve specified in the "crv" parameter.
|
// the curve specified in the "crv" parameter.
|
||||||
// https://tools.ietf.org/html/rfc7518#section-6.2.1.2
|
// https://tools.ietf.org/html/rfc7518#section-6.2.1.2
|
||||||
if curveSize(curve) != len(key.X.data) {
|
if curveSize(curve) != len(key.X.bytes()) {
|
||||||
return nil, fmt.Errorf("go-jose/go-jose: invalid EC public key, wrong length for x")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid EC public key, wrong length for x")
|
||||||
}
|
}
|
||||||
|
|
||||||
if curveSize(curve) != len(key.Y.data) {
|
if curveSize(curve) != len(key.Y.bytes()) {
|
||||||
return nil, fmt.Errorf("go-jose/go-jose: invalid EC public key, wrong length for y")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid EC public key, wrong length for y")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -673,16 +718,21 @@ func (key rawJSONWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) {
|
|||||||
return nil, fmt.Errorf("go-jose/go-jose: invalid RSA private key, missing %s value(s)", strings.Join(missing, ", "))
|
return nil, fmt.Errorf("go-jose/go-jose: invalid RSA private key, missing %s value(s)", strings.Join(missing, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pubKey, err := rsaPublicKey(key.N, key.E)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
n := pubKey.N
|
||||||
|
p, q := key.P.bigInt(), key.Q.bigInt()
|
||||||
|
if p.BitLen()+q.BitLen() > n.BitLen()+1 || new(big.Int).Mul(p, q).Cmp(n) != 0 {
|
||||||
|
return nil, errors.New("go-jose/go-jose: invalid RSA private key, p * q != n")
|
||||||
|
}
|
||||||
|
|
||||||
rv := &rsa.PrivateKey{
|
rv := &rsa.PrivateKey{
|
||||||
PublicKey: rsa.PublicKey{
|
PublicKey: *pubKey,
|
||||||
N: key.N.bigInt(),
|
D: key.D.bigInt(),
|
||||||
E: key.E.toInt(),
|
Primes: []*big.Int{p, q},
|
||||||
},
|
|
||||||
D: key.D.bigInt(),
|
|
||||||
Primes: []*big.Int{
|
|
||||||
key.P.bigInt(),
|
|
||||||
key.Q.bigInt(),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if key.Dp != nil {
|
if key.Dp != nil {
|
||||||
@@ -695,7 +745,7 @@ func (key rawJSONWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) {
|
|||||||
rv.Precomputed.Qinv = key.Qi.bigInt()
|
rv.Precomputed.Qinv = key.Qi.bigInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
err := rv.Validate()
|
err = rv.Validate()
|
||||||
return rv, err
|
return rv, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -769,16 +819,16 @@ func (key rawJSONWebKey) ecPrivateKey() (*ecdsa.PrivateKey, error) {
|
|||||||
// The length of this octet string MUST be the full size of a coordinate for
|
// The length of this octet string MUST be the full size of a coordinate for
|
||||||
// the curve specified in the "crv" parameter.
|
// the curve specified in the "crv" parameter.
|
||||||
// https://tools.ietf.org/html/rfc7518#section-6.2.1.2
|
// https://tools.ietf.org/html/rfc7518#section-6.2.1.2
|
||||||
if curveSize(curve) != len(key.X.data) {
|
if curveSize(curve) != len(key.X.bytes()) {
|
||||||
return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, wrong length for x")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, wrong length for x")
|
||||||
}
|
}
|
||||||
|
|
||||||
if curveSize(curve) != len(key.Y.data) {
|
if curveSize(curve) != len(key.Y.bytes()) {
|
||||||
return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, wrong length for y")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, wrong length for y")
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc7518#section-6.2.2.1
|
// https://tools.ietf.org/html/rfc7518#section-6.2.2.1
|
||||||
if dSize(curve) != len(key.D.data) {
|
if dSize(curve) != len(key.D.bytes()) {
|
||||||
return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, wrong length for d")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, wrong length for d")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+79
-32
@@ -27,6 +27,12 @@ import (
|
|||||||
"github.com/go-jose/go-jose/v4/json"
|
"github.com/go-jose/go-jose/v4/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// MaxSignatures is the maximum number of signatures in a JWS at parse time.
|
||||||
|
// Higher values mean more potential work done across untrusted input.
|
||||||
|
//
|
||||||
|
// If modifying MaxSignatures, do it before any parsing occurs.
|
||||||
|
var MaxSignatures = 32
|
||||||
|
|
||||||
// rawJSONWebSignature represents a raw JWS JSON object. Used for parsing/serializing.
|
// rawJSONWebSignature represents a raw JWS JSON object. Used for parsing/serializing.
|
||||||
type rawJSONWebSignature struct {
|
type rawJSONWebSignature struct {
|
||||||
Payload *byteBuffer `json:"payload,omitempty"`
|
Payload *byteBuffer `json:"payload,omitempty"`
|
||||||
@@ -86,6 +92,11 @@ type Signature struct {
|
|||||||
// Verify() after parsing to validate the signature and obtain the payload.
|
// Verify() after parsing to validate the signature and obtain the payload.
|
||||||
//
|
//
|
||||||
// https://datatracker.ietf.org/doc/html/rfc7515#section-7
|
// https://datatracker.ietf.org/doc/html/rfc7515#section-7
|
||||||
|
//
|
||||||
|
// The signatureAlgorithms parameter contains the allowable algorithms in the "alg" header parameter.
|
||||||
|
// It must be non-empty. Non-matching algorithms cause a parse error.
|
||||||
|
//
|
||||||
|
// Deprecated: call ParseSignedJSON or ParseSignedCompact depending on which serialization you expect.
|
||||||
func ParseSigned(
|
func ParseSigned(
|
||||||
signature string,
|
signature string,
|
||||||
signatureAlgorithms []SignatureAlgorithm,
|
signatureAlgorithms []SignatureAlgorithm,
|
||||||
@@ -108,6 +119,9 @@ func ParseSigned(
|
|||||||
// Verify() after parsing to validate the signature and obtain the payload.
|
// Verify() after parsing to validate the signature and obtain the payload.
|
||||||
//
|
//
|
||||||
// https://datatracker.ietf.org/doc/html/rfc7515#section-7.1
|
// https://datatracker.ietf.org/doc/html/rfc7515#section-7.1
|
||||||
|
//
|
||||||
|
// The signatureAlgorithms parameter contains the allowable algorithms in the "alg" header parameter.
|
||||||
|
// It must be non-empty. Non-matching algorithms cause a parse error.
|
||||||
func ParseSignedCompact(
|
func ParseSignedCompact(
|
||||||
signature string,
|
signature string,
|
||||||
signatureAlgorithms []SignatureAlgorithm,
|
signatureAlgorithms []SignatureAlgorithm,
|
||||||
@@ -125,6 +139,9 @@ func ParseSignedCompact(
|
|||||||
// parsing. Call Verify() after parsing to validate the signature and obtain the payload.
|
// parsing. Call Verify() after parsing to validate the signature and obtain the payload.
|
||||||
//
|
//
|
||||||
// https://datatracker.ietf.org/doc/html/rfc7515#appendix-F
|
// https://datatracker.ietf.org/doc/html/rfc7515#appendix-F
|
||||||
|
//
|
||||||
|
// The signatureAlgorithms parameter contains the allowable algorithms in the "alg" header parameter.
|
||||||
|
// It must be non-empty. Any non-allowed algorithms will cause parsing to fail.
|
||||||
func ParseDetached(
|
func ParseDetached(
|
||||||
signature string,
|
signature string,
|
||||||
payload []byte,
|
payload []byte,
|
||||||
@@ -157,7 +174,11 @@ func (obj JSONWebSignature) computeAuthData(payload []byte, signature *Signature
|
|||||||
authData.WriteString(signature.original.Protected.base64())
|
authData.WriteString(signature.original.Protected.base64())
|
||||||
} else if signature.protected != nil {
|
} else if signature.protected != nil {
|
||||||
protectedHeader = signature.protected
|
protectedHeader = signature.protected
|
||||||
authData.WriteString(base64.RawURLEncoding.EncodeToString(mustSerializeJSON(protectedHeader)))
|
serializedProtected, err := json.Marshal(protectedHeader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
authData.WriteString(base64.RawURLEncoding.EncodeToString(serializedProtected))
|
||||||
}
|
}
|
||||||
|
|
||||||
needsBase64 := true
|
needsBase64 := true
|
||||||
@@ -180,9 +201,13 @@ func (obj JSONWebSignature) computeAuthData(payload []byte, signature *Signature
|
|||||||
return authData.Bytes(), nil
|
return authData.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseSignedJSON parses a message in JWS JSON Serialization.
|
// ParseSignedJSON parses a message in JWS JSON Serialization. Messages with
|
||||||
|
// more than [MaxSignatures] signatures are rejected.
|
||||||
//
|
//
|
||||||
// https://datatracker.ietf.org/doc/html/rfc7515#section-7.2
|
// https://datatracker.ietf.org/doc/html/rfc7515#section-7.2
|
||||||
|
//
|
||||||
|
// The signatureAlgorithms parameter contains the allowable algorithms in the "alg" header parameter.
|
||||||
|
// It must be non-empty. Non-matching algorithms cause a parse error.
|
||||||
func ParseSignedJSON(
|
func ParseSignedJSON(
|
||||||
input string,
|
input string,
|
||||||
signatureAlgorithms []SignatureAlgorithm,
|
signatureAlgorithms []SignatureAlgorithm,
|
||||||
@@ -236,6 +261,9 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo
|
|||||||
if parsed.Payload == nil {
|
if parsed.Payload == nil {
|
||||||
return nil, fmt.Errorf("go-jose/go-jose: missing payload in JWS message")
|
return nil, fmt.Errorf("go-jose/go-jose: missing payload in JWS message")
|
||||||
}
|
}
|
||||||
|
if len(parsed.Signatures) > MaxSignatures {
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: too many signatures in JWS; maximum is %d", MaxSignatures)
|
||||||
|
}
|
||||||
|
|
||||||
obj := &JSONWebSignature{
|
obj := &JSONWebSignature{
|
||||||
payload: parsed.Payload.bytes(),
|
payload: parsed.Payload.bytes(),
|
||||||
@@ -245,14 +273,13 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo
|
|||||||
if len(parsed.Signatures) == 0 {
|
if len(parsed.Signatures) == 0 {
|
||||||
// No signatures array, must be flattened serialization
|
// No signatures array, must be flattened serialization
|
||||||
signature := Signature{}
|
signature := Signature{}
|
||||||
if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 {
|
prot, err := parseProtectedHeader(parsed.Protected)
|
||||||
signature.protected = &rawHeader{}
|
if err != nil {
|
||||||
err := json.Unmarshal(parsed.Protected.bytes(), signature.protected)
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signature.protected = prot
|
||||||
|
|
||||||
// Check that there is not a nonce in the unprotected header
|
// Check that there is not a nonce in the unprotected header
|
||||||
if parsed.Header != nil && parsed.Header.getNonce() != "" {
|
if parsed.Header != nil && parsed.Header.getNonce() != "" {
|
||||||
return nil, ErrUnprotectedNonce
|
return nil, ErrUnprotectedNonce
|
||||||
@@ -275,17 +302,17 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo
|
|||||||
Signature: parsed.Signature,
|
Signature: parsed.Signature,
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
mergedHeaders := signature.mergedHeaders()
|
||||||
signature.Header, err = signature.mergedHeaders().sanitized()
|
alg := mergedHeaders.getSignatureAlgorithm()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
alg := SignatureAlgorithm(signature.Header.Algorithm)
|
|
||||||
if !containsSignatureAlgorithm(signatureAlgorithms, alg) {
|
if !containsSignatureAlgorithm(signatureAlgorithms, alg) {
|
||||||
return nil, newErrUnexpectedSignatureAlgorithm(alg, signatureAlgorithms)
|
return nil, newErrUnexpectedSignatureAlgorithm(alg, signatureAlgorithms)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signature.Header, err = mergedHeaders.sanitized()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if signature.header != nil {
|
if signature.header != nil {
|
||||||
signature.Unprotected, err = signature.header.sanitized()
|
signature.Unprotected, err = signature.header.sanitized()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -310,30 +337,31 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, sig := range parsed.Signatures {
|
for i, sig := range parsed.Signatures {
|
||||||
if sig.Protected != nil && len(sig.Protected.bytes()) > 0 {
|
prot, err := parseProtectedHeader(sig.Protected)
|
||||||
obj.Signatures[i].protected = &rawHeader{}
|
if err != nil {
|
||||||
err := json.Unmarshal(sig.Protected.bytes(), obj.Signatures[i].protected)
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
obj.Signatures[i].protected = prot
|
||||||
|
|
||||||
// Check that there is not a nonce in the unprotected header
|
// Check that there is not a nonce in the unprotected header
|
||||||
if sig.Header != nil && sig.Header.getNonce() != "" {
|
if sig.Header != nil && sig.Header.getNonce() != "" {
|
||||||
return nil, ErrUnprotectedNonce
|
return nil, ErrUnprotectedNonce
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
obj.Signatures[i].header = sig.Header
|
||||||
obj.Signatures[i].Header, err = obj.Signatures[i].mergedHeaders().sanitized()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
alg := SignatureAlgorithm(obj.Signatures[i].Header.Algorithm)
|
mergedHeaders := obj.Signatures[i].mergedHeaders()
|
||||||
|
alg := mergedHeaders.getSignatureAlgorithm()
|
||||||
if !containsSignatureAlgorithm(signatureAlgorithms, alg) {
|
if !containsSignatureAlgorithm(signatureAlgorithms, alg) {
|
||||||
return nil, newErrUnexpectedSignatureAlgorithm(alg, signatureAlgorithms)
|
return nil, newErrUnexpectedSignatureAlgorithm(alg, signatureAlgorithms)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
obj.Signatures[i].Header, err = mergedHeaders.sanitized()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if obj.Signatures[i].header != nil {
|
if obj.Signatures[i].header != nil {
|
||||||
obj.Signatures[i].Unprotected, err = obj.Signatures[i].header.sanitized()
|
obj.Signatures[i].Unprotected, err = obj.Signatures[i].header.sanitized()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -359,7 +387,6 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo
|
|||||||
// Copy value of sig
|
// Copy value of sig
|
||||||
original := sig
|
original := sig
|
||||||
|
|
||||||
obj.Signatures[i].header = sig.Header
|
|
||||||
obj.Signatures[i].original = &original
|
obj.Signatures[i].original = &original
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,7 +451,10 @@ func (obj JSONWebSignature) compactSerialize(detached bool) (string, error) {
|
|||||||
return "", ErrNotSupported
|
return "", ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
serializedProtected := mustSerializeJSON(obj.Signatures[0].protected)
|
serializedProtected, err := json.Marshal(obj.Signatures[0].protected)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
var payload []byte
|
var payload []byte
|
||||||
if !detached {
|
if !detached {
|
||||||
@@ -449,14 +479,23 @@ func (obj JSONWebSignature) DetachedCompactSerialize() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FullSerialize serializes an object using the full JSON serialization format.
|
// FullSerialize serializes an object using the full JSON serialization format.
|
||||||
|
//
|
||||||
|
// It returns "" if obj cannot be serialized.
|
||||||
func (obj JSONWebSignature) FullSerialize() string {
|
func (obj JSONWebSignature) FullSerialize() string {
|
||||||
raw := rawJSONWebSignature{
|
raw := rawJSONWebSignature{
|
||||||
Payload: newBuffer(obj.payload),
|
Payload: newBuffer(obj.payload),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(obj.Signatures) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
if len(obj.Signatures) == 1 {
|
if len(obj.Signatures) == 1 {
|
||||||
if obj.Signatures[0].protected != nil {
|
if obj.Signatures[0].protected != nil {
|
||||||
serializedProtected := mustSerializeJSON(obj.Signatures[0].protected)
|
serializedProtected, err := json.Marshal(obj.Signatures[0].protected)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
raw.Protected = newBuffer(serializedProtected)
|
raw.Protected = newBuffer(serializedProtected)
|
||||||
}
|
}
|
||||||
raw.Header = obj.Signatures[0].header
|
raw.Header = obj.Signatures[0].header
|
||||||
@@ -470,10 +509,18 @@ func (obj JSONWebSignature) FullSerialize() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if signature.protected != nil {
|
if signature.protected != nil {
|
||||||
raw.Signatures[i].Protected = newBuffer(mustSerializeJSON(signature.protected))
|
serializedProtected, err := json.Marshal(signature.protected)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
raw.Signatures[i].Protected = newBuffer(serializedProtected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(mustSerializeJSON(raw))
|
out, err := json.Marshal(raw)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(out)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -47,7 +47,7 @@ var ErrNotValidYet = errors.New("go-jose/go-jose/jwt: validation failed, token n
|
|||||||
var ErrExpired = errors.New("go-jose/go-jose/jwt: validation failed, token is expired (exp)")
|
var ErrExpired = errors.New("go-jose/go-jose/jwt: validation failed, token is expired (exp)")
|
||||||
|
|
||||||
// ErrIssuedInTheFuture indicates that the iat field is in the future.
|
// ErrIssuedInTheFuture indicates that the iat field is in the future.
|
||||||
var ErrIssuedInTheFuture = errors.New("go-jose/go-jose/jwt: validation field, token issued in the future (iat)")
|
var ErrIssuedInTheFuture = errors.New("go-jose/go-jose/jwt: validation failed, token issued in the future (iat)")
|
||||||
|
|
||||||
// ErrInvalidContentType indicates that token requires JWT cty header.
|
// ErrInvalidContentType indicates that token requires JWT cty header.
|
||||||
var ErrInvalidContentType = errors.New("go-jose/go-jose/jwt: expected content type to be JWT (cty header)")
|
var ErrInvalidContentType = errors.New("go-jose/go-jose/jwt: expected content type to be JWT (cty header)")
|
||||||
|
|||||||
+12
-17
@@ -85,7 +85,12 @@ func (t *NestedJSONWebToken) Decrypt(decryptionKey interface{}) (*JSONWebToken,
|
|||||||
return sig, nil
|
return sig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseSigned parses token from JWS form.
|
// ParseSigned parses a JWT in the JWS Compact Serialization.
|
||||||
|
//
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc7515#section-3.1
|
||||||
|
//
|
||||||
|
// The signatureAlgorithms parameter contains the allowable algorithms in the "alg" header parameter.
|
||||||
|
// It must be non-empty. Non-matching algorithms cause a parse error.
|
||||||
func ParseSigned(s string, signatureAlgorithms []jose.SignatureAlgorithm) (*JSONWebToken, error) {
|
func ParseSigned(s string, signatureAlgorithms []jose.SignatureAlgorithm) (*JSONWebToken, error) {
|
||||||
sig, err := jose.ParseSignedCompact(s, signatureAlgorithms)
|
sig, err := jose.ParseSignedCompact(s, signatureAlgorithms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -142,13 +147,10 @@ func parseEncryptedCompact(
|
|||||||
return enc, nil
|
return enc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseEncrypted parses token from JWE form.
|
// ParseEncrypted parses a JWT in the JWE Compact Serialization.
|
||||||
//
|
//
|
||||||
// The keyAlgorithms and contentEncryption parameters are used to validate the "alg" and "enc"
|
// The keyAlgorithms and contentEncryption parameters contain the allowable algorithms in the "alg" and "enc"
|
||||||
// header parameters respectively. They must be nonempty, and each "alg" or "enc" header in
|
// header parameters respectively. They must be non-empty. Non-matching algorithms cause a parse error.
|
||||||
// parsed data must contain a value that is present in the corresponding parameter. That
|
|
||||||
// includes the protected and unprotected headers as well as all recipients. To accept
|
|
||||||
// multiple algorithms, pass a slice of all the algorithms you want to accept.
|
|
||||||
func ParseEncrypted(s string,
|
func ParseEncrypted(s string,
|
||||||
keyAlgorithms []jose.KeyAlgorithm,
|
keyAlgorithms []jose.KeyAlgorithm,
|
||||||
contentEncryption []jose.ContentEncryption,
|
contentEncryption []jose.ContentEncryption,
|
||||||
@@ -164,17 +166,10 @@ func ParseEncrypted(s string,
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseSignedAndEncrypted parses signed-then-encrypted token from JWE form.
|
// ParseSignedAndEncrypted parses a signed-then-encrypted token in the JWE Compact Serialization.
|
||||||
//
|
//
|
||||||
// The encryptionKeyAlgorithms and contentEncryption parameters are used to validate the "alg" and "enc"
|
// The encryptionKeyAlgorithms, contentEncryption, and signatureAlgorithms parameters contain the allowable
|
||||||
// header parameters, respectively, of the outer JWE. They must be nonempty, and each "alg" or "enc"
|
// algorithms. They must be non-empty. Non-matching algorithms cause a parse error.
|
||||||
// header in parsed data must contain a value that is present in the corresponding parameter. That
|
|
||||||
// includes the protected and unprotected headers as well as all recipients. To accept
|
|
||||||
// multiple algorithms, pass a slice of all the algorithms you want to accept.
|
|
||||||
//
|
|
||||||
// The signatureAlgorithms parameter is used to validate the "alg" header parameter of the
|
|
||||||
// inner JWS. It must be nonempty, and the "alg" header in the inner JWS must contain a value
|
|
||||||
// that is present in the parameter.
|
|
||||||
func ParseSignedAndEncrypted(s string,
|
func ParseSignedAndEncrypted(s string,
|
||||||
encryptionKeyAlgorithms []jose.KeyAlgorithm,
|
encryptionKeyAlgorithms []jose.KeyAlgorithm,
|
||||||
contentEncryption []jose.ContentEncryption,
|
contentEncryption []jose.ContentEncryption,
|
||||||
|
|||||||
+43
-37
@@ -18,6 +18,7 @@ package jose
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -210,13 +211,20 @@ type Header struct {
|
|||||||
|
|
||||||
// Certificates verifies & returns the certificate chain present
|
// Certificates verifies & returns the certificate chain present
|
||||||
// in the x5c header field of a message, if one was present. Returns
|
// in the x5c header field of a message, if one was present. Returns
|
||||||
// an error if there was no x5c header present or the chain could
|
// an error if there was no x5c header present, any certificate in the
|
||||||
|
// chain has an RSA key larger than [MaxRSAModulusBits], or the chain could
|
||||||
// not be validated with the given verify options.
|
// not be validated with the given verify options.
|
||||||
func (h Header) Certificates(opts x509.VerifyOptions) ([][]*x509.Certificate, error) {
|
func (h Header) Certificates(opts x509.VerifyOptions) ([][]*x509.Certificate, error) {
|
||||||
if len(h.certificates) == 0 {
|
if len(h.certificates) == 0 {
|
||||||
return nil, ErrMissingX5cHeader
|
return nil, ErrMissingX5cHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, cert := range h.certificates {
|
||||||
|
if pub, ok := cert.PublicKey.(*rsa.PublicKey); ok && pub.N.BitLen() > MaxRSAModulusBits {
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: invalid RSA key in x5c certificate, modulus too large")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
leaf := h.certificates[0]
|
leaf := h.certificates[0]
|
||||||
if opts.Intermediates == nil {
|
if opts.Intermediates == nil {
|
||||||
opts.Intermediates = x509.NewCertPool()
|
opts.Intermediates = x509.NewCertPool()
|
||||||
@@ -297,9 +305,10 @@ func (parsed rawHeader) getEPK() (*JSONWebKey, error) {
|
|||||||
if v == nil {
|
if v == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
var epk *JSONWebKey
|
// RFC 7518 Section 4.6.1.1: the "epk" header is the ephemeral public key
|
||||||
err := json.Unmarshal(*v, &epk)
|
// of the originator. Reject private key material before parsing it.
|
||||||
if err != nil {
|
epk := new(JSONWebKey)
|
||||||
|
if err := epk.unmarshalJSON(*v, false); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return epk, nil
|
return epk, nil
|
||||||
@@ -325,20 +334,6 @@ func (parsed rawHeader) getTag() (*byteBuffer, error) {
|
|||||||
return parsed.getByteBuffer(headerTag)
|
return parsed.getByteBuffer(headerTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getJWK extracts parsed "jwk" from the raw JSON.
|
|
||||||
func (parsed rawHeader) getJWK() (*JSONWebKey, error) {
|
|
||||||
v := parsed[headerJWK]
|
|
||||||
if v == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
var jwk *JSONWebKey
|
|
||||||
err := json.Unmarshal(*v, &jwk)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return jwk, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCritical extracts parsed "crit" from the raw JSON. If omitted, it
|
// getCritical extracts parsed "crit" from the raw JSON. If omitted, it
|
||||||
// returns an empty slice.
|
// returns an empty slice.
|
||||||
func (parsed rawHeader) getCritical() ([]string, error) {
|
func (parsed rawHeader) getCritical() ([]string, error) {
|
||||||
@@ -424,10 +419,14 @@ func (parsed rawHeader) sanitized() (h Header, err error) {
|
|||||||
}
|
}
|
||||||
switch k {
|
switch k {
|
||||||
case headerJWK:
|
case headerJWK:
|
||||||
var jwk *JSONWebKey
|
// RFC 7515 Section 4.1.3 and RFC 7516 Section 4.1.5: the "jwk"
|
||||||
err = json.Unmarshal(*v, &jwk)
|
// header is the public key that corresponds to the key used to
|
||||||
|
// sign the JWS or encrypt the JWE. Reject private key material
|
||||||
|
// before parsing it.
|
||||||
|
jwk := new(JSONWebKey)
|
||||||
|
err = jwk.unmarshalJSON(*v, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("failed to unmarshal JWK: %v: %#v", err, string(*v))
|
err = fmt.Errorf("failed to unmarshal JWK: %w", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
h.JSONWebKey = jwk
|
h.JSONWebKey = jwk
|
||||||
@@ -499,22 +498,8 @@ func parseCertificateChain(chain []string) ([]*x509.Certificate, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (parsed rawHeader) isSet(k HeaderKey) bool {
|
func (parsed rawHeader) isSet(k HeaderKey) bool {
|
||||||
dvr := parsed[k]
|
_, ok := parsed[k]
|
||||||
if dvr == nil {
|
return ok
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var dv interface{}
|
|
||||||
err := json.Unmarshal(*dvr, &dv)
|
|
||||||
if err != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if dvStr, ok := dv.(string); ok {
|
|
||||||
return dvStr != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge headers from src into dst, giving precedence to headers from l.
|
// Merge headers from src into dst, giving precedence to headers from l.
|
||||||
@@ -564,3 +549,24 @@ func makeRawMessage(b []byte) *json.RawMessage {
|
|||||||
rm := json.RawMessage(b)
|
rm := json.RawMessage(b)
|
||||||
return &rm
|
return &rm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseProtectedHeader parses a `byteBuffer` into a `*rawHeader`.
|
||||||
|
//
|
||||||
|
// The byteBuffer represents a base64-decoded "protected" header, which
|
||||||
|
// is specified to contain a JSON object.
|
||||||
|
//
|
||||||
|
// If the input is nil or empty, returns `nil, nil`.
|
||||||
|
//
|
||||||
|
// `rawHeader` is a map type and so can be nil. When this function
|
||||||
|
// returns a non-nil `*rawHeader`, the map is guaranteed to be non-nil.
|
||||||
|
func parseProtectedHeader(in *byteBuffer) (*rawHeader, error) {
|
||||||
|
if in == nil || len(in.bytes()) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var header rawHeader
|
||||||
|
err := json.Unmarshal(in.bytes(), &header)
|
||||||
|
if err != nil || header == nil {
|
||||||
|
return nil, errors.New("go-jose/go-jose: invalid protected header")
|
||||||
|
}
|
||||||
|
return &header, nil
|
||||||
|
}
|
||||||
|
|||||||
+4
-1
@@ -321,7 +321,10 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
|
|||||||
protected[k] = v
|
protected[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
serializedProtected := mustSerializeJSON(protected)
|
serializedProtected, err := json.Marshal(protected)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: Error marshaling JSON: %s", err)
|
||||||
|
}
|
||||||
needsBase64 := true
|
needsBase64 := true
|
||||||
|
|
||||||
if b64, ok := protected[headerB64]; ok {
|
if b64, ok := protected[headerB64]; ok {
|
||||||
|
|||||||
+1
-1
@@ -421,7 +421,7 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("go-jose/go-jose: invalid P2S: %v", err)
|
return nil, fmt.Errorf("go-jose/go-jose: invalid P2S: %v", err)
|
||||||
}
|
}
|
||||||
if p2s == nil || len(p2s.data) == 0 {
|
if p2s == nil || len(p2s.bytes()) == 0 {
|
||||||
return nil, fmt.Errorf("go-jose/go-jose: invalid P2S: must be present")
|
return nil, fmt.Errorf("go-jose/go-jose: invalid P2S: must be present")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
# This containerfile can be used to run the go-jose tests and cli
|
||||||
|
|
||||||
|
FROM golang:1.27-trixie
|
||||||
|
ENV GOTOOLCHAIN=local
|
||||||
|
|
||||||
|
COPY . /src
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
# Install the jose-util cli, which will also build the library
|
||||||
|
RUN go build -o /usr/local/bin/jose-util ./jose-util
|
||||||
|
|
||||||
|
# By default, run the tests
|
||||||
|
CMD ["go", "test", "./..."]
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
# Threat model
|
||||||
|
|
||||||
|
## What this project does and where untrusted input enters
|
||||||
|
go-jose is a Go implementation of JOSE family of algorithms.
|
||||||
|
|
||||||
|
It is a library which will parse attacker-controlled data in network-reachable
|
||||||
|
services.
|
||||||
|
|
||||||
|
## Components that matter most / least
|
||||||
|
The primary attack surface of interest is parsing and validating JWS, JWE, JWK,
|
||||||
|
etc when this library is used to implement authentication and authorization.
|
||||||
|
|
||||||
|
## How to exercise it
|
||||||
|
Testing is primarily done by Golang unit tests, which can be run with the
|
||||||
|
tests.Container file's default CMD.
|
||||||
|
|
||||||
|
## How you rate severity
|
||||||
|
Any authentication bypass enabled by a go-jose issue is a high+ severity.
|
||||||
|
|
||||||
|
Denial-of-service attacks are of interest when the functions may be used in a protocol like OIDC, ACME, etc
|
||||||
|
which could have a server processing attacker-controlled data.
|
||||||
|
|
||||||
|
## Anything to leave alone
|
||||||
|
Any bug which only affects the jose-util CLI tool, but it can be used to exercise the library.
|
||||||
|
|
||||||
+2
-1
@@ -659,7 +659,8 @@ func (s *Scratch) validateNorm() (err error) {
|
|||||||
if v >= 0 {
|
if v >= 0 {
|
||||||
total += int(v)
|
total += int(v)
|
||||||
} else {
|
} else {
|
||||||
total -= int(v)
|
// -1 takes one cell. Adding -v would let a wrapped -32768 pass.
|
||||||
|
total++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|||||||
+6
@@ -3,6 +3,7 @@ package fse
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -123,6 +124,11 @@ func (s *Scratch) readNCount() error {
|
|||||||
remaining -= count
|
remaining -= count
|
||||||
gotTotal += count
|
gotTotal += count
|
||||||
}
|
}
|
||||||
|
if count > math.MaxInt16 {
|
||||||
|
// Only one symbol filling a tableLog 15 table, which cannot be decoded.
|
||||||
|
// Truncated, it would leave the decoding table unwritten.
|
||||||
|
return fmt.Errorf("corruption detected (count %d > %d)", count, math.MaxInt16)
|
||||||
|
}
|
||||||
s.norm[charnum&0xff] = int16(count)
|
s.norm[charnum&0xff] = int16(count)
|
||||||
charnum++
|
charnum++
|
||||||
previous0 = count == 0
|
previous0 = count == 0
|
||||||
|
|||||||
+4
@@ -54,6 +54,10 @@ func ReadTable(in []byte, s *Scratch) (s2 *Scratch, remain []byte, err error) {
|
|||||||
if len(in) < int(iSize) {
|
if len(in) < int(iSize) {
|
||||||
return s, nil, fmt.Errorf("input too small for table, want %d bytes, have %d", iSize, len(in))
|
return s, nil, fmt.Errorf("input too small for table, want %d bytes, have %d", iSize, len(in))
|
||||||
}
|
}
|
||||||
|
// The low nibble is the weights' FSE tableLog - 5, which the format limits to 6.
|
||||||
|
if in[0]&15+5 > 6 {
|
||||||
|
return s, nil, errors.New("corrupt input: weights tableLog too big")
|
||||||
|
}
|
||||||
// FSE compressed weights
|
// FSE compressed weights
|
||||||
s.fse.DecompressLimit = 255
|
s.fse.DecompressLimit = 255
|
||||||
hw := s.huffWeight[:]
|
hw := s.huffWeight[:]
|
||||||
|
|||||||
+3
@@ -219,6 +219,9 @@ func BuildDict(o BuildDictOptions) ([]byte, error) {
|
|||||||
if len(contents) == 0 {
|
if len(contents) == 0 {
|
||||||
return nil, errors.New("no content provided")
|
return nil, errors.New("no content provided")
|
||||||
}
|
}
|
||||||
|
if o.Level < speedNotSet || o.Level >= speedLast {
|
||||||
|
return nil, fmt.Errorf("unknown encoder level %d", o.Level)
|
||||||
|
}
|
||||||
d := dict{
|
d := dict{
|
||||||
id: o.ID,
|
id: o.ID,
|
||||||
litEnc: nil,
|
litEnc: nil,
|
||||||
|
|||||||
+12
@@ -69,6 +69,10 @@ var (
|
|||||||
// No CRC value is being generated and not all CRC values of the Snappy stream are checked.
|
// No CRC value is being generated and not all CRC values of the Snappy stream are checked.
|
||||||
// However, it provides really fast recompression of Snappy streams.
|
// However, it provides really fast recompression of Snappy streams.
|
||||||
// The converter can be reused to avoid allocations, even after errors.
|
// The converter can be reused to avoid allocations, even after errors.
|
||||||
|
//
|
||||||
|
// Deprecated: Decode the stream with s2.NewReader, which reads Snappy streams,
|
||||||
|
// and compress the output with an Encoder. This validates the input and
|
||||||
|
// compresses much better.
|
||||||
type SnappyConverter struct {
|
type SnappyConverter struct {
|
||||||
r io.Reader
|
r io.Reader
|
||||||
err error
|
err error
|
||||||
@@ -373,6 +377,14 @@ func decodeSnappy(blk *blockEnc, src []byte) error {
|
|||||||
offset = uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24
|
offset = uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A zstd match is at least zstdMinMatch bytes. A shorter copy
|
||||||
|
// cannot be represented as a sequence and would underflow the
|
||||||
|
// matchLen computation below, producing an out-of-range match
|
||||||
|
// length code.
|
||||||
|
if length < zstdMinMatch {
|
||||||
|
return ErrSnappyCorrupt
|
||||||
|
}
|
||||||
|
|
||||||
if offset <= 0 || blk.size+lits < int(offset) /*|| length > len(blk)-d */ {
|
if offset <= 0 || blk.size+lits < int(offset) /*|| length > len(blk)-d */ {
|
||||||
println("offset <= 0 || blk.size+lits < int(offset)", offset, blk.size+lits, int(offset), blk.size, lits)
|
println("offset <= 0 || blk.size+lits < int(offset)", offset, blk.size+lits, int(offset), blk.size, lits)
|
||||||
|
|
||||||
|
|||||||
+60
-9
@@ -22,7 +22,7 @@ file {
|
|||||||
java_package: "com.google.protobuf"
|
java_package: "com.google.protobuf"
|
||||||
java_outer_classname: "TimestampProto"
|
java_outer_classname: "TimestampProto"
|
||||||
java_multiple_files: true
|
java_multiple_files: true
|
||||||
go_package: "github.com/golang/protobuf/ptypes/timestamp"
|
go_package: "google.golang.org/protobuf/types/known/timestamppb"
|
||||||
cc_enable_arenas: true
|
cc_enable_arenas: true
|
||||||
objc_class_prefix: "GPB"
|
objc_class_prefix: "GPB"
|
||||||
csharp_namespace: "Google.Protobuf.WellKnownTypes"
|
csharp_namespace: "Google.Protobuf.WellKnownTypes"
|
||||||
@@ -53,7 +53,7 @@ file {
|
|||||||
java_package: "com.google.protobuf"
|
java_package: "com.google.protobuf"
|
||||||
java_outer_classname: "DurationProto"
|
java_outer_classname: "DurationProto"
|
||||||
java_multiple_files: true
|
java_multiple_files: true
|
||||||
go_package: "github.com/golang/protobuf/ptypes/duration"
|
go_package: "google.golang.org/protobuf/types/known/durationpb"
|
||||||
cc_enable_arenas: true
|
cc_enable_arenas: true
|
||||||
objc_class_prefix: "GPB"
|
objc_class_prefix: "GPB"
|
||||||
csharp_namespace: "Google.Protobuf.WellKnownTypes"
|
csharp_namespace: "Google.Protobuf.WellKnownTypes"
|
||||||
@@ -157,7 +157,7 @@ file {
|
|||||||
java_package: "com.google.protobuf"
|
java_package: "com.google.protobuf"
|
||||||
java_outer_classname: "WrappersProto"
|
java_outer_classname: "WrappersProto"
|
||||||
java_multiple_files: true
|
java_multiple_files: true
|
||||||
go_package: "github.com/golang/protobuf/ptypes/wrappers"
|
go_package: "google.golang.org/protobuf/types/known/wrapperspb"
|
||||||
cc_enable_arenas: true
|
cc_enable_arenas: true
|
||||||
objc_class_prefix: "GPB"
|
objc_class_prefix: "GPB"
|
||||||
csharp_namespace: "Google.Protobuf.WellKnownTypes"
|
csharp_namespace: "Google.Protobuf.WellKnownTypes"
|
||||||
@@ -484,6 +484,13 @@ file {
|
|||||||
type_name: ".google.protobuf.FieldOptions"
|
type_name: ".google.protobuf.FieldOptions"
|
||||||
json_name: "options"
|
json_name: "options"
|
||||||
}
|
}
|
||||||
|
field {
|
||||||
|
name: "proto3_optional"
|
||||||
|
number: 17
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_BOOL
|
||||||
|
json_name: "proto3Optional"
|
||||||
|
}
|
||||||
enum_type {
|
enum_type {
|
||||||
name: "Type"
|
name: "Type"
|
||||||
value {
|
value {
|
||||||
@@ -853,7 +860,7 @@ file {
|
|||||||
number: 31
|
number: 31
|
||||||
label: LABEL_OPTIONAL
|
label: LABEL_OPTIONAL
|
||||||
type: TYPE_BOOL
|
type: TYPE_BOOL
|
||||||
default_value: "false"
|
default_value: "true"
|
||||||
json_name: "ccEnableArenas"
|
json_name: "ccEnableArenas"
|
||||||
}
|
}
|
||||||
field {
|
field {
|
||||||
@@ -1405,7 +1412,7 @@ file {
|
|||||||
java_package: "com.google.protobuf"
|
java_package: "com.google.protobuf"
|
||||||
java_outer_classname: "DescriptorProtos"
|
java_outer_classname: "DescriptorProtos"
|
||||||
optimize_for: SPEED
|
optimize_for: SPEED
|
||||||
go_package: "github.com/golang/protobuf/protoc-gen-go/descriptor;descriptor"
|
go_package: "google.golang.org/protobuf/types/descriptorpb"
|
||||||
cc_enable_arenas: true
|
cc_enable_arenas: true
|
||||||
objc_class_prefix: "GPB"
|
objc_class_prefix: "GPB"
|
||||||
csharp_namespace: "Google.Protobuf.Reflection"
|
csharp_namespace: "Google.Protobuf.Reflection"
|
||||||
@@ -5208,7 +5215,7 @@ file {
|
|||||||
java_package: "com.google.protobuf"
|
java_package: "com.google.protobuf"
|
||||||
java_outer_classname: "AnyProto"
|
java_outer_classname: "AnyProto"
|
||||||
java_multiple_files: true
|
java_multiple_files: true
|
||||||
go_package: "github.com/golang/protobuf/ptypes/any"
|
go_package: "google.golang.org/protobuf/types/known/anypb"
|
||||||
objc_class_prefix: "GPB"
|
objc_class_prefix: "GPB"
|
||||||
csharp_namespace: "Google.Protobuf.WellKnownTypes"
|
csharp_namespace: "Google.Protobuf.WellKnownTypes"
|
||||||
}
|
}
|
||||||
@@ -10590,7 +10597,7 @@ file {
|
|||||||
syntax: "proto3"
|
syntax: "proto3"
|
||||||
}
|
}
|
||||||
file {
|
file {
|
||||||
name: "go.etcd.io/etcd/raft/v3/raftpb/raft.proto"
|
name: "go.etcd.io/raft/v3/raftpb/raft.proto"
|
||||||
package: "raftpb"
|
package: "raftpb"
|
||||||
dependency: "gogoproto/gogo.proto"
|
dependency: "gogoproto/gogo.proto"
|
||||||
message_type {
|
message_type {
|
||||||
@@ -10773,6 +10780,16 @@ file {
|
|||||||
}
|
}
|
||||||
json_name: "commit"
|
json_name: "commit"
|
||||||
}
|
}
|
||||||
|
field {
|
||||||
|
name: "vote"
|
||||||
|
number: 13
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_UINT64
|
||||||
|
options {
|
||||||
|
65001: 0
|
||||||
|
}
|
||||||
|
json_name: "vote"
|
||||||
|
}
|
||||||
field {
|
field {
|
||||||
name: "snapshot"
|
name: "snapshot"
|
||||||
number: 9
|
number: 9
|
||||||
@@ -10780,7 +10797,7 @@ file {
|
|||||||
type: TYPE_MESSAGE
|
type: TYPE_MESSAGE
|
||||||
type_name: ".raftpb.Snapshot"
|
type_name: ".raftpb.Snapshot"
|
||||||
options {
|
options {
|
||||||
65001: 0
|
65001: 1
|
||||||
}
|
}
|
||||||
json_name: "snapshot"
|
json_name: "snapshot"
|
||||||
}
|
}
|
||||||
@@ -10809,8 +10826,22 @@ file {
|
|||||||
number: 12
|
number: 12
|
||||||
label: LABEL_OPTIONAL
|
label: LABEL_OPTIONAL
|
||||||
type: TYPE_BYTES
|
type: TYPE_BYTES
|
||||||
|
options {
|
||||||
|
65001: 1
|
||||||
|
}
|
||||||
json_name: "context"
|
json_name: "context"
|
||||||
}
|
}
|
||||||
|
field {
|
||||||
|
name: "responses"
|
||||||
|
number: 14
|
||||||
|
label: LABEL_REPEATED
|
||||||
|
type: TYPE_MESSAGE
|
||||||
|
type_name: ".raftpb.Message"
|
||||||
|
options {
|
||||||
|
65001: 0
|
||||||
|
}
|
||||||
|
json_name: "responses"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
message_type {
|
message_type {
|
||||||
name: "HardState"
|
name: "HardState"
|
||||||
@@ -11079,6 +11110,26 @@ file {
|
|||||||
name: "MsgPreVoteResp"
|
name: "MsgPreVoteResp"
|
||||||
number: 18
|
number: 18
|
||||||
}
|
}
|
||||||
|
value {
|
||||||
|
name: "MsgStorageAppend"
|
||||||
|
number: 19
|
||||||
|
}
|
||||||
|
value {
|
||||||
|
name: "MsgStorageAppendResp"
|
||||||
|
number: 20
|
||||||
|
}
|
||||||
|
value {
|
||||||
|
name: "MsgStorageApply"
|
||||||
|
number: 21
|
||||||
|
}
|
||||||
|
value {
|
||||||
|
name: "MsgStorageApplyResp"
|
||||||
|
number: 22
|
||||||
|
}
|
||||||
|
value {
|
||||||
|
name: "MsgForgetLeader"
|
||||||
|
number: 23
|
||||||
|
}
|
||||||
}
|
}
|
||||||
enum_type {
|
enum_type {
|
||||||
name: "ConfChangeTransition"
|
name: "ConfChangeTransition"
|
||||||
@@ -11130,7 +11181,7 @@ file {
|
|||||||
package: "docker.swarmkit.v1"
|
package: "docker.swarmkit.v1"
|
||||||
dependency: "github.com/docker/swarmkit/api/objects.proto"
|
dependency: "github.com/docker/swarmkit/api/objects.proto"
|
||||||
dependency: "github.com/docker/swarmkit/api/types.proto"
|
dependency: "github.com/docker/swarmkit/api/types.proto"
|
||||||
dependency: "go.etcd.io/etcd/raft/v3/raftpb/raft.proto"
|
dependency: "go.etcd.io/raft/v3/raftpb/raft.proto"
|
||||||
dependency: "gogoproto/gogo.proto"
|
dependency: "gogoproto/gogo.proto"
|
||||||
dependency: "github.com/docker/swarmkit/protobuf/plugin/plugin.proto"
|
dependency: "github.com/docker/swarmkit/protobuf/plugin/plugin.proto"
|
||||||
message_type {
|
message_type {
|
||||||
|
|||||||
+2
-2
@@ -12,7 +12,7 @@ type CopierFrom interface {
|
|||||||
// Copy takes the fields from src and copies them into the target object.
|
// Copy takes the fields from src and copies them into the target object.
|
||||||
//
|
//
|
||||||
// Calling this method with a nil receiver or a nil src may panic.
|
// Calling this method with a nil receiver or a nil src may panic.
|
||||||
CopyFrom(src interface{})
|
CopyFrom(src any)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy copies src into dst. dst and src must have the same type.
|
// Copy copies src into dst. dst and src must have the same type.
|
||||||
@@ -24,7 +24,7 @@ type CopierFrom interface {
|
|||||||
//
|
//
|
||||||
// If the copy cannot be performed, this function will panic. Make sure to test
|
// If the copy cannot be performed, this function will panic. Make sure to test
|
||||||
// types that use this function.
|
// types that use this function.
|
||||||
func Copy(dst, src interface{}) {
|
func Copy(dst, src any) {
|
||||||
switch dst := dst.(type) {
|
switch dst := dst.(type) {
|
||||||
case *types.Any:
|
case *types.Any:
|
||||||
src := src.(*types.Any)
|
src := src.(*types.Any)
|
||||||
|
|||||||
+2
-2
@@ -73,7 +73,7 @@ func customIndexer(kind string, annotations *Annotations) (bool, [][]byte, error
|
|||||||
return len(converted) != 0, converted, nil
|
return len(converted) != 0, converted, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromArgs(args ...interface{}) ([]byte, error) {
|
func fromArgs(args ...any) ([]byte, error) {
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return nil, fmt.Errorf("must provide only a single argument")
|
return nil, fmt.Errorf("must provide only a single argument")
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ func fromArgs(args ...interface{}) ([]byte, error) {
|
|||||||
return []byte(arg), nil
|
return []byte(arg), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func prefixFromArgs(args ...interface{}) ([]byte, error) {
|
func prefixFromArgs(args ...any) ([]byte, error) {
|
||||||
val, err := fromArgs(args...)
|
val, err := fromArgs(args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
+1
@@ -42,6 +42,7 @@ func init() {
|
|||||||
for _, c := range "yYnNtTfFoO~" {
|
for _, c := range "yYnNtTfFoO~" {
|
||||||
t[int(c)] = 'M' // In map
|
t[int(c)] = 'M' // In map
|
||||||
}
|
}
|
||||||
|
t[int('<')] = 'M' // In map (merge key "<<")
|
||||||
t[int('.')] = '.' // Float (potentially in map)
|
t[int('.')] = '.' // Float (potentially in map)
|
||||||
|
|
||||||
var resolveMapList = []struct {
|
var resolveMapList = []struct {
|
||||||
|
|||||||
Vendored
+7
-7
@@ -5,7 +5,7 @@ dario.cat/mergo
|
|||||||
## explicit; go 1.17
|
## explicit; go 1.17
|
||||||
github.com/Azure/go-ansiterm
|
github.com/Azure/go-ansiterm
|
||||||
github.com/Azure/go-ansiterm/winterm
|
github.com/Azure/go-ansiterm/winterm
|
||||||
# github.com/Microsoft/go-winio v0.6.3-0.20260930231756-f19d9717deb0
|
# github.com/Microsoft/go-winio v0.6.3
|
||||||
## explicit; go 1.26.0
|
## explicit; go 1.26.0
|
||||||
github.com/Microsoft/go-winio
|
github.com/Microsoft/go-winio
|
||||||
github.com/Microsoft/go-winio/internal/fs
|
github.com/Microsoft/go-winio/internal/fs
|
||||||
@@ -90,10 +90,10 @@ github.com/docker/go-units
|
|||||||
# github.com/felixge/httpsnoop v1.1.0
|
# github.com/felixge/httpsnoop v1.1.0
|
||||||
## explicit; go 1.25
|
## explicit; go 1.25
|
||||||
github.com/felixge/httpsnoop
|
github.com/felixge/httpsnoop
|
||||||
# github.com/fvbommel/sortorder v1.2.0
|
# github.com/fvbommel/sortorder v1.2.1
|
||||||
## explicit; go 1.21
|
## explicit; go 1.21
|
||||||
github.com/fvbommel/sortorder
|
github.com/fvbommel/sortorder
|
||||||
# github.com/go-jose/go-jose/v4 v4.1.5
|
# github.com/go-jose/go-jose/v4 v4.1.7
|
||||||
## explicit; go 1.24.0
|
## explicit; go 1.24.0
|
||||||
github.com/go-jose/go-jose/v4
|
github.com/go-jose/go-jose/v4
|
||||||
github.com/go-jose/go-jose/v4/cipher
|
github.com/go-jose/go-jose/v4/cipher
|
||||||
@@ -145,7 +145,7 @@ github.com/grpc-ecosystem/grpc-gateway/v2/utilities
|
|||||||
# github.com/inconshreveable/mousetrap v1.1.0
|
# github.com/inconshreveable/mousetrap v1.1.0
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
github.com/inconshreveable/mousetrap
|
github.com/inconshreveable/mousetrap
|
||||||
# github.com/klauspost/compress v1.20.1
|
# github.com/klauspost/compress v1.20.2
|
||||||
## explicit; go 1.25
|
## explicit; go 1.25
|
||||||
github.com/klauspost/compress
|
github.com/klauspost/compress
|
||||||
github.com/klauspost/compress/fse
|
github.com/klauspost/compress/fse
|
||||||
@@ -205,8 +205,8 @@ github.com/moby/moby/client/pkg/versions
|
|||||||
## explicit; go 1.19
|
## explicit; go 1.19
|
||||||
github.com/moby/patternmatcher
|
github.com/moby/patternmatcher
|
||||||
github.com/moby/patternmatcher/ignorefile
|
github.com/moby/patternmatcher/ignorefile
|
||||||
# github.com/moby/swarmkit/v2 v2.1.2
|
# github.com/moby/swarmkit/v2 v2.1.3
|
||||||
## explicit; go 1.24.0
|
## explicit; go 1.25.0
|
||||||
github.com/moby/swarmkit/v2/api
|
github.com/moby/swarmkit/v2/api
|
||||||
github.com/moby/swarmkit/v2/api/deepcopy
|
github.com/moby/swarmkit/v2/api/deepcopy
|
||||||
github.com/moby/swarmkit/v2/api/defaults
|
github.com/moby/swarmkit/v2/api/defaults
|
||||||
@@ -388,7 +388,7 @@ go.opentelemetry.io/proto/otlp/common/v1
|
|||||||
go.opentelemetry.io/proto/otlp/metrics/v1
|
go.opentelemetry.io/proto/otlp/metrics/v1
|
||||||
go.opentelemetry.io/proto/otlp/resource/v1
|
go.opentelemetry.io/proto/otlp/resource/v1
|
||||||
go.opentelemetry.io/proto/otlp/trace/v1
|
go.opentelemetry.io/proto/otlp/trace/v1
|
||||||
# go.yaml.in/yaml/v3 v3.0.5
|
# go.yaml.in/yaml/v3 v3.0.6
|
||||||
## explicit; go 1.16
|
## explicit; go 1.16
|
||||||
go.yaml.in/yaml/v3
|
go.yaml.in/yaml/v3
|
||||||
# golang.org/x/mod v0.41.0
|
# golang.org/x/mod v0.41.0
|
||||||
|
|||||||
Reference in New Issue
Block a user