chore: go mod tidy / vendor / make deps
This commit is contained in:
83
go.mod
83
go.mod
@ -8,13 +8,15 @@ require (
|
||||
coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca
|
||||
git.coopcloud.tech/toolshed/godotenv v1.5.2-0.20250103171850-4d0ca41daa5c
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||
github.com/charmbracelet/bubbletea v1.3.6
|
||||
github.com/charmbracelet/bubbles v0.21.0
|
||||
github.com/charmbracelet/bubbletea v1.3.10
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
github.com/charmbracelet/log v0.4.2
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/docker/cli v28.3.3+incompatible
|
||||
github.com/docker/docker v28.3.3+incompatible
|
||||
github.com/docker/cli v28.4.0+incompatible
|
||||
github.com/docker/docker v28.4.0+incompatible
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/evertras/bubble-table v0.19.2
|
||||
github.com/go-git/go-git/v5 v5.16.2
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/leonelquinteros/gotext v1.7.2
|
||||
@ -22,7 +24,7 @@ require (
|
||||
github.com/moby/term v0.5.2
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/schollz/progressbar/v3 v3.18.0
|
||||
golang.org/x/term v0.34.0
|
||||
golang.org/x/term v0.35.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gotest.tools/v3 v3.5.2
|
||||
)
|
||||
@ -33,22 +35,24 @@ require (
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.3.2 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.10.1 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.10.2 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v0.2.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
@ -64,21 +68,21 @@ require (
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
@ -90,47 +94,48 @@ require (
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/runc v1.1.13 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.1.0 // indirect
|
||||
github.com/pjbgf/sha1cd v0.4.0 // indirect
|
||||
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.65.0 // indirect
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
github.com/prometheus/procfs v0.17.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/spf13/pflag v1.0.7 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
|
||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
|
||||
golang.org/x/crypto v0.41.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // indirect
|
||||
google.golang.org/grpc v1.74.2 // indirect
|
||||
google.golang.org/protobuf v1.36.7 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.8.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
golang.org/x/crypto v0.42.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect
|
||||
golang.org/x/net v0.44.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/time v0.13.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // indirect
|
||||
google.golang.org/grpc v1.75.1 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
@ -147,11 +152,11 @@ require (
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/prometheus/client_golang v1.23.0 // indirect
|
||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||
github.com/sergi/go-diff v1.4.0 // indirect
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
golang.org/x/sys v0.35.0
|
||||
golang.org/x/sys v0.36.0
|
||||
)
|
||||
|
175
go.sum
175
go.sum
@ -99,6 +99,8 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
@ -133,20 +135,22 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=
|
||||
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
|
||||
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
|
||||
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
|
||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||
github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI=
|
||||
github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI=
|
||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
|
||||
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
|
||||
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
|
||||
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
||||
github.com/charmbracelet/x/ansi v0.10.2 h1:ith2ArZS0CJG30cIUfID1LXN7ZFXRCww6RUvAPA+Pzw=
|
||||
github.com/charmbracelet/x/ansi v0.10.2/go.mod h1:HbLdJjQH4UH4AqA2HpRWuWNluRE6zxJH/yteYEYCFa8=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
|
||||
@ -164,6 +168,8 @@ github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJ
|
||||
github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
|
||||
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
@ -296,8 +302,8 @@ github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
|
||||
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/cyphar/filepath-securejoin v0.5.0 h1:hIAhkRBMQ8nIeuVwcAoymp7MY4oherZdAxD+m0u9zaw=
|
||||
github.com/cyphar/filepath-securejoin v0.5.0/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
|
||||
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
|
||||
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
|
||||
@ -316,16 +322,16 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v28.3.3+incompatible h1:fp9ZHAr1WWPGdIWBM1b3zLtgCF+83gRdVMTJsUeiyAo=
|
||||
github.com/docker/cli v28.3.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v28.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY=
|
||||
github.com/docker/cli v28.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
|
||||
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
|
||||
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk=
|
||||
github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
||||
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
|
||||
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
|
||||
@ -367,6 +373,8 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/evertras/bubble-table v0.19.2 h1:u77oiM6JlRR+CvS5FZc3Hz+J6iEsvEDcR5kO8OFb1Yw=
|
||||
github.com/evertras/bubble-table v0.19.2/go.mod h1:ifHujS1YxwnYSOgcR2+m3GnJ84f7CVU/4kUOxUCjEbQ=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
@ -439,7 +447,6 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV
|
||||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
@ -529,8 +536,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
||||
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@ -579,8 +586,8 @@ github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVE
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
|
||||
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
@ -590,6 +597,8 @@ github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdY
|
||||
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@ -611,8 +620,8 @@ github.com/leonelquinteros/gotext v1.7.2 h1:bDPndU8nt+/kRo1m4l/1OXiiy2v7Z7dfPQ9+
|
||||
github.com/leonelquinteros/gotext v1.7.2/go.mod h1:9/haCkm5P7Jay1sxKDGJ5WIg4zkz8oZKw4ekNpALob8=
|
||||
github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
@ -631,8 +640,9 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
@ -692,6 +702,8 @@ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
@ -758,8 +770,8 @@ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pjbgf/sha1cd v0.4.0 h1:NXzbL1RvjTUi6kgYZCX3fPwwl27Q1LJndxtUDVfJGRY=
|
||||
github.com/pjbgf/sha1cd v0.4.0/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
|
||||
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@ -775,8 +787,8 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
|
||||
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
@ -790,8 +802,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
@ -806,6 +818,7 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
|
||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
||||
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
@ -851,8 +864,8 @@ github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3
|
||||
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||
github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
@ -861,9 +874,9 @@ github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bd
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
|
||||
@ -878,8 +891,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
|
||||
@ -937,37 +950,39 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 h1:zG8GlgXCJQd5BU98C0hZnBbElszTmUgCNCfYneaDL0A=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0/go.mod h1:hOfBCz8kv/wuq73Mx2H2QnWokh/kHZxkh6SNF2bdKtw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
|
||||
go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE=
|
||||
go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
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/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@ -986,8 +1001,8 @@ golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWP
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -998,8 +1013,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE=
|
||||
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@ -1063,8 +1078,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
|
||||
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@ -1082,8 +1097,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -1162,13 +1175,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -1178,16 +1191,16 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
|
||||
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@ -1237,6 +1250,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
@ -1281,10 +1296,10 @@ google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfG
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a h1:DMCgtIAIQGZqJXMVzJF4MV8BlWoJh2ZuFiRdAleyr58=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a/go.mod h1:y2yVLIE/CSMCPXaHnSKXxu1spLPnglFLegmgdY23uuE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 h1:i8QOKZfYg6AbGVZzUAY3LrNWCKF8O6zFisU9Wl9RER4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ=
|
||||
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
@ -1304,8 +1319,8 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
|
||||
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
|
||||
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
||||
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@ -1319,8 +1334,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
||||
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU=
|
||||
|
22
vendor/github.com/atotto/clipboard/.travis.yml
generated
vendored
Normal file
22
vendor/github.com/atotto/clipboard/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
language: go
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
- windows
|
||||
|
||||
go:
|
||||
- go1.13.x
|
||||
- go1.x
|
||||
|
||||
services:
|
||||
- xvfb
|
||||
|
||||
before_install:
|
||||
- export DISPLAY=:99.0
|
||||
|
||||
script:
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get install xsel; fi
|
||||
- go test -v .
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get install xclip; fi
|
||||
- go test -v .
|
4
vendor/golang.org/x/sync/LICENSE → vendor/github.com/atotto/clipboard/LICENSE
generated
vendored
4
vendor/golang.org/x/sync/LICENSE → vendor/github.com/atotto/clipboard/LICENSE
generated
vendored
@ -1,4 +1,4 @@
|
||||
Copyright 2009 The Go Authors.
|
||||
Copyright (c) 2013 Ato Araki. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google LLC nor the names of its
|
||||
* Neither the name of @atotto. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
48
vendor/github.com/atotto/clipboard/README.md
generated
vendored
Normal file
48
vendor/github.com/atotto/clipboard/README.md
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
[](https://travis-ci.org/atotto/clipboard)
|
||||
|
||||
[](http://godoc.org/github.com/atotto/clipboard)
|
||||
|
||||
# Clipboard for Go
|
||||
|
||||
Provide copying and pasting to the Clipboard for Go.
|
||||
|
||||
Build:
|
||||
|
||||
$ go get github.com/atotto/clipboard
|
||||
|
||||
Platforms:
|
||||
|
||||
* OSX
|
||||
* Windows 7 (probably work on other Windows)
|
||||
* Linux, Unix (requires 'xclip' or 'xsel' command to be installed)
|
||||
|
||||
|
||||
Document:
|
||||
|
||||
* http://godoc.org/github.com/atotto/clipboard
|
||||
|
||||
Notes:
|
||||
|
||||
* Text string only
|
||||
* UTF-8 text encoding only (no conversion)
|
||||
|
||||
TODO:
|
||||
|
||||
* Clipboard watcher(?)
|
||||
|
||||
## Commands:
|
||||
|
||||
paste shell command:
|
||||
|
||||
$ go get github.com/atotto/clipboard/cmd/gopaste
|
||||
$ # example:
|
||||
$ gopaste > document.txt
|
||||
|
||||
copy shell command:
|
||||
|
||||
$ go get github.com/atotto/clipboard/cmd/gocopy
|
||||
$ # example:
|
||||
$ cat document.txt | gocopy
|
||||
|
||||
|
||||
|
20
vendor/github.com/atotto/clipboard/clipboard.go
generated
vendored
Normal file
20
vendor/github.com/atotto/clipboard/clipboard.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2013 @atotto. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package clipboard read/write on clipboard
|
||||
package clipboard
|
||||
|
||||
// ReadAll read string from clipboard
|
||||
func ReadAll() (string, error) {
|
||||
return readAll()
|
||||
}
|
||||
|
||||
// WriteAll write string to clipboard
|
||||
func WriteAll(text string) error {
|
||||
return writeAll(text)
|
||||
}
|
||||
|
||||
// Unsupported might be set true during clipboard init, to help callers decide
|
||||
// whether or not to offer clipboard options.
|
||||
var Unsupported bool
|
52
vendor/github.com/atotto/clipboard/clipboard_darwin.go
generated
vendored
Normal file
52
vendor/github.com/atotto/clipboard/clipboard_darwin.go
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
// Copyright 2013 @atotto. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin
|
||||
|
||||
package clipboard
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
var (
|
||||
pasteCmdArgs = "pbpaste"
|
||||
copyCmdArgs = "pbcopy"
|
||||
)
|
||||
|
||||
func getPasteCommand() *exec.Cmd {
|
||||
return exec.Command(pasteCmdArgs)
|
||||
}
|
||||
|
||||
func getCopyCommand() *exec.Cmd {
|
||||
return exec.Command(copyCmdArgs)
|
||||
}
|
||||
|
||||
func readAll() (string, error) {
|
||||
pasteCmd := getPasteCommand()
|
||||
out, err := pasteCmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(out), nil
|
||||
}
|
||||
|
||||
func writeAll(text string) error {
|
||||
copyCmd := getCopyCommand()
|
||||
in, err := copyCmd.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := copyCmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := in.Write([]byte(text)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := in.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return copyCmd.Wait()
|
||||
}
|
42
vendor/github.com/atotto/clipboard/clipboard_plan9.go
generated
vendored
Normal file
42
vendor/github.com/atotto/clipboard/clipboard_plan9.go
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright 2013 @atotto. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build plan9
|
||||
|
||||
package clipboard
|
||||
|
||||
import (
|
||||
"os"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
func readAll() (string, error) {
|
||||
f, err := os.Open("/dev/snarf")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
str, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(str), nil
|
||||
}
|
||||
|
||||
func writeAll(text string) error {
|
||||
f, err := os.OpenFile("/dev/snarf", os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Write([]byte(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
149
vendor/github.com/atotto/clipboard/clipboard_unix.go
generated
vendored
Normal file
149
vendor/github.com/atotto/clipboard/clipboard_unix.go
generated
vendored
Normal file
@ -0,0 +1,149 @@
|
||||
// Copyright 2013 @atotto. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build freebsd linux netbsd openbsd solaris dragonfly
|
||||
|
||||
package clipboard
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
const (
|
||||
xsel = "xsel"
|
||||
xclip = "xclip"
|
||||
powershellExe = "powershell.exe"
|
||||
clipExe = "clip.exe"
|
||||
wlcopy = "wl-copy"
|
||||
wlpaste = "wl-paste"
|
||||
termuxClipboardGet = "termux-clipboard-get"
|
||||
termuxClipboardSet = "termux-clipboard-set"
|
||||
)
|
||||
|
||||
var (
|
||||
Primary bool
|
||||
trimDos bool
|
||||
|
||||
pasteCmdArgs []string
|
||||
copyCmdArgs []string
|
||||
|
||||
xselPasteArgs = []string{xsel, "--output", "--clipboard"}
|
||||
xselCopyArgs = []string{xsel, "--input", "--clipboard"}
|
||||
|
||||
xclipPasteArgs = []string{xclip, "-out", "-selection", "clipboard"}
|
||||
xclipCopyArgs = []string{xclip, "-in", "-selection", "clipboard"}
|
||||
|
||||
powershellExePasteArgs = []string{powershellExe, "Get-Clipboard"}
|
||||
clipExeCopyArgs = []string{clipExe}
|
||||
|
||||
wlpasteArgs = []string{wlpaste, "--no-newline"}
|
||||
wlcopyArgs = []string{wlcopy}
|
||||
|
||||
termuxPasteArgs = []string{termuxClipboardGet}
|
||||
termuxCopyArgs = []string{termuxClipboardSet}
|
||||
|
||||
missingCommands = errors.New("No clipboard utilities available. Please install xsel, xclip, wl-clipboard or Termux:API add-on for termux-clipboard-get/set.")
|
||||
)
|
||||
|
||||
func init() {
|
||||
if os.Getenv("WAYLAND_DISPLAY") != "" {
|
||||
pasteCmdArgs = wlpasteArgs
|
||||
copyCmdArgs = wlcopyArgs
|
||||
|
||||
if _, err := exec.LookPath(wlcopy); err == nil {
|
||||
if _, err := exec.LookPath(wlpaste); err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pasteCmdArgs = xclipPasteArgs
|
||||
copyCmdArgs = xclipCopyArgs
|
||||
|
||||
if _, err := exec.LookPath(xclip); err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
pasteCmdArgs = xselPasteArgs
|
||||
copyCmdArgs = xselCopyArgs
|
||||
|
||||
if _, err := exec.LookPath(xsel); err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
pasteCmdArgs = termuxPasteArgs
|
||||
copyCmdArgs = termuxCopyArgs
|
||||
|
||||
if _, err := exec.LookPath(termuxClipboardSet); err == nil {
|
||||
if _, err := exec.LookPath(termuxClipboardGet); err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
pasteCmdArgs = powershellExePasteArgs
|
||||
copyCmdArgs = clipExeCopyArgs
|
||||
trimDos = true
|
||||
|
||||
if _, err := exec.LookPath(clipExe); err == nil {
|
||||
if _, err := exec.LookPath(powershellExe); err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Unsupported = true
|
||||
}
|
||||
|
||||
func getPasteCommand() *exec.Cmd {
|
||||
if Primary {
|
||||
pasteCmdArgs = pasteCmdArgs[:1]
|
||||
}
|
||||
return exec.Command(pasteCmdArgs[0], pasteCmdArgs[1:]...)
|
||||
}
|
||||
|
||||
func getCopyCommand() *exec.Cmd {
|
||||
if Primary {
|
||||
copyCmdArgs = copyCmdArgs[:1]
|
||||
}
|
||||
return exec.Command(copyCmdArgs[0], copyCmdArgs[1:]...)
|
||||
}
|
||||
|
||||
func readAll() (string, error) {
|
||||
if Unsupported {
|
||||
return "", missingCommands
|
||||
}
|
||||
pasteCmd := getPasteCommand()
|
||||
out, err := pasteCmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result := string(out)
|
||||
if trimDos && len(result) > 1 {
|
||||
result = result[:len(result)-2]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func writeAll(text string) error {
|
||||
if Unsupported {
|
||||
return missingCommands
|
||||
}
|
||||
copyCmd := getCopyCommand()
|
||||
in, err := copyCmd.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := copyCmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := in.Write([]byte(text)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := in.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return copyCmd.Wait()
|
||||
}
|
157
vendor/github.com/atotto/clipboard/clipboard_windows.go
generated
vendored
Normal file
157
vendor/github.com/atotto/clipboard/clipboard_windows.go
generated
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
// Copyright 2013 @atotto. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build windows
|
||||
|
||||
package clipboard
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
cfUnicodetext = 13
|
||||
gmemMoveable = 0x0002
|
||||
)
|
||||
|
||||
var (
|
||||
user32 = syscall.MustLoadDLL("user32")
|
||||
isClipboardFormatAvailable = user32.MustFindProc("IsClipboardFormatAvailable")
|
||||
openClipboard = user32.MustFindProc("OpenClipboard")
|
||||
closeClipboard = user32.MustFindProc("CloseClipboard")
|
||||
emptyClipboard = user32.MustFindProc("EmptyClipboard")
|
||||
getClipboardData = user32.MustFindProc("GetClipboardData")
|
||||
setClipboardData = user32.MustFindProc("SetClipboardData")
|
||||
|
||||
kernel32 = syscall.NewLazyDLL("kernel32")
|
||||
globalAlloc = kernel32.NewProc("GlobalAlloc")
|
||||
globalFree = kernel32.NewProc("GlobalFree")
|
||||
globalLock = kernel32.NewProc("GlobalLock")
|
||||
globalUnlock = kernel32.NewProc("GlobalUnlock")
|
||||
lstrcpy = kernel32.NewProc("lstrcpyW")
|
||||
)
|
||||
|
||||
// waitOpenClipboard opens the clipboard, waiting for up to a second to do so.
|
||||
func waitOpenClipboard() error {
|
||||
started := time.Now()
|
||||
limit := started.Add(time.Second)
|
||||
var r uintptr
|
||||
var err error
|
||||
for time.Now().Before(limit) {
|
||||
r, _, err = openClipboard.Call(0)
|
||||
if r != 0 {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func readAll() (string, error) {
|
||||
// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
|
||||
// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
if formatAvailable, _, err := isClipboardFormatAvailable.Call(cfUnicodetext); formatAvailable == 0 {
|
||||
return "", err
|
||||
}
|
||||
err := waitOpenClipboard()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
h, _, err := getClipboardData.Call(cfUnicodetext)
|
||||
if h == 0 {
|
||||
_, _, _ = closeClipboard.Call()
|
||||
return "", err
|
||||
}
|
||||
|
||||
l, _, err := globalLock.Call(h)
|
||||
if l == 0 {
|
||||
_, _, _ = closeClipboard.Call()
|
||||
return "", err
|
||||
}
|
||||
|
||||
text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:])
|
||||
|
||||
r, _, err := globalUnlock.Call(h)
|
||||
if r == 0 {
|
||||
_, _, _ = closeClipboard.Call()
|
||||
return "", err
|
||||
}
|
||||
|
||||
closed, _, err := closeClipboard.Call()
|
||||
if closed == 0 {
|
||||
return "", err
|
||||
}
|
||||
return text, nil
|
||||
}
|
||||
|
||||
func writeAll(text string) error {
|
||||
// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
|
||||
// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
err := waitOpenClipboard()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r, _, err := emptyClipboard.Call(0)
|
||||
if r == 0 {
|
||||
_, _, _ = closeClipboard.Call()
|
||||
return err
|
||||
}
|
||||
|
||||
data := syscall.StringToUTF16(text)
|
||||
|
||||
// "If the hMem parameter identifies a memory object, the object must have
|
||||
// been allocated using the function with the GMEM_MOVEABLE flag."
|
||||
h, _, err := globalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0]))))
|
||||
if h == 0 {
|
||||
_, _, _ = closeClipboard.Call()
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if h != 0 {
|
||||
globalFree.Call(h)
|
||||
}
|
||||
}()
|
||||
|
||||
l, _, err := globalLock.Call(h)
|
||||
if l == 0 {
|
||||
_, _, _ = closeClipboard.Call()
|
||||
return err
|
||||
}
|
||||
|
||||
r, _, err = lstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0])))
|
||||
if r == 0 {
|
||||
_, _, _ = closeClipboard.Call()
|
||||
return err
|
||||
}
|
||||
|
||||
r, _, err = globalUnlock.Call(h)
|
||||
if r == 0 {
|
||||
if err.(syscall.Errno) != 0 {
|
||||
_, _, _ = closeClipboard.Call()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
r, _, err = setClipboardData.Call(cfUnicodetext, h)
|
||||
if r == 0 {
|
||||
_, _, _ = closeClipboard.Call()
|
||||
return err
|
||||
}
|
||||
h = 0 // suppress deferred cleanup
|
||||
closed, _, err := closeClipboard.Call()
|
||||
if closed == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
21
vendor/github.com/charmbracelet/bubbles/LICENSE
generated
vendored
Normal file
21
vendor/github.com/charmbracelet/bubbles/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2023 Charmbracelet, Inc
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
219
vendor/github.com/charmbracelet/bubbles/cursor/cursor.go
generated
vendored
Normal file
219
vendor/github.com/charmbracelet/bubbles/cursor/cursor.go
generated
vendored
Normal file
@ -0,0 +1,219 @@
|
||||
// Package cursor provides cursor functionality for Bubble Tea applications.
|
||||
package cursor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
const defaultBlinkSpeed = time.Millisecond * 530
|
||||
|
||||
// initialBlinkMsg initializes cursor blinking.
|
||||
type initialBlinkMsg struct{}
|
||||
|
||||
// BlinkMsg signals that the cursor should blink. It contains metadata that
|
||||
// allows us to tell if the blink message is the one we're expecting.
|
||||
type BlinkMsg struct {
|
||||
id int
|
||||
tag int
|
||||
}
|
||||
|
||||
// blinkCanceled is sent when a blink operation is canceled.
|
||||
type blinkCanceled struct{}
|
||||
|
||||
// blinkCtx manages cursor blinking.
|
||||
type blinkCtx struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// Mode describes the behavior of the cursor.
|
||||
type Mode int
|
||||
|
||||
// Available cursor modes.
|
||||
const (
|
||||
CursorBlink Mode = iota
|
||||
CursorStatic
|
||||
CursorHide
|
||||
)
|
||||
|
||||
// String returns the cursor mode in a human-readable format. This method is
|
||||
// provisional and for informational purposes only.
|
||||
func (c Mode) String() string {
|
||||
return [...]string{
|
||||
"blink",
|
||||
"static",
|
||||
"hidden",
|
||||
}[c]
|
||||
}
|
||||
|
||||
// Model is the Bubble Tea model for this cursor element.
|
||||
type Model struct {
|
||||
BlinkSpeed time.Duration
|
||||
// Style for styling the cursor block.
|
||||
Style lipgloss.Style
|
||||
// TextStyle is the style used for the cursor when it is hidden (when blinking).
|
||||
// I.e. displaying normal text.
|
||||
TextStyle lipgloss.Style
|
||||
|
||||
// char is the character under the cursor
|
||||
char string
|
||||
// The ID of this Model as it relates to other cursors
|
||||
id int
|
||||
// focus indicates whether the containing input is focused
|
||||
focus bool
|
||||
// Cursor Blink state.
|
||||
Blink bool
|
||||
// Used to manage cursor blink
|
||||
blinkCtx *blinkCtx
|
||||
// The ID of the blink message we're expecting to receive.
|
||||
blinkTag int
|
||||
// mode determines the behavior of the cursor
|
||||
mode Mode
|
||||
}
|
||||
|
||||
// New creates a new model with default settings.
|
||||
func New() Model {
|
||||
return Model{
|
||||
BlinkSpeed: defaultBlinkSpeed,
|
||||
|
||||
Blink: true,
|
||||
mode: CursorBlink,
|
||||
|
||||
blinkCtx: &blinkCtx{
|
||||
ctx: context.Background(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Update updates the cursor.
|
||||
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case initialBlinkMsg:
|
||||
// We accept all initialBlinkMsgs generated by the Blink command.
|
||||
|
||||
if m.mode != CursorBlink || !m.focus {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
cmd := m.BlinkCmd()
|
||||
return m, cmd
|
||||
|
||||
case tea.FocusMsg:
|
||||
return m, m.Focus()
|
||||
|
||||
case tea.BlurMsg:
|
||||
m.Blur()
|
||||
return m, nil
|
||||
|
||||
case BlinkMsg:
|
||||
// We're choosy about whether to accept blinkMsgs so that our cursor
|
||||
// only exactly when it should.
|
||||
|
||||
// Is this model blink-able?
|
||||
if m.mode != CursorBlink || !m.focus {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Were we expecting this blink message?
|
||||
if msg.id != m.id || msg.tag != m.blinkTag {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
var cmd tea.Cmd
|
||||
if m.mode == CursorBlink {
|
||||
m.Blink = !m.Blink
|
||||
cmd = m.BlinkCmd()
|
||||
}
|
||||
return m, cmd
|
||||
|
||||
case blinkCanceled: // no-op
|
||||
return m, nil
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Mode returns the model's cursor mode. For available cursor modes, see
|
||||
// type Mode.
|
||||
func (m Model) Mode() Mode {
|
||||
return m.mode
|
||||
}
|
||||
|
||||
// SetMode sets the model's cursor mode. This method returns a command.
|
||||
//
|
||||
// For available cursor modes, see type CursorMode.
|
||||
func (m *Model) SetMode(mode Mode) tea.Cmd {
|
||||
// Adjust the mode value if it's value is out of range
|
||||
if mode < CursorBlink || mode > CursorHide {
|
||||
return nil
|
||||
}
|
||||
m.mode = mode
|
||||
m.Blink = m.mode == CursorHide || !m.focus
|
||||
if mode == CursorBlink {
|
||||
return Blink
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BlinkCmd is a command used to manage cursor blinking.
|
||||
func (m *Model) BlinkCmd() tea.Cmd {
|
||||
if m.mode != CursorBlink {
|
||||
return nil
|
||||
}
|
||||
|
||||
if m.blinkCtx != nil && m.blinkCtx.cancel != nil {
|
||||
m.blinkCtx.cancel()
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(m.blinkCtx.ctx, m.BlinkSpeed)
|
||||
m.blinkCtx.cancel = cancel
|
||||
|
||||
m.blinkTag++
|
||||
|
||||
return func() tea.Msg {
|
||||
defer cancel()
|
||||
<-ctx.Done()
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return BlinkMsg{id: m.id, tag: m.blinkTag}
|
||||
}
|
||||
return blinkCanceled{}
|
||||
}
|
||||
}
|
||||
|
||||
// Blink is a command used to initialize cursor blinking.
|
||||
func Blink() tea.Msg {
|
||||
return initialBlinkMsg{}
|
||||
}
|
||||
|
||||
// Focus focuses the cursor to allow it to blink if desired.
|
||||
func (m *Model) Focus() tea.Cmd {
|
||||
m.focus = true
|
||||
m.Blink = m.mode == CursorHide // show the cursor unless we've explicitly hidden it
|
||||
|
||||
if m.mode == CursorBlink && m.focus {
|
||||
return m.BlinkCmd()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Blur blurs the cursor.
|
||||
func (m *Model) Blur() {
|
||||
m.focus = false
|
||||
m.Blink = true
|
||||
}
|
||||
|
||||
// SetChar sets the character under the cursor.
|
||||
func (m *Model) SetChar(char string) {
|
||||
m.char = char
|
||||
}
|
||||
|
||||
// View displays the cursor.
|
||||
func (m Model) View() string {
|
||||
if m.Blink {
|
||||
return m.TextStyle.Inline(true).Render(m.char)
|
||||
}
|
||||
return m.Style.Inline(true).Reverse(true).Render(m.char)
|
||||
}
|
140
vendor/github.com/charmbracelet/bubbles/key/key.go
generated
vendored
Normal file
140
vendor/github.com/charmbracelet/bubbles/key/key.go
generated
vendored
Normal file
@ -0,0 +1,140 @@
|
||||
// Package key provides some types and functions for generating user-definable
|
||||
// keymappings useful in Bubble Tea components. There are a few different ways
|
||||
// you can define a keymapping with this package. Here's one example:
|
||||
//
|
||||
// type KeyMap struct {
|
||||
// Up key.Binding
|
||||
// Down key.Binding
|
||||
// }
|
||||
//
|
||||
// var DefaultKeyMap = KeyMap{
|
||||
// Up: key.NewBinding(
|
||||
// key.WithKeys("k", "up"), // actual keybindings
|
||||
// key.WithHelp("↑/k", "move up"), // corresponding help text
|
||||
// ),
|
||||
// Down: key.NewBinding(
|
||||
// key.WithKeys("j", "down"),
|
||||
// key.WithHelp("↓/j", "move down"),
|
||||
// ),
|
||||
// }
|
||||
//
|
||||
// func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// switch msg := msg.(type) {
|
||||
// case tea.KeyMsg:
|
||||
// switch {
|
||||
// case key.Matches(msg, DefaultKeyMap.Up):
|
||||
// // The user pressed up
|
||||
// case key.Matches(msg, DefaultKeyMap.Down):
|
||||
// // The user pressed down
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // ...
|
||||
// }
|
||||
//
|
||||
// The help information, which is not used in the example above, can be used
|
||||
// to render help text for keystrokes in your views.
|
||||
package key
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Binding describes a set of keybindings and, optionally, their associated
|
||||
// help text.
|
||||
type Binding struct {
|
||||
keys []string
|
||||
help Help
|
||||
disabled bool
|
||||
}
|
||||
|
||||
// BindingOpt is an initialization option for a keybinding. It's used as an
|
||||
// argument to NewBinding.
|
||||
type BindingOpt func(*Binding)
|
||||
|
||||
// NewBinding returns a new keybinding from a set of BindingOpt options.
|
||||
func NewBinding(opts ...BindingOpt) Binding {
|
||||
b := &Binding{}
|
||||
for _, opt := range opts {
|
||||
opt(b)
|
||||
}
|
||||
return *b
|
||||
}
|
||||
|
||||
// WithKeys initializes a keybinding with the given keystrokes.
|
||||
func WithKeys(keys ...string) BindingOpt {
|
||||
return func(b *Binding) {
|
||||
b.keys = keys
|
||||
}
|
||||
}
|
||||
|
||||
// WithHelp initializes a keybinding with the given help text.
|
||||
func WithHelp(key, desc string) BindingOpt {
|
||||
return func(b *Binding) {
|
||||
b.help = Help{Key: key, Desc: desc}
|
||||
}
|
||||
}
|
||||
|
||||
// WithDisabled initializes a disabled keybinding.
|
||||
func WithDisabled() BindingOpt {
|
||||
return func(b *Binding) {
|
||||
b.disabled = true
|
||||
}
|
||||
}
|
||||
|
||||
// SetKeys sets the keys for the keybinding.
|
||||
func (b *Binding) SetKeys(keys ...string) {
|
||||
b.keys = keys
|
||||
}
|
||||
|
||||
// Keys returns the keys for the keybinding.
|
||||
func (b Binding) Keys() []string {
|
||||
return b.keys
|
||||
}
|
||||
|
||||
// SetHelp sets the help text for the keybinding.
|
||||
func (b *Binding) SetHelp(key, desc string) {
|
||||
b.help = Help{Key: key, Desc: desc}
|
||||
}
|
||||
|
||||
// Help returns the Help information for the keybinding.
|
||||
func (b Binding) Help() Help {
|
||||
return b.help
|
||||
}
|
||||
|
||||
// Enabled returns whether or not the keybinding is enabled. Disabled
|
||||
// keybindings won't be activated and won't show up in help. Keybindings are
|
||||
// enabled by default.
|
||||
func (b Binding) Enabled() bool {
|
||||
return !b.disabled && b.keys != nil
|
||||
}
|
||||
|
||||
// SetEnabled enables or disables the keybinding.
|
||||
func (b *Binding) SetEnabled(v bool) {
|
||||
b.disabled = !v
|
||||
}
|
||||
|
||||
// Unbind removes the keys and help from this binding, effectively nullifying
|
||||
// it. This is a step beyond disabling it, since applications can enable
|
||||
// or disable key bindings based on application state.
|
||||
func (b *Binding) Unbind() {
|
||||
b.keys = nil
|
||||
b.help = Help{}
|
||||
}
|
||||
|
||||
// Help is help information for a given keybinding.
|
||||
type Help struct {
|
||||
Key string
|
||||
Desc string
|
||||
}
|
||||
|
||||
// Matches checks if the given key matches the given bindings.
|
||||
func Matches[Key fmt.Stringer](k Key, b ...Binding) bool {
|
||||
keys := k.String()
|
||||
for _, binding := range b {
|
||||
for _, v := range binding.keys {
|
||||
if keys == v && binding.Enabled() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
102
vendor/github.com/charmbracelet/bubbles/runeutil/runeutil.go
generated
vendored
Normal file
102
vendor/github.com/charmbracelet/bubbles/runeutil/runeutil.go
generated
vendored
Normal file
@ -0,0 +1,102 @@
|
||||
// Package runeutil provides a utility function for use in Bubbles
|
||||
// that can process Key messages containing runes.
|
||||
package runeutil
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Sanitizer is a helper for bubble widgets that want to process
|
||||
// Runes from input key messages.
|
||||
type Sanitizer interface {
|
||||
// Sanitize removes control characters from runes in a KeyRunes
|
||||
// message, and optionally replaces newline/carriage return/tabs by a
|
||||
// specified character.
|
||||
//
|
||||
// The rune array is modified in-place if possible. In that case, the
|
||||
// returned slice is the original slice shortened after the control
|
||||
// characters have been removed/translated.
|
||||
Sanitize(runes []rune) []rune
|
||||
}
|
||||
|
||||
// NewSanitizer constructs a rune sanitizer.
|
||||
func NewSanitizer(opts ...Option) Sanitizer {
|
||||
s := sanitizer{
|
||||
replaceNewLine: []rune("\n"),
|
||||
replaceTab: []rune(" "),
|
||||
}
|
||||
for _, o := range opts {
|
||||
s = o(s)
|
||||
}
|
||||
return &s
|
||||
}
|
||||
|
||||
// Option is the type of option that can be passed to Sanitize().
|
||||
type Option func(sanitizer) sanitizer
|
||||
|
||||
// ReplaceTabs replaces tabs by the specified string.
|
||||
func ReplaceTabs(tabRepl string) Option {
|
||||
return func(s sanitizer) sanitizer {
|
||||
s.replaceTab = []rune(tabRepl)
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceNewlines replaces newline characters by the specified string.
|
||||
func ReplaceNewlines(nlRepl string) Option {
|
||||
return func(s sanitizer) sanitizer {
|
||||
s.replaceNewLine = []rune(nlRepl)
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sanitizer) Sanitize(runes []rune) []rune {
|
||||
// dstrunes are where we are storing the result.
|
||||
dstrunes := runes[:0:len(runes)]
|
||||
// copied indicates whether dstrunes is an alias of runes
|
||||
// or a copy. We need a copy when dst moves past src.
|
||||
// We use this as an optimization to avoid allocating
|
||||
// a new rune slice in the common case where the output
|
||||
// is smaller or equal to the input.
|
||||
copied := false
|
||||
|
||||
for src := 0; src < len(runes); src++ {
|
||||
r := runes[src]
|
||||
switch {
|
||||
case r == utf8.RuneError:
|
||||
// skip
|
||||
|
||||
case r == '\r' || r == '\n':
|
||||
if len(dstrunes)+len(s.replaceNewLine) > src && !copied {
|
||||
dst := len(dstrunes)
|
||||
dstrunes = make([]rune, dst, len(runes)+len(s.replaceNewLine))
|
||||
copy(dstrunes, runes[:dst])
|
||||
copied = true
|
||||
}
|
||||
dstrunes = append(dstrunes, s.replaceNewLine...)
|
||||
|
||||
case r == '\t':
|
||||
if len(dstrunes)+len(s.replaceTab) > src && !copied {
|
||||
dst := len(dstrunes)
|
||||
dstrunes = make([]rune, dst, len(runes)+len(s.replaceTab))
|
||||
copy(dstrunes, runes[:dst])
|
||||
copied = true
|
||||
}
|
||||
dstrunes = append(dstrunes, s.replaceTab...)
|
||||
|
||||
case unicode.IsControl(r):
|
||||
// Other control characters: skip.
|
||||
|
||||
default:
|
||||
// Keep the character.
|
||||
dstrunes = append(dstrunes, runes[src])
|
||||
}
|
||||
}
|
||||
return dstrunes
|
||||
}
|
||||
|
||||
type sanitizer struct {
|
||||
replaceNewLine []rune
|
||||
replaceTab []rune
|
||||
}
|
224
vendor/github.com/charmbracelet/bubbles/spinner/spinner.go
generated
vendored
Normal file
224
vendor/github.com/charmbracelet/bubbles/spinner/spinner.go
generated
vendored
Normal file
@ -0,0 +1,224 @@
|
||||
// Package spinner provides a spinner component for Bubble Tea applications.
|
||||
package spinner
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// Internal ID management. Used during animating to ensure that frame messages
|
||||
// are received only by spinner components that sent them.
|
||||
var lastID int64
|
||||
|
||||
func nextID() int {
|
||||
return int(atomic.AddInt64(&lastID, 1))
|
||||
}
|
||||
|
||||
// Spinner is a set of frames used in animating the spinner.
|
||||
type Spinner struct {
|
||||
Frames []string
|
||||
FPS time.Duration
|
||||
}
|
||||
|
||||
// Some spinners to choose from. You could also make your own.
|
||||
var (
|
||||
Line = Spinner{
|
||||
Frames: []string{"|", "/", "-", "\\"},
|
||||
FPS: time.Second / 10, //nolint:mnd
|
||||
}
|
||||
Dot = Spinner{
|
||||
Frames: []string{"⣾ ", "⣽ ", "⣻ ", "⢿ ", "⡿ ", "⣟ ", "⣯ ", "⣷ "},
|
||||
FPS: time.Second / 10, //nolint:mnd
|
||||
}
|
||||
MiniDot = Spinner{
|
||||
Frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
|
||||
FPS: time.Second / 12, //nolint:mnd
|
||||
}
|
||||
Jump = Spinner{
|
||||
Frames: []string{"⢄", "⢂", "⢁", "⡁", "⡈", "⡐", "⡠"},
|
||||
FPS: time.Second / 10, //nolint:mnd
|
||||
}
|
||||
Pulse = Spinner{
|
||||
Frames: []string{"█", "▓", "▒", "░"},
|
||||
FPS: time.Second / 8, //nolint:mnd
|
||||
}
|
||||
Points = Spinner{
|
||||
Frames: []string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●"},
|
||||
FPS: time.Second / 7, //nolint:mnd
|
||||
}
|
||||
Globe = Spinner{
|
||||
Frames: []string{"🌍", "🌎", "🌏"},
|
||||
FPS: time.Second / 4, //nolint:mnd
|
||||
}
|
||||
Moon = Spinner{
|
||||
Frames: []string{"🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"},
|
||||
FPS: time.Second / 8, //nolint:mnd
|
||||
}
|
||||
Monkey = Spinner{
|
||||
Frames: []string{"🙈", "🙉", "🙊"},
|
||||
FPS: time.Second / 3, //nolint:mnd
|
||||
}
|
||||
Meter = Spinner{
|
||||
Frames: []string{
|
||||
"▱▱▱",
|
||||
"▰▱▱",
|
||||
"▰▰▱",
|
||||
"▰▰▰",
|
||||
"▰▰▱",
|
||||
"▰▱▱",
|
||||
"▱▱▱",
|
||||
},
|
||||
FPS: time.Second / 7, //nolint:mnd
|
||||
}
|
||||
Hamburger = Spinner{
|
||||
Frames: []string{"☱", "☲", "☴", "☲"},
|
||||
FPS: time.Second / 3, //nolint:mnd
|
||||
}
|
||||
Ellipsis = Spinner{
|
||||
Frames: []string{"", ".", "..", "..."},
|
||||
FPS: time.Second / 3, //nolint:mnd
|
||||
}
|
||||
)
|
||||
|
||||
// Model contains the state for the spinner. Use New to create new models
|
||||
// rather than using Model as a struct literal.
|
||||
type Model struct {
|
||||
// Spinner settings to use. See type Spinner.
|
||||
Spinner Spinner
|
||||
|
||||
// Style sets the styling for the spinner. Most of the time you'll just
|
||||
// want foreground and background coloring, and potentially some padding.
|
||||
//
|
||||
// For an introduction to styling with Lip Gloss see:
|
||||
// https://github.com/charmbracelet/lipgloss
|
||||
Style lipgloss.Style
|
||||
|
||||
frame int
|
||||
id int
|
||||
tag int
|
||||
}
|
||||
|
||||
// ID returns the spinner's unique ID.
|
||||
func (m Model) ID() int {
|
||||
return m.id
|
||||
}
|
||||
|
||||
// New returns a model with default values.
|
||||
func New(opts ...Option) Model {
|
||||
m := Model{
|
||||
Spinner: Line,
|
||||
id: nextID(),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(&m)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// NewModel returns a model with default values.
|
||||
//
|
||||
// Deprecated: use [New] instead.
|
||||
var NewModel = New
|
||||
|
||||
// TickMsg indicates that the timer has ticked and we should render a frame.
|
||||
type TickMsg struct {
|
||||
Time time.Time
|
||||
tag int
|
||||
ID int
|
||||
}
|
||||
|
||||
// Update is the Tea update function.
|
||||
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case TickMsg:
|
||||
// If an ID is set, and the ID doesn't belong to this spinner, reject
|
||||
// the message.
|
||||
if msg.ID > 0 && msg.ID != m.id {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// If a tag is set, and it's not the one we expect, reject the message.
|
||||
// This prevents the spinner from receiving too many messages and
|
||||
// thus spinning too fast.
|
||||
if msg.tag > 0 && msg.tag != m.tag {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
m.frame++
|
||||
if m.frame >= len(m.Spinner.Frames) {
|
||||
m.frame = 0
|
||||
}
|
||||
|
||||
m.tag++
|
||||
return m, m.tick(m.id, m.tag)
|
||||
default:
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
|
||||
// View renders the model's view.
|
||||
func (m Model) View() string {
|
||||
if m.frame >= len(m.Spinner.Frames) {
|
||||
return "(error)"
|
||||
}
|
||||
|
||||
return m.Style.Render(m.Spinner.Frames[m.frame])
|
||||
}
|
||||
|
||||
// Tick is the command used to advance the spinner one frame. Use this command
|
||||
// to effectively start the spinner.
|
||||
func (m Model) Tick() tea.Msg {
|
||||
return TickMsg{
|
||||
// The time at which the tick occurred.
|
||||
Time: time.Now(),
|
||||
|
||||
// The ID of the spinner that this message belongs to. This can be
|
||||
// helpful when routing messages, however bear in mind that spinners
|
||||
// will ignore messages that don't contain ID by default.
|
||||
ID: m.id,
|
||||
|
||||
tag: m.tag,
|
||||
}
|
||||
}
|
||||
|
||||
func (m Model) tick(id, tag int) tea.Cmd {
|
||||
return tea.Tick(m.Spinner.FPS, func(t time.Time) tea.Msg {
|
||||
return TickMsg{
|
||||
Time: t,
|
||||
ID: id,
|
||||
tag: tag,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Tick is the command used to advance the spinner one frame. Use this command
|
||||
// to effectively start the spinner.
|
||||
//
|
||||
// Deprecated: Use [Model.Tick] instead.
|
||||
func Tick() tea.Msg {
|
||||
return TickMsg{Time: time.Now()}
|
||||
}
|
||||
|
||||
// Option is used to set options in New. For example:
|
||||
//
|
||||
// spinner := New(WithSpinner(Dot))
|
||||
type Option func(*Model)
|
||||
|
||||
// WithSpinner is an option to set the spinner.
|
||||
func WithSpinner(spinner Spinner) Option {
|
||||
return func(m *Model) {
|
||||
m.Spinner = spinner
|
||||
}
|
||||
}
|
||||
|
||||
// WithStyle is an option to set the spinner style.
|
||||
func WithStyle(style lipgloss.Style) Option {
|
||||
return func(m *Model) {
|
||||
m.Style = style
|
||||
}
|
||||
}
|
898
vendor/github.com/charmbracelet/bubbles/textinput/textinput.go
generated
vendored
Normal file
898
vendor/github.com/charmbracelet/bubbles/textinput/textinput.go
generated
vendored
Normal file
@ -0,0 +1,898 @@
|
||||
// Package textinput provides a text input component for Bubble Tea
|
||||
// applications.
|
||||
package textinput
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/atotto/clipboard"
|
||||
"github.com/charmbracelet/bubbles/cursor"
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
"github.com/charmbracelet/bubbles/runeutil"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
rw "github.com/mattn/go-runewidth"
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
// Internal messages for clipboard operations.
|
||||
type (
|
||||
pasteMsg string
|
||||
pasteErrMsg struct{ error }
|
||||
)
|
||||
|
||||
// EchoMode sets the input behavior of the text input field.
|
||||
type EchoMode int
|
||||
|
||||
const (
|
||||
// EchoNormal displays text as is. This is the default behavior.
|
||||
EchoNormal EchoMode = iota
|
||||
|
||||
// EchoPassword displays the EchoCharacter mask instead of actual
|
||||
// characters. This is commonly used for password fields.
|
||||
EchoPassword
|
||||
|
||||
// EchoNone displays nothing as characters are entered. This is commonly
|
||||
// seen for password fields on the command line.
|
||||
EchoNone
|
||||
)
|
||||
|
||||
// ValidateFunc is a function that returns an error if the input is invalid.
|
||||
type ValidateFunc func(string) error
|
||||
|
||||
// KeyMap is the key bindings for different actions within the textinput.
|
||||
type KeyMap struct {
|
||||
CharacterForward key.Binding
|
||||
CharacterBackward key.Binding
|
||||
WordForward key.Binding
|
||||
WordBackward key.Binding
|
||||
DeleteWordBackward key.Binding
|
||||
DeleteWordForward key.Binding
|
||||
DeleteAfterCursor key.Binding
|
||||
DeleteBeforeCursor key.Binding
|
||||
DeleteCharacterBackward key.Binding
|
||||
DeleteCharacterForward key.Binding
|
||||
LineStart key.Binding
|
||||
LineEnd key.Binding
|
||||
Paste key.Binding
|
||||
AcceptSuggestion key.Binding
|
||||
NextSuggestion key.Binding
|
||||
PrevSuggestion key.Binding
|
||||
}
|
||||
|
||||
// DefaultKeyMap is the default set of key bindings for navigating and acting
|
||||
// upon the textinput.
|
||||
var DefaultKeyMap = KeyMap{
|
||||
CharacterForward: key.NewBinding(key.WithKeys("right", "ctrl+f")),
|
||||
CharacterBackward: key.NewBinding(key.WithKeys("left", "ctrl+b")),
|
||||
WordForward: key.NewBinding(key.WithKeys("alt+right", "ctrl+right", "alt+f")),
|
||||
WordBackward: key.NewBinding(key.WithKeys("alt+left", "ctrl+left", "alt+b")),
|
||||
DeleteWordBackward: key.NewBinding(key.WithKeys("alt+backspace", "ctrl+w")),
|
||||
DeleteWordForward: key.NewBinding(key.WithKeys("alt+delete", "alt+d")),
|
||||
DeleteAfterCursor: key.NewBinding(key.WithKeys("ctrl+k")),
|
||||
DeleteBeforeCursor: key.NewBinding(key.WithKeys("ctrl+u")),
|
||||
DeleteCharacterBackward: key.NewBinding(key.WithKeys("backspace", "ctrl+h")),
|
||||
DeleteCharacterForward: key.NewBinding(key.WithKeys("delete", "ctrl+d")),
|
||||
LineStart: key.NewBinding(key.WithKeys("home", "ctrl+a")),
|
||||
LineEnd: key.NewBinding(key.WithKeys("end", "ctrl+e")),
|
||||
Paste: key.NewBinding(key.WithKeys("ctrl+v")),
|
||||
AcceptSuggestion: key.NewBinding(key.WithKeys("tab")),
|
||||
NextSuggestion: key.NewBinding(key.WithKeys("down", "ctrl+n")),
|
||||
PrevSuggestion: key.NewBinding(key.WithKeys("up", "ctrl+p")),
|
||||
}
|
||||
|
||||
// Model is the Bubble Tea model for this text input element.
|
||||
type Model struct {
|
||||
Err error
|
||||
|
||||
// General settings.
|
||||
Prompt string
|
||||
Placeholder string
|
||||
EchoMode EchoMode
|
||||
EchoCharacter rune
|
||||
Cursor cursor.Model
|
||||
|
||||
// Deprecated: use [cursor.BlinkSpeed] instead.
|
||||
BlinkSpeed time.Duration
|
||||
|
||||
// Styles. These will be applied as inline styles.
|
||||
//
|
||||
// For an introduction to styling with Lip Gloss see:
|
||||
// https://github.com/charmbracelet/lipgloss
|
||||
PromptStyle lipgloss.Style
|
||||
TextStyle lipgloss.Style
|
||||
PlaceholderStyle lipgloss.Style
|
||||
CompletionStyle lipgloss.Style
|
||||
|
||||
// Deprecated: use Cursor.Style instead.
|
||||
CursorStyle lipgloss.Style
|
||||
|
||||
// CharLimit is the maximum amount of characters this input element will
|
||||
// accept. If 0 or less, there's no limit.
|
||||
CharLimit int
|
||||
|
||||
// Width is the maximum number of characters that can be displayed at once.
|
||||
// It essentially treats the text field like a horizontally scrolling
|
||||
// viewport. If 0 or less this setting is ignored.
|
||||
Width int
|
||||
|
||||
// KeyMap encodes the keybindings recognized by the widget.
|
||||
KeyMap KeyMap
|
||||
|
||||
// Underlying text value.
|
||||
value []rune
|
||||
|
||||
// focus indicates whether user input focus should be on this input
|
||||
// component. When false, ignore keyboard input and hide the cursor.
|
||||
focus bool
|
||||
|
||||
// Cursor position.
|
||||
pos int
|
||||
|
||||
// Used to emulate a viewport when width is set and the content is
|
||||
// overflowing.
|
||||
offset int
|
||||
offsetRight int
|
||||
|
||||
// Validate is a function that checks whether or not the text within the
|
||||
// input is valid. If it is not valid, the `Err` field will be set to the
|
||||
// error returned by the function. If the function is not defined, all
|
||||
// input is considered valid.
|
||||
Validate ValidateFunc
|
||||
|
||||
// rune sanitizer for input.
|
||||
rsan runeutil.Sanitizer
|
||||
|
||||
// Should the input suggest to complete
|
||||
ShowSuggestions bool
|
||||
|
||||
// suggestions is a list of suggestions that may be used to complete the
|
||||
// input.
|
||||
suggestions [][]rune
|
||||
matchedSuggestions [][]rune
|
||||
currentSuggestionIndex int
|
||||
}
|
||||
|
||||
// New creates a new model with default settings.
|
||||
func New() Model {
|
||||
return Model{
|
||||
Prompt: "> ",
|
||||
EchoCharacter: '*',
|
||||
CharLimit: 0,
|
||||
PlaceholderStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("240")),
|
||||
ShowSuggestions: false,
|
||||
CompletionStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("240")),
|
||||
Cursor: cursor.New(),
|
||||
KeyMap: DefaultKeyMap,
|
||||
|
||||
suggestions: [][]rune{},
|
||||
value: nil,
|
||||
focus: false,
|
||||
pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// NewModel creates a new model with default settings.
|
||||
//
|
||||
// Deprecated: Use [New] instead.
|
||||
var NewModel = New
|
||||
|
||||
// SetValue sets the value of the text input.
|
||||
func (m *Model) SetValue(s string) {
|
||||
// Clean up any special characters in the input provided by the
|
||||
// caller. This avoids bugs due to e.g. tab characters and whatnot.
|
||||
runes := m.san().Sanitize([]rune(s))
|
||||
err := m.validate(runes)
|
||||
m.setValueInternal(runes, err)
|
||||
}
|
||||
|
||||
func (m *Model) setValueInternal(runes []rune, err error) {
|
||||
m.Err = err
|
||||
|
||||
empty := len(m.value) == 0
|
||||
|
||||
if m.CharLimit > 0 && len(runes) > m.CharLimit {
|
||||
m.value = runes[:m.CharLimit]
|
||||
} else {
|
||||
m.value = runes
|
||||
}
|
||||
if (m.pos == 0 && empty) || m.pos > len(m.value) {
|
||||
m.SetCursor(len(m.value))
|
||||
}
|
||||
m.handleOverflow()
|
||||
}
|
||||
|
||||
// Value returns the value of the text input.
|
||||
func (m Model) Value() string {
|
||||
return string(m.value)
|
||||
}
|
||||
|
||||
// Position returns the cursor position.
|
||||
func (m Model) Position() int {
|
||||
return m.pos
|
||||
}
|
||||
|
||||
// SetCursor moves the cursor to the given position. If the position is
|
||||
// out of bounds the cursor will be moved to the start or end accordingly.
|
||||
func (m *Model) SetCursor(pos int) {
|
||||
m.pos = clamp(pos, 0, len(m.value))
|
||||
m.handleOverflow()
|
||||
}
|
||||
|
||||
// CursorStart moves the cursor to the start of the input field.
|
||||
func (m *Model) CursorStart() {
|
||||
m.SetCursor(0)
|
||||
}
|
||||
|
||||
// CursorEnd moves the cursor to the end of the input field.
|
||||
func (m *Model) CursorEnd() {
|
||||
m.SetCursor(len(m.value))
|
||||
}
|
||||
|
||||
// Focused returns the focus state on the model.
|
||||
func (m Model) Focused() bool {
|
||||
return m.focus
|
||||
}
|
||||
|
||||
// Focus sets the focus state on the model. When the model is in focus it can
|
||||
// receive keyboard input and the cursor will be shown.
|
||||
func (m *Model) Focus() tea.Cmd {
|
||||
m.focus = true
|
||||
return m.Cursor.Focus()
|
||||
}
|
||||
|
||||
// Blur removes the focus state on the model. When the model is blurred it can
|
||||
// not receive keyboard input and the cursor will be hidden.
|
||||
func (m *Model) Blur() {
|
||||
m.focus = false
|
||||
m.Cursor.Blur()
|
||||
}
|
||||
|
||||
// Reset sets the input to its default state with no input.
|
||||
func (m *Model) Reset() {
|
||||
m.value = nil
|
||||
m.SetCursor(0)
|
||||
}
|
||||
|
||||
// SetSuggestions sets the suggestions for the input.
|
||||
func (m *Model) SetSuggestions(suggestions []string) {
|
||||
m.suggestions = make([][]rune, len(suggestions))
|
||||
for i, s := range suggestions {
|
||||
m.suggestions[i] = []rune(s)
|
||||
}
|
||||
|
||||
m.updateSuggestions()
|
||||
}
|
||||
|
||||
// rsan initializes or retrieves the rune sanitizer.
|
||||
func (m *Model) san() runeutil.Sanitizer {
|
||||
if m.rsan == nil {
|
||||
// Textinput has all its input on a single line so collapse
|
||||
// newlines/tabs to single spaces.
|
||||
m.rsan = runeutil.NewSanitizer(
|
||||
runeutil.ReplaceTabs(" "), runeutil.ReplaceNewlines(" "))
|
||||
}
|
||||
return m.rsan
|
||||
}
|
||||
|
||||
func (m *Model) insertRunesFromUserInput(v []rune) {
|
||||
// Clean up any special characters in the input provided by the
|
||||
// clipboard. This avoids bugs due to e.g. tab characters and
|
||||
// whatnot.
|
||||
paste := m.san().Sanitize(v)
|
||||
|
||||
var availSpace int
|
||||
if m.CharLimit > 0 {
|
||||
availSpace = m.CharLimit - len(m.value)
|
||||
|
||||
// If the char limit's been reached, cancel.
|
||||
if availSpace <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// If there's not enough space to paste the whole thing cut the pasted
|
||||
// runes down so they'll fit.
|
||||
if availSpace < len(paste) {
|
||||
paste = paste[:availSpace]
|
||||
}
|
||||
}
|
||||
|
||||
// Stuff before and after the cursor
|
||||
head := m.value[:m.pos]
|
||||
tailSrc := m.value[m.pos:]
|
||||
tail := make([]rune, len(tailSrc))
|
||||
copy(tail, tailSrc)
|
||||
|
||||
// Insert pasted runes
|
||||
for _, r := range paste {
|
||||
head = append(head, r)
|
||||
m.pos++
|
||||
if m.CharLimit > 0 {
|
||||
availSpace--
|
||||
if availSpace <= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Put it all back together
|
||||
value := append(head, tail...)
|
||||
inputErr := m.validate(value)
|
||||
m.setValueInternal(value, inputErr)
|
||||
}
|
||||
|
||||
// If a max width is defined, perform some logic to treat the visible area
|
||||
// as a horizontally scrolling viewport.
|
||||
func (m *Model) handleOverflow() {
|
||||
if m.Width <= 0 || uniseg.StringWidth(string(m.value)) <= m.Width {
|
||||
m.offset = 0
|
||||
m.offsetRight = len(m.value)
|
||||
return
|
||||
}
|
||||
|
||||
// Correct right offset if we've deleted characters
|
||||
m.offsetRight = min(m.offsetRight, len(m.value))
|
||||
|
||||
if m.pos < m.offset {
|
||||
m.offset = m.pos
|
||||
|
||||
w := 0
|
||||
i := 0
|
||||
runes := m.value[m.offset:]
|
||||
|
||||
for i < len(runes) && w <= m.Width {
|
||||
w += rw.RuneWidth(runes[i])
|
||||
if w <= m.Width+1 {
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
m.offsetRight = m.offset + i
|
||||
} else if m.pos >= m.offsetRight {
|
||||
m.offsetRight = m.pos
|
||||
|
||||
w := 0
|
||||
runes := m.value[:m.offsetRight]
|
||||
i := len(runes) - 1
|
||||
|
||||
for i > 0 && w < m.Width {
|
||||
w += rw.RuneWidth(runes[i])
|
||||
if w <= m.Width {
|
||||
i--
|
||||
}
|
||||
}
|
||||
|
||||
m.offset = m.offsetRight - (len(runes) - 1 - i)
|
||||
}
|
||||
}
|
||||
|
||||
// deleteBeforeCursor deletes all text before the cursor.
|
||||
func (m *Model) deleteBeforeCursor() {
|
||||
m.value = m.value[m.pos:]
|
||||
m.Err = m.validate(m.value)
|
||||
m.offset = 0
|
||||
m.SetCursor(0)
|
||||
}
|
||||
|
||||
// deleteAfterCursor deletes all text after the cursor. If input is masked
|
||||
// delete everything after the cursor so as not to reveal word breaks in the
|
||||
// masked input.
|
||||
func (m *Model) deleteAfterCursor() {
|
||||
m.value = m.value[:m.pos]
|
||||
m.Err = m.validate(m.value)
|
||||
m.SetCursor(len(m.value))
|
||||
}
|
||||
|
||||
// deleteWordBackward deletes the word left to the cursor.
|
||||
func (m *Model) deleteWordBackward() {
|
||||
if m.pos == 0 || len(m.value) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if m.EchoMode != EchoNormal {
|
||||
m.deleteBeforeCursor()
|
||||
return
|
||||
}
|
||||
|
||||
// Linter note: it's critical that we acquire the initial cursor position
|
||||
// here prior to altering it via SetCursor() below. As such, moving this
|
||||
// call into the corresponding if clause does not apply here.
|
||||
oldPos := m.pos //nolint:ifshort
|
||||
|
||||
m.SetCursor(m.pos - 1)
|
||||
for unicode.IsSpace(m.value[m.pos]) {
|
||||
if m.pos <= 0 {
|
||||
break
|
||||
}
|
||||
// ignore series of whitespace before cursor
|
||||
m.SetCursor(m.pos - 1)
|
||||
}
|
||||
|
||||
for m.pos > 0 {
|
||||
if !unicode.IsSpace(m.value[m.pos]) {
|
||||
m.SetCursor(m.pos - 1)
|
||||
} else {
|
||||
if m.pos > 0 {
|
||||
// keep the previous space
|
||||
m.SetCursor(m.pos + 1)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if oldPos > len(m.value) {
|
||||
m.value = m.value[:m.pos]
|
||||
} else {
|
||||
m.value = append(m.value[:m.pos], m.value[oldPos:]...)
|
||||
}
|
||||
m.Err = m.validate(m.value)
|
||||
}
|
||||
|
||||
// deleteWordForward deletes the word right to the cursor. If input is masked
|
||||
// delete everything after the cursor so as not to reveal word breaks in the
|
||||
// masked input.
|
||||
func (m *Model) deleteWordForward() {
|
||||
if m.pos >= len(m.value) || len(m.value) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if m.EchoMode != EchoNormal {
|
||||
m.deleteAfterCursor()
|
||||
return
|
||||
}
|
||||
|
||||
oldPos := m.pos
|
||||
m.SetCursor(m.pos + 1)
|
||||
for unicode.IsSpace(m.value[m.pos]) {
|
||||
// ignore series of whitespace after cursor
|
||||
m.SetCursor(m.pos + 1)
|
||||
|
||||
if m.pos >= len(m.value) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for m.pos < len(m.value) {
|
||||
if !unicode.IsSpace(m.value[m.pos]) {
|
||||
m.SetCursor(m.pos + 1)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if m.pos > len(m.value) {
|
||||
m.value = m.value[:oldPos]
|
||||
} else {
|
||||
m.value = append(m.value[:oldPos], m.value[m.pos:]...)
|
||||
}
|
||||
m.Err = m.validate(m.value)
|
||||
|
||||
m.SetCursor(oldPos)
|
||||
}
|
||||
|
||||
// wordBackward moves the cursor one word to the left. If input is masked, move
|
||||
// input to the start so as not to reveal word breaks in the masked input.
|
||||
func (m *Model) wordBackward() {
|
||||
if m.pos == 0 || len(m.value) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if m.EchoMode != EchoNormal {
|
||||
m.CursorStart()
|
||||
return
|
||||
}
|
||||
|
||||
i := m.pos - 1
|
||||
for i >= 0 {
|
||||
if unicode.IsSpace(m.value[i]) {
|
||||
m.SetCursor(m.pos - 1)
|
||||
i--
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for i >= 0 {
|
||||
if !unicode.IsSpace(m.value[i]) {
|
||||
m.SetCursor(m.pos - 1)
|
||||
i--
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wordForward moves the cursor one word to the right. If the input is masked,
|
||||
// move input to the end so as not to reveal word breaks in the masked input.
|
||||
func (m *Model) wordForward() {
|
||||
if m.pos >= len(m.value) || len(m.value) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if m.EchoMode != EchoNormal {
|
||||
m.CursorEnd()
|
||||
return
|
||||
}
|
||||
|
||||
i := m.pos
|
||||
for i < len(m.value) {
|
||||
if unicode.IsSpace(m.value[i]) {
|
||||
m.SetCursor(m.pos + 1)
|
||||
i++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for i < len(m.value) {
|
||||
if !unicode.IsSpace(m.value[i]) {
|
||||
m.SetCursor(m.pos + 1)
|
||||
i++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m Model) echoTransform(v string) string {
|
||||
switch m.EchoMode {
|
||||
case EchoPassword:
|
||||
return strings.Repeat(string(m.EchoCharacter), uniseg.StringWidth(v))
|
||||
case EchoNone:
|
||||
return ""
|
||||
case EchoNormal:
|
||||
return v
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// Update is the Bubble Tea update loop.
|
||||
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
if !m.focus {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Need to check for completion before, because key is configurable and might be double assigned
|
||||
keyMsg, ok := msg.(tea.KeyMsg)
|
||||
if ok && key.Matches(keyMsg, m.KeyMap.AcceptSuggestion) {
|
||||
if m.canAcceptSuggestion() {
|
||||
m.value = append(m.value, m.matchedSuggestions[m.currentSuggestionIndex][len(m.value):]...)
|
||||
m.CursorEnd()
|
||||
}
|
||||
}
|
||||
|
||||
// Let's remember where the position of the cursor currently is so that if
|
||||
// the cursor position changes, we can reset the blink.
|
||||
oldPos := m.pos
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch {
|
||||
case key.Matches(msg, m.KeyMap.DeleteWordBackward):
|
||||
m.deleteWordBackward()
|
||||
case key.Matches(msg, m.KeyMap.DeleteCharacterBackward):
|
||||
m.Err = nil
|
||||
if len(m.value) > 0 {
|
||||
m.value = append(m.value[:max(0, m.pos-1)], m.value[m.pos:]...)
|
||||
m.Err = m.validate(m.value)
|
||||
if m.pos > 0 {
|
||||
m.SetCursor(m.pos - 1)
|
||||
}
|
||||
}
|
||||
case key.Matches(msg, m.KeyMap.WordBackward):
|
||||
m.wordBackward()
|
||||
case key.Matches(msg, m.KeyMap.CharacterBackward):
|
||||
if m.pos > 0 {
|
||||
m.SetCursor(m.pos - 1)
|
||||
}
|
||||
case key.Matches(msg, m.KeyMap.WordForward):
|
||||
m.wordForward()
|
||||
case key.Matches(msg, m.KeyMap.CharacterForward):
|
||||
if m.pos < len(m.value) {
|
||||
m.SetCursor(m.pos + 1)
|
||||
}
|
||||
case key.Matches(msg, m.KeyMap.LineStart):
|
||||
m.CursorStart()
|
||||
case key.Matches(msg, m.KeyMap.DeleteCharacterForward):
|
||||
if len(m.value) > 0 && m.pos < len(m.value) {
|
||||
m.value = append(m.value[:m.pos], m.value[m.pos+1:]...)
|
||||
m.Err = m.validate(m.value)
|
||||
}
|
||||
case key.Matches(msg, m.KeyMap.LineEnd):
|
||||
m.CursorEnd()
|
||||
case key.Matches(msg, m.KeyMap.DeleteAfterCursor):
|
||||
m.deleteAfterCursor()
|
||||
case key.Matches(msg, m.KeyMap.DeleteBeforeCursor):
|
||||
m.deleteBeforeCursor()
|
||||
case key.Matches(msg, m.KeyMap.Paste):
|
||||
return m, Paste
|
||||
case key.Matches(msg, m.KeyMap.DeleteWordForward):
|
||||
m.deleteWordForward()
|
||||
case key.Matches(msg, m.KeyMap.NextSuggestion):
|
||||
m.nextSuggestion()
|
||||
case key.Matches(msg, m.KeyMap.PrevSuggestion):
|
||||
m.previousSuggestion()
|
||||
default:
|
||||
// Input one or more regular characters.
|
||||
m.insertRunesFromUserInput(msg.Runes)
|
||||
}
|
||||
|
||||
// Check again if can be completed
|
||||
// because value might be something that does not match the completion prefix
|
||||
m.updateSuggestions()
|
||||
|
||||
case pasteMsg:
|
||||
m.insertRunesFromUserInput([]rune(msg))
|
||||
|
||||
case pasteErrMsg:
|
||||
m.Err = msg
|
||||
}
|
||||
|
||||
var cmds []tea.Cmd
|
||||
var cmd tea.Cmd
|
||||
|
||||
m.Cursor, cmd = m.Cursor.Update(msg)
|
||||
cmds = append(cmds, cmd)
|
||||
|
||||
if oldPos != m.pos && m.Cursor.Mode() == cursor.CursorBlink {
|
||||
m.Cursor.Blink = false
|
||||
cmds = append(cmds, m.Cursor.BlinkCmd())
|
||||
}
|
||||
|
||||
m.handleOverflow()
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
// View renders the textinput in its current state.
|
||||
func (m Model) View() string {
|
||||
// Placeholder text
|
||||
if len(m.value) == 0 && m.Placeholder != "" {
|
||||
return m.placeholderView()
|
||||
}
|
||||
|
||||
styleText := m.TextStyle.Inline(true).Render
|
||||
|
||||
value := m.value[m.offset:m.offsetRight]
|
||||
pos := max(0, m.pos-m.offset)
|
||||
v := styleText(m.echoTransform(string(value[:pos])))
|
||||
|
||||
if pos < len(value) { //nolint:nestif
|
||||
char := m.echoTransform(string(value[pos]))
|
||||
m.Cursor.SetChar(char)
|
||||
v += m.Cursor.View() // cursor and text under it
|
||||
v += styleText(m.echoTransform(string(value[pos+1:]))) // text after cursor
|
||||
v += m.completionView(0) // suggested completion
|
||||
} else {
|
||||
if m.focus && m.canAcceptSuggestion() {
|
||||
suggestion := m.matchedSuggestions[m.currentSuggestionIndex]
|
||||
if len(value) < len(suggestion) {
|
||||
m.Cursor.TextStyle = m.CompletionStyle
|
||||
m.Cursor.SetChar(m.echoTransform(string(suggestion[pos])))
|
||||
v += m.Cursor.View()
|
||||
v += m.completionView(1)
|
||||
} else {
|
||||
m.Cursor.SetChar(" ")
|
||||
v += m.Cursor.View()
|
||||
}
|
||||
} else {
|
||||
m.Cursor.SetChar(" ")
|
||||
v += m.Cursor.View()
|
||||
}
|
||||
}
|
||||
|
||||
// If a max width and background color were set fill the empty spaces with
|
||||
// the background color.
|
||||
valWidth := uniseg.StringWidth(string(value))
|
||||
if m.Width > 0 && valWidth <= m.Width {
|
||||
padding := max(0, m.Width-valWidth)
|
||||
if valWidth+padding <= m.Width && pos < len(value) {
|
||||
padding++
|
||||
}
|
||||
v += styleText(strings.Repeat(" ", padding))
|
||||
}
|
||||
|
||||
return m.PromptStyle.Render(m.Prompt) + v
|
||||
}
|
||||
|
||||
// placeholderView returns the prompt and placeholder view, if any.
|
||||
func (m Model) placeholderView() string {
|
||||
var (
|
||||
v string
|
||||
style = m.PlaceholderStyle.Inline(true).Render
|
||||
)
|
||||
|
||||
p := make([]rune, m.Width+1)
|
||||
copy(p, []rune(m.Placeholder))
|
||||
|
||||
m.Cursor.TextStyle = m.PlaceholderStyle
|
||||
m.Cursor.SetChar(string(p[:1]))
|
||||
v += m.Cursor.View()
|
||||
|
||||
// If the entire placeholder is already set and no padding is needed, finish
|
||||
if m.Width < 1 && len(p) <= 1 {
|
||||
return m.PromptStyle.Render(m.Prompt) + v
|
||||
}
|
||||
|
||||
// If Width is set then size placeholder accordingly
|
||||
if m.Width > 0 {
|
||||
// available width is width - len + cursor offset of 1
|
||||
minWidth := lipgloss.Width(m.Placeholder)
|
||||
availWidth := m.Width - minWidth + 1
|
||||
|
||||
// if width < len, 'subtract'(add) number to len and dont add padding
|
||||
if availWidth < 0 {
|
||||
minWidth += availWidth
|
||||
availWidth = 0
|
||||
}
|
||||
// append placeholder[len] - cursor, append padding
|
||||
v += style(string(p[1:minWidth]))
|
||||
v += style(strings.Repeat(" ", availWidth))
|
||||
} else {
|
||||
// if there is no width, the placeholder can be any length
|
||||
v += style(string(p[1:]))
|
||||
}
|
||||
|
||||
return m.PromptStyle.Render(m.Prompt) + v
|
||||
}
|
||||
|
||||
// Blink is a command used to initialize cursor blinking.
|
||||
func Blink() tea.Msg {
|
||||
return cursor.Blink()
|
||||
}
|
||||
|
||||
// Paste is a command for pasting from the clipboard into the text input.
|
||||
func Paste() tea.Msg {
|
||||
str, err := clipboard.ReadAll()
|
||||
if err != nil {
|
||||
return pasteErrMsg{err}
|
||||
}
|
||||
return pasteMsg(str)
|
||||
}
|
||||
|
||||
func clamp(v, low, high int) int {
|
||||
if high < low {
|
||||
low, high = high, low
|
||||
}
|
||||
return min(high, max(low, v))
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
|
||||
// Deprecated: use [cursor.Mode].
|
||||
//
|
||||
//nolint:revive
|
||||
type CursorMode int
|
||||
|
||||
//nolint:revive
|
||||
const (
|
||||
// Deprecated: use [cursor.CursorBlink].
|
||||
CursorBlink = CursorMode(cursor.CursorBlink)
|
||||
// Deprecated: use [cursor.CursorStatic].
|
||||
CursorStatic = CursorMode(cursor.CursorStatic)
|
||||
// Deprecated: use [cursor.CursorHide].
|
||||
CursorHide = CursorMode(cursor.CursorHide)
|
||||
)
|
||||
|
||||
func (c CursorMode) String() string {
|
||||
return cursor.Mode(c).String()
|
||||
}
|
||||
|
||||
// Deprecated: use [cursor.Mode].
|
||||
//
|
||||
//nolint:revive
|
||||
func (m Model) CursorMode() CursorMode {
|
||||
return CursorMode(m.Cursor.Mode())
|
||||
}
|
||||
|
||||
// Deprecated: use cursor.SetMode().
|
||||
//
|
||||
//nolint:revive
|
||||
func (m *Model) SetCursorMode(mode CursorMode) tea.Cmd {
|
||||
return m.Cursor.SetMode(cursor.Mode(mode))
|
||||
}
|
||||
|
||||
func (m Model) completionView(offset int) string {
|
||||
var (
|
||||
value = m.value
|
||||
style = m.PlaceholderStyle.Inline(true).Render
|
||||
)
|
||||
|
||||
if m.canAcceptSuggestion() {
|
||||
suggestion := m.matchedSuggestions[m.currentSuggestionIndex]
|
||||
if len(value) < len(suggestion) {
|
||||
return style(string(suggestion[len(value)+offset:]))
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Model) getSuggestions(sugs [][]rune) []string {
|
||||
suggestions := make([]string, len(sugs))
|
||||
for i, s := range sugs {
|
||||
suggestions[i] = string(s)
|
||||
}
|
||||
return suggestions
|
||||
}
|
||||
|
||||
// AvailableSuggestions returns the list of available suggestions.
|
||||
func (m *Model) AvailableSuggestions() []string {
|
||||
return m.getSuggestions(m.suggestions)
|
||||
}
|
||||
|
||||
// MatchedSuggestions returns the list of matched suggestions.
|
||||
func (m *Model) MatchedSuggestions() []string {
|
||||
return m.getSuggestions(m.matchedSuggestions)
|
||||
}
|
||||
|
||||
// CurrentSuggestionIndex returns the currently selected suggestion index.
|
||||
func (m *Model) CurrentSuggestionIndex() int {
|
||||
return m.currentSuggestionIndex
|
||||
}
|
||||
|
||||
// CurrentSuggestion returns the currently selected suggestion.
|
||||
func (m *Model) CurrentSuggestion() string {
|
||||
if m.currentSuggestionIndex >= len(m.matchedSuggestions) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return string(m.matchedSuggestions[m.currentSuggestionIndex])
|
||||
}
|
||||
|
||||
// canAcceptSuggestion returns whether there is an acceptable suggestion to
|
||||
// autocomplete the current value.
|
||||
func (m *Model) canAcceptSuggestion() bool {
|
||||
return len(m.matchedSuggestions) > 0
|
||||
}
|
||||
|
||||
// updateSuggestions refreshes the list of matching suggestions.
|
||||
func (m *Model) updateSuggestions() {
|
||||
if !m.ShowSuggestions {
|
||||
return
|
||||
}
|
||||
|
||||
if len(m.value) <= 0 || len(m.suggestions) <= 0 {
|
||||
m.matchedSuggestions = [][]rune{}
|
||||
return
|
||||
}
|
||||
|
||||
matches := [][]rune{}
|
||||
for _, s := range m.suggestions {
|
||||
suggestion := string(s)
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(suggestion), strings.ToLower(string(m.value))) {
|
||||
matches = append(matches, []rune(suggestion))
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(matches, m.matchedSuggestions) {
|
||||
m.currentSuggestionIndex = 0
|
||||
}
|
||||
|
||||
m.matchedSuggestions = matches
|
||||
}
|
||||
|
||||
// nextSuggestion selects the next suggestion.
|
||||
func (m *Model) nextSuggestion() {
|
||||
m.currentSuggestionIndex = (m.currentSuggestionIndex + 1)
|
||||
if m.currentSuggestionIndex >= len(m.matchedSuggestions) {
|
||||
m.currentSuggestionIndex = 0
|
||||
}
|
||||
}
|
||||
|
||||
// previousSuggestion selects the previous suggestion.
|
||||
func (m *Model) previousSuggestion() {
|
||||
m.currentSuggestionIndex = (m.currentSuggestionIndex - 1)
|
||||
if m.currentSuggestionIndex < 0 {
|
||||
m.currentSuggestionIndex = len(m.matchedSuggestions) - 1
|
||||
}
|
||||
}
|
||||
|
||||
func (m Model) validate(v []rune) error {
|
||||
if m.Validate != nil {
|
||||
return m.Validate(string(v))
|
||||
}
|
||||
return nil
|
||||
}
|
4
vendor/github.com/charmbracelet/bubbletea/.golangci.yml
generated
vendored
4
vendor/github.com/charmbracelet/bubbletea/.golangci.yml
generated
vendored
@ -26,6 +26,10 @@ linters:
|
||||
- whitespace
|
||||
- wrapcheck
|
||||
exclusions:
|
||||
rules:
|
||||
- text: '(slog|log)\.\w+'
|
||||
linters:
|
||||
- noctx
|
||||
generated: lax
|
||||
presets:
|
||||
- common-false-positives
|
||||
|
2
vendor/github.com/charmbracelet/bubbletea/LICENSE
generated
vendored
2
vendor/github.com/charmbracelet/bubbletea/LICENSE
generated
vendored
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2023 Charmbracelet, Inc
|
||||
Copyright (c) 2020-2025 Charmbracelet, Inc
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
4
vendor/github.com/charmbracelet/bubbletea/README.md
generated
vendored
4
vendor/github.com/charmbracelet/bubbletea/README.md
generated
vendored
@ -9,7 +9,7 @@
|
||||
<br>
|
||||
<a href="https://github.com/charmbracelet/bubbletea/releases"><img src="https://img.shields.io/github/release/charmbracelet/bubbletea.svg" alt="Latest Release"></a>
|
||||
<a href="https://pkg.go.dev/github.com/charmbracelet/bubbletea?tab=doc"><img src="https://godoc.org/github.com/charmbracelet/bubbletea?status.svg" alt="GoDoc"></a>
|
||||
<a href="https://github.com/charmbracelet/bubbletea/actions"><img src="https://github.com/charmbracelet/bubbletea/actions/workflows/build.yml/badge.svg" alt="Build Status"></a>
|
||||
<a href="https://github.com/charmbracelet/bubbletea/actions"><img src="https://github.com/charmbracelet/bubbletea/actions/workflows/build.yml/badge.svg?branch=main" alt="Build Status"></a>
|
||||
</p>
|
||||
|
||||
The fun, functional and stateful way to build terminal apps. A Go framework
|
||||
@ -395,6 +395,6 @@ of days past.
|
||||
|
||||
Part of [Charm](https://charm.sh).
|
||||
|
||||
<a href="https://charm.sh/"><img alt="The Charm logo" src="https://stuff.charm.sh/charm-badge.jpg" width="400"></a>
|
||||
<a href="https://charm.sh/"><img alt="The Charm logo" src="https://stuff.charm.sh/charm-banner-next.jpg" width="400"></a>
|
||||
|
||||
Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة
|
||||
|
38
vendor/github.com/charmbracelet/bubbletea/commands.go
generated
vendored
38
vendor/github.com/charmbracelet/bubbletea/commands.go
generated
vendored
@ -13,6 +13,27 @@ import (
|
||||
// return tea.Batch(someCommand, someOtherCommand)
|
||||
// }
|
||||
func Batch(cmds ...Cmd) Cmd {
|
||||
return compactCmds[BatchMsg](cmds)
|
||||
}
|
||||
|
||||
// BatchMsg is a message used to perform a bunch of commands concurrently with
|
||||
// no ordering guarantees. You can send a BatchMsg with Batch.
|
||||
type BatchMsg []Cmd
|
||||
|
||||
// Sequence runs the given commands one at a time, in order. Contrast this with
|
||||
// Batch, which runs commands concurrently.
|
||||
func Sequence(cmds ...Cmd) Cmd {
|
||||
return compactCmds[sequenceMsg](cmds)
|
||||
}
|
||||
|
||||
// sequenceMsg is used internally to run the given commands in order.
|
||||
type sequenceMsg []Cmd
|
||||
|
||||
// compactCmds ignores any nil commands in cmds, and returns the most direct
|
||||
// command possible. That is, considering the non-nil commands, if there are
|
||||
// none it returns nil, if there is exactly one it returns that command
|
||||
// directly, else it returns the non-nil commands as type T.
|
||||
func compactCmds[T ~[]Cmd](cmds []Cmd) Cmd {
|
||||
var validCmds []Cmd //nolint:prealloc
|
||||
for _, c := range cmds {
|
||||
if c == nil {
|
||||
@ -27,26 +48,11 @@ func Batch(cmds ...Cmd) Cmd {
|
||||
return validCmds[0]
|
||||
default:
|
||||
return func() Msg {
|
||||
return BatchMsg(validCmds)
|
||||
return T(validCmds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BatchMsg is a message used to perform a bunch of commands concurrently with
|
||||
// no ordering guarantees. You can send a BatchMsg with Batch.
|
||||
type BatchMsg []Cmd
|
||||
|
||||
// Sequence runs the given commands one at a time, in order. Contrast this with
|
||||
// Batch, which runs commands concurrently.
|
||||
func Sequence(cmds ...Cmd) Cmd {
|
||||
return func() Msg {
|
||||
return sequenceMsg(cmds)
|
||||
}
|
||||
}
|
||||
|
||||
// sequenceMsg is used internally to run the given commands in order.
|
||||
type sequenceMsg []Cmd
|
||||
|
||||
// Every is a command that ticks in sync with the system clock. So, if you
|
||||
// wanted to tick with the system clock every second, minute or hour you
|
||||
// could use this. It's also handy for having different things tick in sync.
|
||||
|
2
vendor/github.com/charmbracelet/bubbletea/inputreader_windows.go
generated
vendored
2
vendor/github.com/charmbracelet/bubbletea/inputreader_windows.go
generated
vendored
@ -108,7 +108,7 @@ func prepareConsole(input windows.Handle, modes ...uint32) (originalMode uint32,
|
||||
return originalMode, nil
|
||||
}
|
||||
|
||||
// cancelMixin represents a goroutine-safe cancelation status.
|
||||
// cancelMixin represents a goroutine-safe cancellation status.
|
||||
type cancelMixin struct {
|
||||
unsafeCanceled bool
|
||||
lock sync.Mutex
|
||||
|
16
vendor/github.com/charmbracelet/bubbletea/key_windows.go
generated
vendored
16
vendor/github.com/charmbracelet/bubbletea/key_windows.go
generated
vendored
@ -109,12 +109,12 @@ func peekAndReadConsInput(con *conInputReader) ([]coninput.InputRecord, error) {
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// Convert i to unit32 or panic if it cannot be converted. Check satisifes lint G115.
|
||||
// Convert i to unit32 or panic if it cannot be converted. Check satisfies lint G115.
|
||||
func intToUint32OrDie(i int) uint32 {
|
||||
if i < 0 {
|
||||
panic("cannot convert numEvents " + fmt.Sprint(i) + " to uint32")
|
||||
}
|
||||
return uint32(i)
|
||||
return uint32(i) //nolint:gosec
|
||||
}
|
||||
|
||||
// Keeps peeking until there is data or the input is cancelled.
|
||||
@ -158,16 +158,16 @@ func mouseEventButton(p, s coninput.ButtonState) (button MouseButton, action Mou
|
||||
return button, action
|
||||
}
|
||||
|
||||
switch {
|
||||
case btn == coninput.FROM_LEFT_1ST_BUTTON_PRESSED: // left button
|
||||
switch btn {
|
||||
case coninput.FROM_LEFT_1ST_BUTTON_PRESSED: // left button
|
||||
button = MouseButtonLeft
|
||||
case btn == coninput.RIGHTMOST_BUTTON_PRESSED: // right button
|
||||
case coninput.RIGHTMOST_BUTTON_PRESSED: // right button
|
||||
button = MouseButtonRight
|
||||
case btn == coninput.FROM_LEFT_2ND_BUTTON_PRESSED: // middle button
|
||||
case coninput.FROM_LEFT_2ND_BUTTON_PRESSED: // middle button
|
||||
button = MouseButtonMiddle
|
||||
case btn == coninput.FROM_LEFT_3RD_BUTTON_PRESSED: // unknown (possibly mouse backward)
|
||||
case coninput.FROM_LEFT_3RD_BUTTON_PRESSED: // unknown (possibly mouse backward)
|
||||
button = MouseButtonBackward
|
||||
case btn == coninput.FROM_LEFT_4TH_BUTTON_PRESSED: // unknown (possibly mouse forward)
|
||||
case coninput.FROM_LEFT_4TH_BUTTON_PRESSED: // unknown (possibly mouse forward)
|
||||
button = MouseButtonForward
|
||||
}
|
||||
|
||||
|
2
vendor/github.com/charmbracelet/bubbletea/screen.go
generated
vendored
2
vendor/github.com/charmbracelet/bubbletea/screen.go
generated
vendored
@ -131,7 +131,7 @@ func EnableBracketedPaste() Msg {
|
||||
type enableBracketedPasteMsg struct{}
|
||||
|
||||
// DisableBracketedPaste is a special command that tells the Bubble Tea program
|
||||
// to accept bracketed paste input.
|
||||
// to stop processing bracketed paste input.
|
||||
//
|
||||
// Note that bracketed paste will be automatically disabled when the
|
||||
// program quits.
|
||||
|
2
vendor/github.com/charmbracelet/bubbletea/standard_renderer.go
generated
vendored
2
vendor/github.com/charmbracelet/bubbletea/standard_renderer.go
generated
vendored
@ -277,7 +277,7 @@ func (r *standardRenderer) flush() {
|
||||
// using the full terminal window.
|
||||
buf.WriteString(ansi.CursorPosition(0, len(newLines)))
|
||||
} else {
|
||||
buf.WriteString(ansi.CursorBackward(r.width))
|
||||
buf.WriteByte('\r')
|
||||
}
|
||||
|
||||
_, _ = r.out.Write(buf.Bytes())
|
||||
|
107
vendor/github.com/charmbracelet/bubbletea/tea.go
generated
vendored
107
vendor/github.com/charmbracelet/bubbletea/tea.go
generated
vendored
@ -24,7 +24,6 @@ import (
|
||||
|
||||
"github.com/charmbracelet/x/term"
|
||||
"github.com/muesli/cancelreader"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// ErrProgramPanic is returned by [Program.Run] when the program recovers from a panic.
|
||||
@ -73,7 +72,7 @@ const (
|
||||
customInput
|
||||
)
|
||||
|
||||
// String implements the stringer interface for [inputType]. It is inteded to
|
||||
// String implements the stringer interface for [inputType]. It is intended to
|
||||
// be used in testing.
|
||||
func (i inputType) String() string {
|
||||
return [...]string{
|
||||
@ -220,7 +219,7 @@ func Suspend() Msg {
|
||||
// You can send this message with [Suspend()].
|
||||
type SuspendMsg struct{}
|
||||
|
||||
// ResumeMsg can be listen to to do something once a program is resumed back
|
||||
// ResumeMsg can be listen to do something once a program is resumed back
|
||||
// from a suspend state.
|
||||
type ResumeMsg struct{}
|
||||
|
||||
@ -472,42 +471,12 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
|
||||
p.exec(msg.cmd, msg.fn)
|
||||
|
||||
case BatchMsg:
|
||||
for _, cmd := range msg {
|
||||
select {
|
||||
case <-p.ctx.Done():
|
||||
return model, nil
|
||||
case cmds <- cmd:
|
||||
}
|
||||
}
|
||||
go p.execBatchMsg(msg)
|
||||
continue
|
||||
|
||||
case sequenceMsg:
|
||||
go func() {
|
||||
// Execute commands one at a time, in order.
|
||||
for _, cmd := range msg {
|
||||
if cmd == nil {
|
||||
go p.execSequenceMsg(msg)
|
||||
continue
|
||||
}
|
||||
|
||||
msg := cmd()
|
||||
if batchMsg, ok := msg.(BatchMsg); ok {
|
||||
g, _ := errgroup.WithContext(p.ctx)
|
||||
for _, cmd := range batchMsg {
|
||||
cmd := cmd
|
||||
g.Go(func() error {
|
||||
p.Send(cmd())
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
//nolint:errcheck,gosec
|
||||
g.Wait() // wait for all commands from batch msg to finish
|
||||
continue
|
||||
}
|
||||
|
||||
p.Send(msg)
|
||||
}
|
||||
}()
|
||||
|
||||
case setWindowTitleMsg:
|
||||
p.SetWindowTitle(string(msg))
|
||||
@ -535,6 +504,74 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Program) execSequenceMsg(msg sequenceMsg) {
|
||||
if !p.startupOptions.has(withoutCatchPanics) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
p.recoverFromGoPanic(r)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Execute commands one at a time, in order.
|
||||
for _, cmd := range msg {
|
||||
if cmd == nil {
|
||||
continue
|
||||
}
|
||||
msg := cmd()
|
||||
switch msg := msg.(type) {
|
||||
case BatchMsg:
|
||||
p.execBatchMsg(msg)
|
||||
case sequenceMsg:
|
||||
p.execSequenceMsg(msg)
|
||||
default:
|
||||
p.Send(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Program) execBatchMsg(msg BatchMsg) {
|
||||
if !p.startupOptions.has(withoutCatchPanics) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
p.recoverFromGoPanic(r)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Execute commands one at a time.
|
||||
var wg sync.WaitGroup
|
||||
for _, cmd := range msg {
|
||||
if cmd == nil {
|
||||
continue
|
||||
}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
if !p.startupOptions.has(withoutCatchPanics) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
p.recoverFromGoPanic(r)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
msg := cmd()
|
||||
switch msg := msg.(type) {
|
||||
case BatchMsg:
|
||||
p.execBatchMsg(msg)
|
||||
case sequenceMsg:
|
||||
p.execSequenceMsg(msg)
|
||||
default:
|
||||
p.Send(msg)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait() // wait for all commands from batch msg to finish
|
||||
}
|
||||
|
||||
// Run initializes the program and runs its event loops, blocking until it gets
|
||||
// terminated by either [Program.Quit], [Program.Kill], or its signal handler.
|
||||
// Returns the final model.
|
||||
|
2
vendor/github.com/charmbracelet/bubbletea/tty_windows.go
generated
vendored
2
vendor/github.com/charmbracelet/bubbletea/tty_windows.go
generated
vendored
@ -56,7 +56,7 @@ func (p *Program) initInput() (err error) {
|
||||
|
||||
// Open the Windows equivalent of a TTY.
|
||||
func openInputTTY() (*os.File, error) {
|
||||
f, err := os.OpenFile("CONIN$", os.O_RDWR, 0o644)
|
||||
f, err := os.OpenFile("CONIN$", os.O_RDWR, 0o644) //nolint:gosec
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error opening file: %w", err)
|
||||
}
|
||||
|
24
vendor/github.com/charmbracelet/x/ansi/inband.go
generated
vendored
Normal file
24
vendor/github.com/charmbracelet/x/ansi/inband.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
package ansi
|
||||
|
||||
import "fmt"
|
||||
|
||||
// InBandResize encodes an in-band terminal resize event sequence.
|
||||
//
|
||||
// CSI 48 ; height_cells ; widht_cells ; height_pixels ; width_pixels t
|
||||
//
|
||||
// See https://gist.github.com/rockorager/e695fb2924d36b2bcf1fff4a3704bd83
|
||||
func InBandResize(heightCells, widthCells, heightPixels, widthPixels int) string {
|
||||
if heightCells < 0 {
|
||||
heightCells = 0
|
||||
}
|
||||
if widthCells < 0 {
|
||||
widthCells = 0
|
||||
}
|
||||
if heightPixels < 0 {
|
||||
heightPixels = 0
|
||||
}
|
||||
if widthPixels < 0 {
|
||||
widthPixels = 0
|
||||
}
|
||||
return fmt.Sprintf("\x1b[48;%d;%d;%d;%dt", heightCells, widthCells, heightPixels, widthPixels)
|
||||
}
|
34
vendor/github.com/charmbracelet/x/ansi/palette.go
generated
vendored
Normal file
34
vendor/github.com/charmbracelet/x/ansi/palette.go
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
package ansi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
// SetPalette sets the palette color for the given index. The index is a 16
|
||||
// color index between 0 and 15. The color is a 24-bit RGB color.
|
||||
//
|
||||
// OSC P n rrggbb BEL
|
||||
//
|
||||
// Where n is the color index in hex (0-f), and rrggbb is the color in
|
||||
// hexadecimal format (e.g., ff0000 for red).
|
||||
//
|
||||
// This sequence is specific to the Linux Console and may not work in other
|
||||
// terminal emulators.
|
||||
//
|
||||
// See https://man7.org/linux/man-pages/man4/console_codes.4.html
|
||||
func SetPalette(i int, c color.Color) string {
|
||||
if c == nil || i < 0 || i > 15 {
|
||||
return ""
|
||||
}
|
||||
r, g, b, _ := c.RGBA()
|
||||
return fmt.Sprintf("\x1b]P%x%02x%02x%02x\x07", i, r>>8, g>>8, b>>8)
|
||||
}
|
||||
|
||||
// ResetPalette resets the color palette to the default values.
|
||||
//
|
||||
// This sequence is specific to the Linux Console and may not work in other
|
||||
// terminal emulators.
|
||||
//
|
||||
// See https://man7.org/linux/man-pages/man4/console_codes.4.html
|
||||
const ResetPalette = "\x1b]R\x07"
|
49
vendor/github.com/charmbracelet/x/ansi/progress.go
generated
vendored
Normal file
49
vendor/github.com/charmbracelet/x/ansi/progress.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
package ansi
|
||||
|
||||
import "strconv"
|
||||
|
||||
// ResetProgressBar is a sequence that resets the progress bar to its default
|
||||
// state (hidden).
|
||||
//
|
||||
// OSC 9 ; 4 ; 0 BEL
|
||||
//
|
||||
// See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences
|
||||
const ResetProgressBar = "\x1b]9;4;0\x07"
|
||||
|
||||
// SetProgressBar returns a sequence for setting the progress bar to a specific
|
||||
// percentage (0-100) in the "default" state.
|
||||
//
|
||||
// OSC 9 ; 4 ; 1 Percentage BEL
|
||||
//
|
||||
// See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences
|
||||
func SetProgressBar(percentage int) string {
|
||||
return "\x1b]9;4;1;" + strconv.Itoa(min(max(0, percentage), 100)) + "\x07"
|
||||
}
|
||||
|
||||
// SetErrorProgressBar returns a sequence for setting the progress bar to a
|
||||
// specific percentage (0-100) in the "Error" state..
|
||||
//
|
||||
// OSC 9 ; 4 ; 2 Percentage BEL
|
||||
//
|
||||
// See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences
|
||||
func SetErrorProgressBar(percentage int) string {
|
||||
return "\x1b]9;4;2;" + strconv.Itoa(min(max(0, percentage), 100)) + "\x07"
|
||||
}
|
||||
|
||||
// SetIndeterminateProgressBar is a sequence that sets the progress bar to the
|
||||
// indeterminate state.
|
||||
//
|
||||
// OSC 9 ; 4 ; 3 BEL
|
||||
//
|
||||
// See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences
|
||||
const SetIndeterminateProgressBar = "\x1b]9;4;3\x07"
|
||||
|
||||
// SetWarningProgressBar is a sequence that sets the progress bar to the
|
||||
// "Warning" state.
|
||||
//
|
||||
// OSC 9 ; 4 ; 4 Percentage BEL
|
||||
//
|
||||
// See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences
|
||||
func SetWarningProgressBar(percentage int) string {
|
||||
return "\x1b]9;4;4;" + strconv.Itoa(min(max(0, percentage), 100)) + "\x07"
|
||||
}
|
6
vendor/github.com/charmbracelet/x/ansi/winop.go
generated
vendored
6
vendor/github.com/charmbracelet/x/ansi/winop.go
generated
vendored
@ -8,16 +8,22 @@ import (
|
||||
const (
|
||||
// ResizeWindowWinOp is a window operation that resizes the terminal
|
||||
// window.
|
||||
//
|
||||
// Deprecated: Use constant number directly with [WindowOp].
|
||||
ResizeWindowWinOp = 4
|
||||
|
||||
// RequestWindowSizeWinOp is a window operation that requests a report of
|
||||
// the size of the terminal window in pixels. The response is in the form:
|
||||
// CSI 4 ; height ; width t
|
||||
//
|
||||
// Deprecated: Use constant number directly with [WindowOp].
|
||||
RequestWindowSizeWinOp = 14
|
||||
|
||||
// RequestCellSizeWinOp is a window operation that requests a report of
|
||||
// the size of the terminal cell size in pixels. The response is in the form:
|
||||
// CSI 6 ; height ; width t
|
||||
//
|
||||
// Deprecated: Use constant number directly with [WindowOp].
|
||||
RequestCellSizeWinOp = 16
|
||||
)
|
||||
|
||||
|
21
vendor/github.com/clipperhouse/uax29/v2/LICENSE
generated
vendored
Normal file
21
vendor/github.com/clipperhouse/uax29/v2/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Matt Sherman
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
82
vendor/github.com/clipperhouse/uax29/v2/graphemes/README.md
generated
vendored
Normal file
82
vendor/github.com/clipperhouse/uax29/v2/graphemes/README.md
generated
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
An implementation of grapheme cluster boundaries from [Unicode text segmentation](https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries) (UAX 29), for Unicode version 15.0.0.
|
||||
|
||||
## Quick start
|
||||
|
||||
```
|
||||
go get "github.com/clipperhouse/uax29/v2/graphemes"
|
||||
```
|
||||
|
||||
```go
|
||||
import "github.com/clipperhouse/uax29/v2/graphemes"
|
||||
|
||||
text := "Hello, 世界. Nice dog! 👍🐶"
|
||||
|
||||
tokens := graphemes.FromString(text)
|
||||
|
||||
for tokens.Next() { // Next() returns true until end of data
|
||||
fmt.Println(tokens.Value()) // Do something with the current grapheme
|
||||
}
|
||||
```
|
||||
|
||||
[](https://pkg.go.dev/github.com/clipperhouse/uax29/v2/graphemes)
|
||||
|
||||
_A grapheme is a “single visible character”, which might be a simple as a single letter, or a complex emoji that consists of several Unicode code points._
|
||||
|
||||
## Conformance
|
||||
|
||||
We use the Unicode [test suite](https://unicode.org/reports/tr41/tr41-26.html#Tests29). Status:
|
||||
|
||||

|
||||
|
||||
## APIs
|
||||
|
||||
### If you have a `string`
|
||||
|
||||
```go
|
||||
text := "Hello, 世界. Nice dog! 👍🐶"
|
||||
|
||||
tokens := graphemes.FromString(text)
|
||||
|
||||
for tokens.Next() { // Next() returns true until end of data
|
||||
fmt.Println(tokens.Value()) // Do something with the current grapheme
|
||||
}
|
||||
```
|
||||
|
||||
### If you have an `io.Reader`
|
||||
|
||||
`FromReader` embeds a [`bufio.Scanner`](https://pkg.go.dev/bufio#Scanner), so just use those methods.
|
||||
|
||||
```go
|
||||
r := getYourReader() // from a file or network maybe
|
||||
tokens := graphemes.FromReader(r)
|
||||
|
||||
for tokens.Scan() { // Scan() returns true until error or EOF
|
||||
fmt.Println(tokens.Text()) // Do something with the current grapheme
|
||||
}
|
||||
|
||||
if tokens.Err() != nil { // Check the error
|
||||
log.Fatal(tokens.Err())
|
||||
}
|
||||
```
|
||||
|
||||
### If you have a `[]byte`
|
||||
|
||||
```go
|
||||
b := []byte("Hello, 世界. Nice dog! 👍🐶")
|
||||
|
||||
tokens := graphemes.FromBytes(b)
|
||||
|
||||
for tokens.Next() { // Next() returns true until end of data
|
||||
fmt.Println(tokens.Value()) // Do something with the current grapheme
|
||||
}
|
||||
```
|
||||
|
||||
### Performance
|
||||
|
||||
On a Mac M2 laptop, we see around 200MB/s, or around 100 million graphemes per second. You should see ~constant memory, and no allocations.
|
||||
|
||||
### Invalid inputs
|
||||
|
||||
Invalid UTF-8 input is considered undefined behavior. We test to ensure that bad inputs will not cause pathological outcomes, such as a panic or infinite loop. Callers should expect “garbage-in, garbage-out”.
|
||||
|
||||
Your pipeline should probably include a call to [`utf8.Valid()`](https://pkg.go.dev/unicode/utf8#Valid).
|
28
vendor/github.com/clipperhouse/uax29/v2/graphemes/iterator.go
generated
vendored
Normal file
28
vendor/github.com/clipperhouse/uax29/v2/graphemes/iterator.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
package graphemes
|
||||
|
||||
import "github.com/clipperhouse/uax29/v2/internal/iterators"
|
||||
|
||||
type Iterator[T iterators.Stringish] struct {
|
||||
*iterators.Iterator[T]
|
||||
}
|
||||
|
||||
var (
|
||||
splitFuncString = splitFunc[string]
|
||||
splitFuncBytes = splitFunc[[]byte]
|
||||
)
|
||||
|
||||
// FromString returns an iterator for the grapheme clusters in the input string.
|
||||
// Iterate while Next() is true, and access the grapheme via Value().
|
||||
func FromString(s string) Iterator[string] {
|
||||
return Iterator[string]{
|
||||
iterators.New(splitFuncString, s),
|
||||
}
|
||||
}
|
||||
|
||||
// FromBytes returns an iterator for the grapheme clusters in the input bytes.
|
||||
// Iterate while Next() is true, and access the grapheme via Value().
|
||||
func FromBytes(b []byte) Iterator[[]byte] {
|
||||
return Iterator[[]byte]{
|
||||
iterators.New(splitFuncBytes, b),
|
||||
}
|
||||
}
|
25
vendor/github.com/clipperhouse/uax29/v2/graphemes/reader.go
generated
vendored
Normal file
25
vendor/github.com/clipperhouse/uax29/v2/graphemes/reader.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
// Package graphemes implements Unicode grapheme cluster boundaries: https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
|
||||
package graphemes
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Scanner struct {
|
||||
*bufio.Scanner
|
||||
}
|
||||
|
||||
// FromReader returns a Scanner, to split graphemes per
|
||||
// https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries.
|
||||
//
|
||||
// It embeds a [bufio.Scanner], so you can use its methods.
|
||||
//
|
||||
// Iterate through graphemes by calling Scan() until false, then check Err().
|
||||
func FromReader(r io.Reader) *Scanner {
|
||||
sc := bufio.NewScanner(r)
|
||||
sc.Split(SplitFunc)
|
||||
return &Scanner{
|
||||
Scanner: sc,
|
||||
}
|
||||
}
|
174
vendor/github.com/clipperhouse/uax29/v2/graphemes/splitfunc.go
generated
vendored
Normal file
174
vendor/github.com/clipperhouse/uax29/v2/graphemes/splitfunc.go
generated
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
package graphemes
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
|
||||
"github.com/clipperhouse/uax29/v2/internal/iterators"
|
||||
)
|
||||
|
||||
// is determines if lookup intersects propert(ies)
|
||||
func (lookup property) is(properties property) bool {
|
||||
return (lookup & properties) != 0
|
||||
}
|
||||
|
||||
const _Ignore = _Extend
|
||||
|
||||
// SplitFunc is a bufio.SplitFunc implementation of Unicode grapheme cluster segmentation, for use with bufio.Scanner.
|
||||
//
|
||||
// See https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries.
|
||||
var SplitFunc bufio.SplitFunc = splitFunc[[]byte]
|
||||
|
||||
func splitFunc[T iterators.Stringish](data T, atEOF bool) (advance int, token T, err error) {
|
||||
var empty T
|
||||
if len(data) == 0 {
|
||||
return 0, empty, nil
|
||||
}
|
||||
|
||||
// These vars are stateful across loop iterations
|
||||
var pos int
|
||||
var lastExIgnore property = 0 // "last excluding ignored categories"
|
||||
var lastLastExIgnore property = 0 // "last one before that"
|
||||
var regionalIndicatorCount int
|
||||
|
||||
// Rules are usually of the form Cat1 × Cat2; "current" refers to the first property
|
||||
// to the right of the ×, from which we look back or forward
|
||||
|
||||
current, w := lookup(data[pos:])
|
||||
if w == 0 {
|
||||
if !atEOF {
|
||||
// Rune extends past current data, request more
|
||||
return 0, empty, nil
|
||||
}
|
||||
pos = len(data)
|
||||
return pos, data[:pos], nil
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB1
|
||||
// Start of text always advances
|
||||
pos += w
|
||||
|
||||
for {
|
||||
eot := pos == len(data) // "end of text"
|
||||
|
||||
if eot {
|
||||
if !atEOF {
|
||||
// Token extends past current data, request more
|
||||
return 0, empty, nil
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB2
|
||||
break
|
||||
}
|
||||
|
||||
/*
|
||||
We've switched the evaluation order of GB1↓ and GB2↑. It's ok:
|
||||
because we've checked for len(data) at the top of this function,
|
||||
sot and eot are mutually exclusive, order doesn't matter.
|
||||
*/
|
||||
|
||||
// Rules are usually of the form Cat1 × Cat2; "current" refers to the first property
|
||||
// to the right of the ×, from which we look back or forward
|
||||
|
||||
// Remember previous properties to avoid lookups/lookbacks
|
||||
last := current
|
||||
if !last.is(_Ignore) {
|
||||
lastLastExIgnore = lastExIgnore
|
||||
lastExIgnore = last
|
||||
}
|
||||
|
||||
current, w = lookup(data[pos:])
|
||||
if w == 0 {
|
||||
if atEOF {
|
||||
// Just return the bytes, we can't do anything with them
|
||||
pos = len(data)
|
||||
break
|
||||
}
|
||||
// Rune extends past current data, request more
|
||||
return 0, empty, nil
|
||||
}
|
||||
|
||||
// Optimization: no rule can possibly apply
|
||||
if current|last == 0 { // i.e. both are zero
|
||||
break
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB3
|
||||
if current.is(_LF) && last.is(_CR) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB4
|
||||
// https://unicode.org/reports/tr29/#GB5
|
||||
if (current | last).is(_Control | _CR | _LF) {
|
||||
break
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB6
|
||||
if current.is(_L|_V|_LV|_LVT) && last.is(_L) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB7
|
||||
if current.is(_V|_T) && last.is(_LV|_V) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB8
|
||||
if current.is(_T) && last.is(_LVT|_T) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB9
|
||||
if current.is(_Extend | _ZWJ) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB9a
|
||||
if current.is(_SpacingMark) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB9b
|
||||
if last.is(_Prepend) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB9c
|
||||
// TODO(clipperhouse):
|
||||
// It appears to be added in Unicode 15.1.0:
|
||||
// https://unicode.org/versions/Unicode15.1.0/#Migration
|
||||
// This package currently supports Unicode 15.0.0, so
|
||||
// out of scope for now
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB11
|
||||
if current.is(_ExtendedPictographic) && last.is(_ZWJ) && lastLastExIgnore.is(_ExtendedPictographic) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB12
|
||||
// https://unicode.org/reports/tr29/#GB13
|
||||
if (current & last).is(_RegionalIndicator) {
|
||||
regionalIndicatorCount++
|
||||
|
||||
odd := regionalIndicatorCount%2 == 1
|
||||
if odd {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// If we fall through all the above rules, it's a grapheme cluster break
|
||||
break
|
||||
}
|
||||
|
||||
// Return token
|
||||
return pos, data[:pos], nil
|
||||
}
|
1409
vendor/github.com/clipperhouse/uax29/v2/graphemes/trie.go
generated
vendored
Normal file
1409
vendor/github.com/clipperhouse/uax29/v2/graphemes/trie.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
85
vendor/github.com/clipperhouse/uax29/v2/internal/iterators/iterator.go
generated
vendored
Normal file
85
vendor/github.com/clipperhouse/uax29/v2/internal/iterators/iterator.go
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
package iterators
|
||||
|
||||
type Stringish interface {
|
||||
[]byte | string
|
||||
}
|
||||
|
||||
type SplitFunc[T Stringish] func(T, bool) (int, T, error)
|
||||
|
||||
// Iterator is a generic iterator for words that are either []byte or string.
|
||||
// Iterate while Next() is true, and access the word via Value().
|
||||
type Iterator[T Stringish] struct {
|
||||
split SplitFunc[T]
|
||||
data T
|
||||
start int
|
||||
pos int
|
||||
}
|
||||
|
||||
// New creates a new Iterator for the given data and SplitFunc.
|
||||
func New[T Stringish](split SplitFunc[T], data T) *Iterator[T] {
|
||||
return &Iterator[T]{
|
||||
split: split,
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// SetText sets the text for the iterator to operate on, and resets all state.
|
||||
func (iter *Iterator[T]) SetText(data T) {
|
||||
iter.data = data
|
||||
iter.start = 0
|
||||
iter.pos = 0
|
||||
}
|
||||
|
||||
// Split sets the SplitFunc for the Iterator.
|
||||
func (iter *Iterator[T]) Split(split SplitFunc[T]) {
|
||||
iter.split = split
|
||||
}
|
||||
|
||||
// Next advances the iterator to the next token. It returns false when there
|
||||
// are no remaining tokens or an error occurred.
|
||||
func (iter *Iterator[T]) Next() bool {
|
||||
if iter.pos == len(iter.data) {
|
||||
return false
|
||||
}
|
||||
if iter.pos > len(iter.data) {
|
||||
panic("SplitFunc advanced beyond the end of the data")
|
||||
}
|
||||
|
||||
iter.start = iter.pos
|
||||
|
||||
advance, _, err := iter.split(iter.data[iter.pos:], true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if advance <= 0 {
|
||||
panic("SplitFunc returned a zero or negative advance")
|
||||
}
|
||||
|
||||
iter.pos += advance
|
||||
if iter.pos > len(iter.data) {
|
||||
panic("SplitFunc advanced beyond the end of the data")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Value returns the current token.
|
||||
func (iter *Iterator[T]) Value() T {
|
||||
return iter.data[iter.start:iter.pos]
|
||||
}
|
||||
|
||||
// Start returns the byte position of the current token in the original data.
|
||||
func (iter *Iterator[T]) Start() int {
|
||||
return iter.start
|
||||
}
|
||||
|
||||
// End returns the byte position after the current token in the original data.
|
||||
func (iter *Iterator[T]) End() int {
|
||||
return iter.pos
|
||||
}
|
||||
|
||||
// Reset resets the iterator to the beginning of the data.
|
||||
func (iter *Iterator[T]) Reset() {
|
||||
iter.start = 0
|
||||
iter.pos = 0
|
||||
}
|
56
vendor/github.com/cyphar/filepath-securejoin/.golangci.yml
generated
vendored
Normal file
56
vendor/github.com/cyphar/filepath-securejoin/.golangci.yml
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
# Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
# Copyright (C) 2025 SUSE LLC
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
version: "2"
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- asasalint
|
||||
- asciicheck
|
||||
- containedctx
|
||||
- contextcheck
|
||||
- errcheck
|
||||
- errorlint
|
||||
- exhaustive
|
||||
- forcetypeassert
|
||||
- godot
|
||||
- goprintffuncname
|
||||
- govet
|
||||
- importas
|
||||
- ineffassign
|
||||
- makezero
|
||||
- misspell
|
||||
- musttag
|
||||
- nilerr
|
||||
- nilnesserr
|
||||
- nilnil
|
||||
- noctx
|
||||
- prealloc
|
||||
- revive
|
||||
- staticcheck
|
||||
- testifylint
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- usetesting
|
||||
settings:
|
||||
govet:
|
||||
enable:
|
||||
- nilness
|
||||
testifylint:
|
||||
enable-all: true
|
||||
|
||||
formatters:
|
||||
enable:
|
||||
- gofumpt
|
||||
- goimports
|
||||
settings:
|
||||
goimports:
|
||||
local-prefixes:
|
||||
- github.com/cyphar/filepath-securejoin
|
121
vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md
generated
vendored
121
vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md
generated
vendored
@ -6,6 +6,122 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [Unreleased] ##
|
||||
|
||||
## [0.5.0] - 2025-09-26 ##
|
||||
|
||||
> Let the past die. Kill it if you have to.
|
||||
|
||||
> **NOTE**: With this release, some parts of
|
||||
> `github.com/cyphar/filepath-securejoin` are now licensed under the Mozilla
|
||||
> Public License (version 2). Please see [COPYING.md][] as well as the the
|
||||
> license header in each file for more details.
|
||||
|
||||
[COPYING.md]: ./COPYING.md
|
||||
|
||||
### Breaking ###
|
||||
- The new API introduced in the [0.3.0][] release has been moved to a new
|
||||
subpackage called `pathrs-lite`. This was primarily done to better indicate
|
||||
the split between the new and old APIs, as well as indicate to users the
|
||||
purpose of this subpackage (it is a less complete version of [libpathrs][]).
|
||||
|
||||
We have added some wrappers to the top-level package to ease the transition,
|
||||
but those are deprecated and will be removed in the next minor release of
|
||||
filepath-securejoin. Users should update their import paths.
|
||||
|
||||
This new subpackage has also been relicensed under the Mozilla Public License
|
||||
(version 2), please see [COPYING.md][] for more details.
|
||||
|
||||
### Added ###
|
||||
- Most of the key bits the safe `procfs` API have now been exported and are
|
||||
available in `github.com/cyphar/filepath-securejoin/pathrs-lite/procfs`. At
|
||||
the moment this primarily consists of a new `procfs.Handle` API:
|
||||
|
||||
* `OpenProcRoot` returns a new handle to `/proc`, endeavouring to make it
|
||||
safe if possible (`subset=pid` to protect against mistaken write attacks
|
||||
and leaks, as well as using `fsopen(2)` to avoid racing mount attacks).
|
||||
|
||||
`OpenUnsafeProcRoot` returns a handle without attempting to create one
|
||||
with `subset=pid`, which makes it more dangerous to leak. Most users
|
||||
should use `OpenProcRoot` (even if you need to use `ProcRoot` as the base
|
||||
of an operation, as filepath-securejoin will internally open a handle when
|
||||
necessary).
|
||||
|
||||
* The `(*procfs.Handle).Open*` family of methods lets you get a safe
|
||||
`O_PATH` handle to subpaths within `/proc` for certain subpaths.
|
||||
|
||||
For `OpenThreadSelf`, the returned `ProcThreadSelfCloser` needs to be
|
||||
called after you completely finish using the handle (this is necessary
|
||||
because Go is multi-threaded and `ProcThreadSelf` references
|
||||
`/proc/thread-self` which may disappear if we do not
|
||||
`runtime.LockOSThread` -- `ProcThreadSelfCloser` is currently equivalent
|
||||
to `runtime.UnlockOSThread`).
|
||||
|
||||
Note that you cannot open any `procfs` symlinks (most notably magic-links)
|
||||
using this API. At the moment, filepath-securejoin does not support this
|
||||
feature (but [libpathrs][] does).
|
||||
|
||||
* `ProcSelfFdReadlink` lets you get the in-kernel path representation of a
|
||||
file descriptor (think `readlink("/proc/self/fd/...")`), except that we
|
||||
verify that there aren't any tricky overmounts that could fool the
|
||||
process.
|
||||
|
||||
Please be aware that the returned string is simply a snapshot at that
|
||||
particular moment, and an attacker could move the file being pointed to.
|
||||
In addition, complex namespace configurations could result in non-sensical
|
||||
or confusing paths to be returned. The value received from this function
|
||||
should only be used as secondary verification of some security property,
|
||||
not as proof that a particular handle has a particular path.
|
||||
|
||||
The procfs handle used internally by the API is the same as the rest of
|
||||
`filepath-securejoin` (for privileged programs this is usually a private
|
||||
in-process `procfs` instance created with `fsopen(2)`).
|
||||
|
||||
As before, this is intended as a stop-gap before users migrate to
|
||||
[libpathrs][], which provides a far more extensive safe `procfs` API and is
|
||||
generally more robust.
|
||||
|
||||
- Previously, the hardened procfs implementation (used internally within
|
||||
`Reopen` and `Open(at)InRoot`) only protected against overmount attacks on
|
||||
systems with `openat2(2)` (Linux 5.6) or systems with `fsopen(2)` or
|
||||
`open_tree(2)` (Linux 5.2) and programs with privileges to use them (with
|
||||
some caveats about locked mounts that probably affect very few users). For
|
||||
other users, an attacker with the ability to create malicious mounts (on most
|
||||
systems, a sysadmin) could trick you into operating on files you didn't
|
||||
expect. This attack only really makes sense in the context of container
|
||||
runtime implementations.
|
||||
|
||||
This was considered a reasonable trade-off, as the long-term intention was to
|
||||
get all users to just switch to [libpathrs][] if they wanted to use the safe
|
||||
`procfs` API (which had more extensive protections, and is what these new
|
||||
protections in `filepath-securejoin` are based on). However, as the API
|
||||
is now being exported it seems unwise to advertise the API as "safe" if we do
|
||||
not protect against known attacks.
|
||||
|
||||
The procfs API is now more protected against attackers on systems lacking the
|
||||
aforementioned protections. However, the most comprehensive of these
|
||||
protections effectively rely on [`statx(STATX_MNT_ID)`][statx.2] (Linux 5.8).
|
||||
On older kernel versions, there is no effective protection (there is some
|
||||
minimal protection against non-`procfs` filesystem components but a
|
||||
sufficiently clever attacker can work around those). In addition,
|
||||
`STATX_MNT_ID` is vulnerable to mount ID reuse attacks by sufficiently
|
||||
motivated and privileged attackers -- this problem is mitigated with
|
||||
`STATX_MNT_ID_UNIQUE` (Linux 6.8) but that raises the minimum kernel version
|
||||
for more protection.
|
||||
|
||||
The fact that these protections are quite limited despite needing a fair bit
|
||||
of extra code to handle was one of the primary reasons we did not initially
|
||||
implement this in `filepath-securejoin` ([libpathrs][] supports all of this,
|
||||
of course).
|
||||
|
||||
### Fixed ###
|
||||
- RHEL 8 kernels have backports of `fsopen(2)` but in some testing we've found
|
||||
that it has very bad (and very difficult to debug) performance issues, and so
|
||||
we will explicitly refuse to use `fsopen(2)` if the running kernel version is
|
||||
pre-5.2 and will instead fallback to `open("/proc")`.
|
||||
|
||||
[CVE-2024-21626]: https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv
|
||||
[libpathrs]: https://github.com/cyphar/libpathrs
|
||||
[statx.2]: https://www.man7.org/linux/man-pages/man2/statx.2.html
|
||||
|
||||
## [0.4.1] - 2025-01-28 ##
|
||||
|
||||
### Fixed ###
|
||||
@ -173,7 +289,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
safe to start migrating to as we have extensive tests ensuring they behave
|
||||
correctly and are safe against various races and other attacks.
|
||||
|
||||
[libpathrs]: https://github.com/openSUSE/libpathrs
|
||||
[libpathrs]: https://github.com/cyphar/libpathrs
|
||||
[open.2]: https://www.man7.org/linux/man-pages/man2/open.2.html
|
||||
|
||||
## [0.2.5] - 2024-05-03 ##
|
||||
@ -238,7 +354,8 @@ This is our first release of `github.com/cyphar/filepath-securejoin`,
|
||||
containing a full implementation with a coverage of 93.5% (the only missing
|
||||
cases are the error cases, which are hard to mocktest at the moment).
|
||||
|
||||
[Unreleased]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...HEAD
|
||||
[Unreleased]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.0...HEAD
|
||||
[0.5.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...v0.5.0
|
||||
[0.4.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.0...v0.4.1
|
||||
[0.4.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.6...v0.4.0
|
||||
[0.3.6]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.5...v0.3.6
|
||||
|
447
vendor/github.com/cyphar/filepath-securejoin/COPYING.md
generated
vendored
Normal file
447
vendor/github.com/cyphar/filepath-securejoin/COPYING.md
generated
vendored
Normal file
@ -0,0 +1,447 @@
|
||||
## COPYING ##
|
||||
|
||||
`SPDX-License-Identifier: BSD-3-Clause AND MPL-2.0`
|
||||
|
||||
This project is made up of code licensed under different licenses. Which code
|
||||
you use will have an impact on whether only one or both licenses apply to your
|
||||
usage of this library.
|
||||
|
||||
Note that **each file** in this project individually has a code comment at the
|
||||
start describing the license of that particular file -- this is the most
|
||||
accurate license information of this project; in case there is any conflict
|
||||
between this document and the comment at the start of a file, the comment shall
|
||||
take precedence. The only purpose of this document is to work around [a known
|
||||
technical limitation of pkg.go.dev's license checking tool when dealing with
|
||||
non-trivial project licenses][go75067].
|
||||
|
||||
[go75067]: https://go.dev/issue/75067
|
||||
|
||||
### `BSD-3-Clause` ###
|
||||
|
||||
At time of writing, the following files and directories are licensed under the
|
||||
BSD-3-Clause license:
|
||||
|
||||
* `doc.go`
|
||||
* `join*.go`
|
||||
* `vfs.go`
|
||||
* `internal/consts/*.go`
|
||||
* `pathrs-lite/internal/gocompat/*.go`
|
||||
* `pathrs-lite/internal/kernelversion/*.go`
|
||||
|
||||
The text of the BSD-3-Clause license used by this project is the following (the
|
||||
text is also available from the [`LICENSE.BSD`](./LICENSE.BSD) file):
|
||||
|
||||
```
|
||||
Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
|
||||
Copyright (C) 2017-2024 SUSE LLC. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
```
|
||||
|
||||
### `MPL-2.0` ###
|
||||
|
||||
All other files (unless otherwise marked) are licensed under the Mozilla Public
|
||||
License (version 2.0).
|
||||
|
||||
The text of the Mozilla Public License (version 2.0) is the following (the text
|
||||
is also available from the [`LICENSE.MPL-2.0`](./LICENSE.MPL-2.0) file):
|
||||
|
||||
```
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
```
|
373
vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0
generated
vendored
Normal file
373
vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0
generated
vendored
Normal file
@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
21
vendor/github.com/cyphar/filepath-securejoin/README.md
generated
vendored
21
vendor/github.com/cyphar/filepath-securejoin/README.md
generated
vendored
@ -67,7 +67,8 @@ func SecureJoin(root, unsafePath string) (string, error) {
|
||||
[libpathrs]: https://github.com/openSUSE/libpathrs
|
||||
[go#20126]: https://github.com/golang/go/issues/20126
|
||||
|
||||
### New API ###
|
||||
### <a name="new-api" /> New API ###
|
||||
[#new-api]: #new-api
|
||||
|
||||
While we recommend users switch to [libpathrs][libpathrs] as soon as it has a
|
||||
stable release, some methods implemented by libpathrs have been ported to this
|
||||
@ -165,5 +166,19 @@ after `MkdirAll`).
|
||||
|
||||
### License ###
|
||||
|
||||
The license of this project is the same as Go, which is a BSD 3-clause license
|
||||
available in the `LICENSE` file.
|
||||
`SPDX-License-Identifier: BSD-3-Clause AND MPL-2.0`
|
||||
|
||||
Some of the code in this project is derived from Go, and is licensed under a
|
||||
BSD 3-clause license (available in `LICENSE.BSD`). Other files (many of which
|
||||
are derived from [libpathrs][libpathrs]) are licensed under the Mozilla Public
|
||||
License version 2.0 (available in `LICENSE.MPL-2.0`). If you are using the
|
||||
["New API" described above][#new-api], you are probably using code from files
|
||||
released under this license.
|
||||
|
||||
Every source file in this project has a copyright header describing its
|
||||
license. Please check the license headers of each file to see what license
|
||||
applies to it.
|
||||
|
||||
See [COPYING.md](./COPYING.md) for some more details.
|
||||
|
||||
[umoci]: https://github.com/opencontainers/umoci
|
||||
|
2
vendor/github.com/cyphar/filepath-securejoin/VERSION
generated
vendored
2
vendor/github.com/cyphar/filepath-securejoin/VERSION
generated
vendored
@ -1 +1 @@
|
||||
0.4.1
|
||||
0.5.0
|
||||
|
29
vendor/github.com/cyphar/filepath-securejoin/codecov.yml
generated
vendored
Normal file
29
vendor/github.com/cyphar/filepath-securejoin/codecov.yml
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
# Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
# Copyright (C) 2025 SUSE LLC
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
comment:
|
||||
layout: "condensed_header, reach, diff, components, condensed_files, condensed_footer"
|
||||
require_changes: true
|
||||
branches:
|
||||
- main
|
||||
|
||||
coverage:
|
||||
range: 60..100
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 85%
|
||||
threshold: 0%
|
||||
patch:
|
||||
default:
|
||||
target: auto
|
||||
informational: true
|
||||
|
||||
github_checks:
|
||||
annotations: false
|
48
vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go
generated
vendored
Normal file
48
vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package securejoin
|
||||
|
||||
import (
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite"
|
||||
)
|
||||
|
||||
var (
|
||||
// MkdirAll is a wrapper around [pathrs.MkdirAll].
|
||||
//
|
||||
// Deprecated: You should use [pathrs.MkdirAll] directly instead. This
|
||||
// wrapper will be removed in filepath-securejoin v0.6.
|
||||
MkdirAll = pathrs.MkdirAll
|
||||
|
||||
// MkdirAllHandle is a wrapper around [pathrs.MkdirAllHandle].
|
||||
//
|
||||
// Deprecated: You should use [pathrs.MkdirAllHandle] directly instead.
|
||||
// This wrapper will be removed in filepath-securejoin v0.6.
|
||||
MkdirAllHandle = pathrs.MkdirAllHandle
|
||||
|
||||
// OpenInRoot is a wrapper around [pathrs.OpenInRoot].
|
||||
//
|
||||
// Deprecated: You should use [pathrs.OpenInRoot] directly instead. This
|
||||
// wrapper will be removed in filepath-securejoin v0.6.
|
||||
OpenInRoot = pathrs.OpenInRoot
|
||||
|
||||
// OpenatInRoot is a wrapper around [pathrs.OpenatInRoot].
|
||||
//
|
||||
// Deprecated: You should use [pathrs.OpenatInRoot] directly instead. This
|
||||
// wrapper will be removed in filepath-securejoin v0.6.
|
||||
OpenatInRoot = pathrs.OpenatInRoot
|
||||
|
||||
// Reopen is a wrapper around [pathrs.Reopen].
|
||||
//
|
||||
// Deprecated: You should use [pathrs.Reopen] directly instead. This
|
||||
// wrapper will be removed in filepath-securejoin v0.6.
|
||||
Reopen = pathrs.Reopen
|
||||
)
|
34
vendor/github.com/cyphar/filepath-securejoin/doc.go
generated
vendored
34
vendor/github.com/cyphar/filepath-securejoin/doc.go
generated
vendored
@ -1,3 +1,5 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
|
||||
// Copyright (C) 2017-2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
@ -14,14 +16,13 @@
|
||||
// **not** safe against race conditions where an attacker changes the
|
||||
// filesystem after (or during) the [SecureJoin] operation.
|
||||
//
|
||||
// The new API is made up of [OpenInRoot] and [MkdirAll] (and derived
|
||||
// functions). These are safe against racing attackers and have several other
|
||||
// protections that are not provided by the legacy API. There are many more
|
||||
// operations that most programs expect to be able to do safely, but we do not
|
||||
// provide explicit support for them because we want to encourage users to
|
||||
// switch to [libpathrs](https://github.com/openSUSE/libpathrs) which is a
|
||||
// cross-language next-generation library that is entirely designed around
|
||||
// operating on paths safely.
|
||||
// The new API is available in the [pathrs-lite] subpackage, and provide
|
||||
// protections against racing attackers as well as several other key
|
||||
// protections against attacks often seen by container runtimes. As the name
|
||||
// suggests, [pathrs-lite] is a stripped down (pure Go) reimplementation of
|
||||
// [libpathrs]. The main APIs provided are [OpenInRoot], [MkdirAll], and
|
||||
// [procfs.Handle] -- other APIs are not planned to be ported. The long-term
|
||||
// goal is for users to migrate to [libpathrs] which is more fully-featured.
|
||||
//
|
||||
// securejoin has been used by several container runtimes (Docker, runc,
|
||||
// Kubernetes, etc) for quite a few years as a de-facto standard for operating
|
||||
@ -31,9 +32,16 @@
|
||||
// API as soon as possible (or even better, switch to libpathrs).
|
||||
//
|
||||
// This project was initially intended to be included in the Go standard
|
||||
// library, but [it was rejected](https://go.dev/issue/20126). There is now a
|
||||
// [new Go proposal](https://go.dev/issue/67002) for a safe path resolution API
|
||||
// that shares some of the goals of filepath-securejoin. However, that design
|
||||
// is intended to work like `openat2(RESOLVE_BENEATH)` which does not fit the
|
||||
// usecase of container runtimes and most system tools.
|
||||
// library, but it was rejected (see https://go.dev/issue/20126). Much later,
|
||||
// [os.Root] was added to the Go stdlib that shares some of the goals of
|
||||
// filepath-securejoin. However, its design is intended to work like
|
||||
// openat2(RESOLVE_BENEATH) which does not fit the usecase of container
|
||||
// runtimes and most system tools.
|
||||
//
|
||||
// [pathrs-lite]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite
|
||||
// [libpathrs]: https://github.com/openSUSE/libpathrs
|
||||
// [OpenInRoot]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite#OpenInRoot
|
||||
// [MkdirAll]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite#MkdirAll
|
||||
// [procfs.Handle]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs#Handle
|
||||
// [os.Root]: https:///pkg.go.dev/os#Root
|
||||
package securejoin
|
||||
|
32
vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_go121.go
generated
vendored
32
vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_go121.go
generated
vendored
@ -1,32 +0,0 @@
|
||||
//go:build linux && go1.21
|
||||
|
||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package securejoin
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func slices_DeleteFunc[S ~[]E, E any](slice S, delFn func(E) bool) S {
|
||||
return slices.DeleteFunc(slice, delFn)
|
||||
}
|
||||
|
||||
func slices_Contains[S ~[]E, E comparable](slice S, val E) bool {
|
||||
return slices.Contains(slice, val)
|
||||
}
|
||||
|
||||
func slices_Clone[S ~[]E, E any](slice S) S {
|
||||
return slices.Clone(slice)
|
||||
}
|
||||
|
||||
func sync_OnceValue[T any](f func() T) func() T {
|
||||
return sync.OnceValue(f)
|
||||
}
|
||||
|
||||
func sync_OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
|
||||
return sync.OnceValues(f)
|
||||
}
|
124
vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_unsupported.go
generated
vendored
124
vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_unsupported.go
generated
vendored
@ -1,124 +0,0 @@
|
||||
//go:build linux && !go1.21
|
||||
|
||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package securejoin
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// These are very minimal implementations of functions that appear in Go 1.21's
|
||||
// stdlib, included so that we can build on older Go versions. Most are
|
||||
// borrowed directly from the stdlib, and a few are modified to be "obviously
|
||||
// correct" without needing to copy too many other helpers.
|
||||
|
||||
// clearSlice is equivalent to the builtin clear from Go 1.21.
|
||||
// Copied from the Go 1.24 stdlib implementation.
|
||||
func clearSlice[S ~[]E, E any](slice S) {
|
||||
var zero E
|
||||
for i := range slice {
|
||||
slice[i] = zero
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from the Go 1.24 stdlib implementation.
|
||||
func slices_IndexFunc[S ~[]E, E any](s S, f func(E) bool) int {
|
||||
for i := range s {
|
||||
if f(s[i]) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Copied from the Go 1.24 stdlib implementation.
|
||||
func slices_DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S {
|
||||
i := slices_IndexFunc(s, del)
|
||||
if i == -1 {
|
||||
return s
|
||||
}
|
||||
// Don't start copying elements until we find one to delete.
|
||||
for j := i + 1; j < len(s); j++ {
|
||||
if v := s[j]; !del(v) {
|
||||
s[i] = v
|
||||
i++
|
||||
}
|
||||
}
|
||||
clearSlice(s[i:]) // zero/nil out the obsolete elements, for GC
|
||||
return s[:i]
|
||||
}
|
||||
|
||||
// Similar to the stdlib slices.Contains, except that we don't have
|
||||
// slices.Index so we need to use slices.IndexFunc for this non-Func helper.
|
||||
func slices_Contains[S ~[]E, E comparable](s S, v E) bool {
|
||||
return slices_IndexFunc(s, func(e E) bool { return e == v }) >= 0
|
||||
}
|
||||
|
||||
// Copied from the Go 1.24 stdlib implementation.
|
||||
func slices_Clone[S ~[]E, E any](s S) S {
|
||||
// Preserve nil in case it matters.
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
return append(S([]E{}), s...)
|
||||
}
|
||||
|
||||
// Copied from the Go 1.24 stdlib implementation.
|
||||
func sync_OnceValue[T any](f func() T) func() T {
|
||||
var (
|
||||
once sync.Once
|
||||
valid bool
|
||||
p any
|
||||
result T
|
||||
)
|
||||
g := func() {
|
||||
defer func() {
|
||||
p = recover()
|
||||
if !valid {
|
||||
panic(p)
|
||||
}
|
||||
}()
|
||||
result = f()
|
||||
f = nil
|
||||
valid = true
|
||||
}
|
||||
return func() T {
|
||||
once.Do(g)
|
||||
if !valid {
|
||||
panic(p)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from the Go 1.24 stdlib implementation.
|
||||
func sync_OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
|
||||
var (
|
||||
once sync.Once
|
||||
valid bool
|
||||
p any
|
||||
r1 T1
|
||||
r2 T2
|
||||
)
|
||||
g := func() {
|
||||
defer func() {
|
||||
p = recover()
|
||||
if !valid {
|
||||
panic(p)
|
||||
}
|
||||
}()
|
||||
r1, r2 = f()
|
||||
f = nil
|
||||
valid = true
|
||||
}
|
||||
return func() (T1, T2) {
|
||||
once.Do(g)
|
||||
if !valid {
|
||||
panic(p)
|
||||
}
|
||||
return r1, r2
|
||||
}
|
||||
}
|
15
vendor/github.com/cyphar/filepath-securejoin/internal/consts/consts.go
generated
vendored
Normal file
15
vendor/github.com/cyphar/filepath-securejoin/internal/consts/consts.go
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
|
||||
// Copyright (C) 2017-2025 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package consts contains the definitions of internal constants used
|
||||
// throughout filepath-securejoin.
|
||||
package consts
|
||||
|
||||
// MaxSymlinkLimit is the maximum number of symlinks that can be encountered
|
||||
// during a single lookup before returning -ELOOP. At time of writing, Linux
|
||||
// has an internal limit of 40.
|
||||
const MaxSymlinkLimit = 255
|
23
vendor/github.com/cyphar/filepath-securejoin/join.go
generated
vendored
23
vendor/github.com/cyphar/filepath-securejoin/join.go
generated
vendored
@ -1,3 +1,5 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
|
||||
// Copyright (C) 2017-2025 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
@ -11,9 +13,9 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const maxSymlinkLimit = 255
|
||||
"github.com/cyphar/filepath-securejoin/internal/consts"
|
||||
)
|
||||
|
||||
// IsNotExist tells you if err is an error that implies that either the path
|
||||
// accessed does not exist (or path components don't exist). This is
|
||||
@ -49,12 +51,13 @@ func hasDotDot(path string) bool {
|
||||
return strings.Contains("/"+path+"/", "/../")
|
||||
}
|
||||
|
||||
// SecureJoinVFS joins the two given path components (similar to [filepath.Join]) except
|
||||
// that the returned path is guaranteed to be scoped inside the provided root
|
||||
// path (when evaluated). Any symbolic links in the path are evaluated with the
|
||||
// given root treated as the root of the filesystem, similar to a chroot. The
|
||||
// filesystem state is evaluated through the given [VFS] interface (if nil, the
|
||||
// standard [os].* family of functions are used).
|
||||
// SecureJoinVFS joins the two given path components (similar to
|
||||
// [filepath.Join]) except that the returned path is guaranteed to be scoped
|
||||
// inside the provided root path (when evaluated). Any symbolic links in the
|
||||
// path are evaluated with the given root treated as the root of the
|
||||
// filesystem, similar to a chroot. The filesystem state is evaluated through
|
||||
// the given [VFS] interface (if nil, the standard [os].* family of functions
|
||||
// are used).
|
||||
//
|
||||
// Note that the guarantees provided by this function only apply if the path
|
||||
// components in the returned string are not modified (in other words are not
|
||||
@ -78,7 +81,7 @@ func hasDotDot(path string) bool {
|
||||
// fully resolved using [filepath.EvalSymlinks] or otherwise constructed to
|
||||
// avoid containing symlink components. Of course, the root also *must not* be
|
||||
// attacker-controlled.
|
||||
func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) {
|
||||
func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) { //nolint:revive // name is part of public API
|
||||
// The root path must not contain ".." components, otherwise when we join
|
||||
// the subpath we will end up with a weird path. We could work around this
|
||||
// in other ways but users shouldn't be giving us non-lexical root paths in
|
||||
@ -138,7 +141,7 @@ func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) {
|
||||
// It's a symlink, so get its contents and expand it by prepending it
|
||||
// to the yet-unparsed path.
|
||||
linksWalked++
|
||||
if linksWalked > maxSymlinkLimit {
|
||||
if linksWalked > consts.MaxSymlinkLimit {
|
||||
return "", &os.PathError{Op: "SecureJoin", Path: root + string(filepath.Separator) + unsafePath, Err: syscall.ELOOP}
|
||||
}
|
||||
|
||||
|
127
vendor/github.com/cyphar/filepath-securejoin/openat2_linux.go
generated
vendored
127
vendor/github.com/cyphar/filepath-securejoin/openat2_linux.go
generated
vendored
@ -1,127 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package securejoin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var hasOpenat2 = sync_OnceValue(func() bool {
|
||||
fd, err := unix.Openat2(unix.AT_FDCWD, ".", &unix.OpenHow{
|
||||
Flags: unix.O_PATH | unix.O_CLOEXEC,
|
||||
Resolve: unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_IN_ROOT,
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_ = unix.Close(fd)
|
||||
return true
|
||||
})
|
||||
|
||||
func scopedLookupShouldRetry(how *unix.OpenHow, err error) bool {
|
||||
// RESOLVE_IN_ROOT (and RESOLVE_BENEATH) can return -EAGAIN if we resolve
|
||||
// ".." while a mount or rename occurs anywhere on the system. This could
|
||||
// happen spuriously, or as the result of an attacker trying to mess with
|
||||
// us during lookup.
|
||||
//
|
||||
// In addition, scoped lookups have a "safety check" at the end of
|
||||
// complete_walk which will return -EXDEV if the final path is not in the
|
||||
// root.
|
||||
return how.Resolve&(unix.RESOLVE_IN_ROOT|unix.RESOLVE_BENEATH) != 0 &&
|
||||
(errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EXDEV))
|
||||
}
|
||||
|
||||
const scopedLookupMaxRetries = 10
|
||||
|
||||
func openat2File(dir *os.File, path string, how *unix.OpenHow) (*os.File, error) {
|
||||
fullPath := dir.Name() + "/" + path
|
||||
// Make sure we always set O_CLOEXEC.
|
||||
how.Flags |= unix.O_CLOEXEC
|
||||
var tries int
|
||||
for tries < scopedLookupMaxRetries {
|
||||
fd, err := unix.Openat2(int(dir.Fd()), path, how)
|
||||
if err != nil {
|
||||
if scopedLookupShouldRetry(how, err) {
|
||||
// We retry a couple of times to avoid the spurious errors, and
|
||||
// if we are being attacked then returning -EAGAIN is the best
|
||||
// we can do.
|
||||
tries++
|
||||
continue
|
||||
}
|
||||
return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: err}
|
||||
}
|
||||
// If we are using RESOLVE_IN_ROOT, the name we generated may be wrong.
|
||||
// NOTE: The procRoot code MUST NOT use RESOLVE_IN_ROOT, otherwise
|
||||
// you'll get infinite recursion here.
|
||||
if how.Resolve&unix.RESOLVE_IN_ROOT == unix.RESOLVE_IN_ROOT {
|
||||
if actualPath, err := rawProcSelfFdReadlink(fd); err == nil {
|
||||
fullPath = actualPath
|
||||
}
|
||||
}
|
||||
return os.NewFile(uintptr(fd), fullPath), nil
|
||||
}
|
||||
return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: errPossibleAttack}
|
||||
}
|
||||
|
||||
func lookupOpenat2(root *os.File, unsafePath string, partial bool) (*os.File, string, error) {
|
||||
if !partial {
|
||||
file, err := openat2File(root, unsafePath, &unix.OpenHow{
|
||||
Flags: unix.O_PATH | unix.O_CLOEXEC,
|
||||
Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS,
|
||||
})
|
||||
return file, "", err
|
||||
}
|
||||
return partialLookupOpenat2(root, unsafePath)
|
||||
}
|
||||
|
||||
// partialLookupOpenat2 is an alternative implementation of
|
||||
// partialLookupInRoot, using openat2(RESOLVE_IN_ROOT) to more safely get a
|
||||
// handle to the deepest existing child of the requested path within the root.
|
||||
func partialLookupOpenat2(root *os.File, unsafePath string) (*os.File, string, error) {
|
||||
// TODO: Implement this as a git-bisect-like binary search.
|
||||
|
||||
unsafePath = filepath.ToSlash(unsafePath) // noop
|
||||
endIdx := len(unsafePath)
|
||||
var lastError error
|
||||
for endIdx > 0 {
|
||||
subpath := unsafePath[:endIdx]
|
||||
|
||||
handle, err := openat2File(root, subpath, &unix.OpenHow{
|
||||
Flags: unix.O_PATH | unix.O_CLOEXEC,
|
||||
Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS,
|
||||
})
|
||||
if err == nil {
|
||||
// Jump over the slash if we have a non-"" remainingPath.
|
||||
if endIdx < len(unsafePath) {
|
||||
endIdx += 1
|
||||
}
|
||||
// We found a subpath!
|
||||
return handle, unsafePath[endIdx:], lastError
|
||||
}
|
||||
if errors.Is(err, unix.ENOENT) || errors.Is(err, unix.ENOTDIR) {
|
||||
// That path doesn't exist, let's try the next directory up.
|
||||
endIdx = strings.LastIndexByte(subpath, '/')
|
||||
lastError = err
|
||||
continue
|
||||
}
|
||||
return nil, "", fmt.Errorf("open subpath: %w", err)
|
||||
}
|
||||
// If we couldn't open anything, the whole subpath is missing. Return a
|
||||
// copy of the root fd so that the caller doesn't close this one by
|
||||
// accident.
|
||||
rootClone, err := dupFile(root)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return rootClone, unsafePath, lastError
|
||||
}
|
59
vendor/github.com/cyphar/filepath-securejoin/openat_linux.go
generated
vendored
59
vendor/github.com/cyphar/filepath-securejoin/openat_linux.go
generated
vendored
@ -1,59 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package securejoin
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func dupFile(f *os.File) (*os.File, error) {
|
||||
fd, err := unix.FcntlInt(f.Fd(), unix.F_DUPFD_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("fcntl(F_DUPFD_CLOEXEC)", err)
|
||||
}
|
||||
return os.NewFile(uintptr(fd), f.Name()), nil
|
||||
}
|
||||
|
||||
func openatFile(dir *os.File, path string, flags int, mode int) (*os.File, error) {
|
||||
// Make sure we always set O_CLOEXEC.
|
||||
flags |= unix.O_CLOEXEC
|
||||
fd, err := unix.Openat(int(dir.Fd()), path, flags, uint32(mode))
|
||||
if err != nil {
|
||||
return nil, &os.PathError{Op: "openat", Path: dir.Name() + "/" + path, Err: err}
|
||||
}
|
||||
// All of the paths we use with openatFile(2) are guaranteed to be
|
||||
// lexically safe, so we can use path.Join here.
|
||||
fullPath := filepath.Join(dir.Name(), path)
|
||||
return os.NewFile(uintptr(fd), fullPath), nil
|
||||
}
|
||||
|
||||
func fstatatFile(dir *os.File, path string, flags int) (unix.Stat_t, error) {
|
||||
var stat unix.Stat_t
|
||||
if err := unix.Fstatat(int(dir.Fd()), path, &stat, flags); err != nil {
|
||||
return stat, &os.PathError{Op: "fstatat", Path: dir.Name() + "/" + path, Err: err}
|
||||
}
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
func readlinkatFile(dir *os.File, path string) (string, error) {
|
||||
size := 4096
|
||||
for {
|
||||
linkBuf := make([]byte, size)
|
||||
n, err := unix.Readlinkat(int(dir.Fd()), path, linkBuf)
|
||||
if err != nil {
|
||||
return "", &os.PathError{Op: "readlinkat", Path: dir.Name() + "/" + path, Err: err}
|
||||
}
|
||||
if n != size {
|
||||
return string(linkBuf[:n]), nil
|
||||
}
|
||||
// Possible truncation, resize the buffer.
|
||||
size *= 2
|
||||
}
|
||||
}
|
33
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md
generated
vendored
Normal file
33
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
## `pathrs-lite` ##
|
||||
|
||||
`github.com/cyphar/filepath-securejoin/pathrs-lite` provides a minimal **pure
|
||||
Go** implementation of the core bits of [libpathrs][]. This is not intended to
|
||||
be a complete replacement for libpathrs, instead it is mainly intended to be
|
||||
useful as a transition tool for existing Go projects.
|
||||
|
||||
The long-term plan for `pathrs-lite` is to provide a build tag that will cause
|
||||
all `pathrs-lite` operations to call into libpathrs directly, thus removing
|
||||
code duplication for projects that wish to make use of libpathrs (and providing
|
||||
the ability for software packagers to opt-in to libpathrs support without
|
||||
needing to patch upstream).
|
||||
|
||||
[libpathrs]: https://github.com/cyphar/libpathrs
|
||||
|
||||
### License ###
|
||||
|
||||
Most of this subpackage is licensed under the Mozilla Public License (version
|
||||
2.0). For more information, see the top-level [COPYING.md][] and
|
||||
[LICENSE.MPL-2.0][] files, as well as the individual license headers for each
|
||||
file.
|
||||
|
||||
```
|
||||
Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
Copyright (C) 2024-2025 SUSE LLC
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
```
|
||||
|
||||
[COPYING.md]: ../COPYING.md
|
||||
[LICENSE.MPL-2.0]: ../LICENSE.MPL-2.0
|
14
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go
generated
vendored
Normal file
14
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package pathrs (pathrs-lite) is a less complete pure Go implementation of
|
||||
// some of the APIs provided by [libpathrs].
|
||||
package pathrs
|
30
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go
generated
vendored
Normal file
30
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
// Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package assert provides some basic assertion helpers for Go.
|
||||
package assert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Assert panics if the predicate is false with the provided argument.
|
||||
func Assert(predicate bool, msg any) {
|
||||
if !predicate {
|
||||
panic(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// Assertf panics if the predicate is false and formats the message using the
|
||||
// same formatting as [fmt.Printf].
|
||||
//
|
||||
// [fmt.Printf]: https://pkg.go.dev/fmt#Printf
|
||||
func Assertf(predicate bool, fmtMsg string, args ...any) {
|
||||
Assert(predicate, fmt.Sprintf(fmtMsg, args...))
|
||||
}
|
30
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors.go
generated
vendored
Normal file
30
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package internal contains unexported common code for filepath-securejoin.
|
||||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrPossibleAttack indicates that some attack was detected.
|
||||
ErrPossibleAttack = errors.New("possible attack detected")
|
||||
|
||||
// ErrPossibleBreakout indicates that during an operation we ended up in a
|
||||
// state that could be a breakout but we detected it.
|
||||
ErrPossibleBreakout = errors.New("possible breakout detected")
|
||||
|
||||
// ErrInvalidDirectory indicates an unlinked directory.
|
||||
ErrInvalidDirectory = errors.New("wandered into deleted directory")
|
||||
|
||||
// ErrDeletedInode indicates an unlinked file (non-directory).
|
||||
ErrDeletedInode = errors.New("cannot verify path of deleted inode")
|
||||
)
|
148
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go
generated
vendored
Normal file
148
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go
generated
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package fd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
|
||||
)
|
||||
|
||||
// prepareAtWith returns -EBADF (an invalid fd) if dir is nil, otherwise using
|
||||
// the dir.Fd(). We use -EBADF because in filepath-securejoin we generally
|
||||
// don't want to allow relative-to-cwd paths. The returned path is an
|
||||
// *informational* string that describes a reasonable pathname for the given
|
||||
// *at(2) arguments. You must not use the full path for any actual filesystem
|
||||
// operations.
|
||||
func prepareAt(dir Fd, path string) (dirFd int, unsafeUnmaskedPath string) {
|
||||
dirFd, dirPath := -int(unix.EBADF), "."
|
||||
if dir != nil {
|
||||
dirFd, dirPath = int(dir.Fd()), dir.Name()
|
||||
}
|
||||
if !filepath.IsAbs(path) {
|
||||
// only prepend the dirfd path for relative paths
|
||||
path = dirPath + "/" + path
|
||||
}
|
||||
// NOTE: If path is "." or "", the returned path won't be filepath.Clean,
|
||||
// but that's okay since this path is either used for errors (in which case
|
||||
// a trailing "/" or "/." is important information) or will be
|
||||
// filepath.Clean'd later (in the case of fd.Openat).
|
||||
return dirFd, path
|
||||
}
|
||||
|
||||
// Openat is an [Fd]-based wrapper around unix.Openat.
|
||||
func Openat(dir Fd, path string, flags int, mode int) (*os.File, error) { //nolint:unparam // wrapper func
|
||||
dirFd, fullPath := prepareAt(dir, path)
|
||||
// Make sure we always set O_CLOEXEC.
|
||||
flags |= unix.O_CLOEXEC
|
||||
fd, err := unix.Openat(dirFd, path, flags, uint32(mode))
|
||||
if err != nil {
|
||||
return nil, &os.PathError{Op: "openat", Path: fullPath, Err: err}
|
||||
}
|
||||
runtime.KeepAlive(dir)
|
||||
// openat is only used with lexically-safe paths so we can use
|
||||
// filepath.Clean here, and also the path itself is not going to be used
|
||||
// for actual path operations.
|
||||
fullPath = filepath.Clean(fullPath)
|
||||
return os.NewFile(uintptr(fd), fullPath), nil
|
||||
}
|
||||
|
||||
// Fstatat is an [Fd]-based wrapper around unix.Fstatat.
|
||||
func Fstatat(dir Fd, path string, flags int) (unix.Stat_t, error) {
|
||||
dirFd, fullPath := prepareAt(dir, path)
|
||||
var stat unix.Stat_t
|
||||
if err := unix.Fstatat(dirFd, path, &stat, flags); err != nil {
|
||||
return stat, &os.PathError{Op: "fstatat", Path: fullPath, Err: err}
|
||||
}
|
||||
runtime.KeepAlive(dir)
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
// Faccessat is an [Fd]-based wrapper around unix.Faccessat.
|
||||
func Faccessat(dir Fd, path string, mode uint32, flags int) error {
|
||||
dirFd, fullPath := prepareAt(dir, path)
|
||||
err := unix.Faccessat(dirFd, path, mode, flags)
|
||||
if err != nil {
|
||||
err = &os.PathError{Op: "faccessat", Path: fullPath, Err: err}
|
||||
}
|
||||
runtime.KeepAlive(dir)
|
||||
return err
|
||||
}
|
||||
|
||||
// Readlinkat is an [Fd]-based wrapper around unix.Readlinkat.
|
||||
func Readlinkat(dir Fd, path string) (string, error) {
|
||||
dirFd, fullPath := prepareAt(dir, path)
|
||||
size := 4096
|
||||
for {
|
||||
linkBuf := make([]byte, size)
|
||||
n, err := unix.Readlinkat(dirFd, path, linkBuf)
|
||||
if err != nil {
|
||||
return "", &os.PathError{Op: "readlinkat", Path: fullPath, Err: err}
|
||||
}
|
||||
runtime.KeepAlive(dir)
|
||||
if n != size {
|
||||
return string(linkBuf[:n]), nil
|
||||
}
|
||||
// Possible truncation, resize the buffer.
|
||||
size *= 2
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// STATX_MNT_ID_UNIQUE is provided in golang.org/x/sys@v0.20.0, but in order to
|
||||
// avoid bumping the requirement for a single constant we can just define it
|
||||
// ourselves.
|
||||
_STATX_MNT_ID_UNIQUE = 0x4000 //nolint:revive // unix.* name
|
||||
|
||||
// We don't care which mount ID we get. The kernel will give us the unique
|
||||
// one if it is supported. If the kernel doesn't support
|
||||
// STATX_MNT_ID_UNIQUE, the bit is ignored and the returned request mask
|
||||
// will only contain STATX_MNT_ID (if supported).
|
||||
wantStatxMntMask = _STATX_MNT_ID_UNIQUE | unix.STATX_MNT_ID
|
||||
)
|
||||
|
||||
var hasStatxMountID = gocompat.SyncOnceValue(func() bool {
|
||||
var stx unix.Statx_t
|
||||
err := unix.Statx(-int(unix.EBADF), "/", 0, wantStatxMntMask, &stx)
|
||||
return err == nil && stx.Mask&wantStatxMntMask != 0
|
||||
})
|
||||
|
||||
// GetMountID gets the mount identifier associated with the fd and path
|
||||
// combination. It is effectively a wrapper around fetching
|
||||
// STATX_MNT_ID{,_UNIQUE} with unix.Statx, but with a fallback to 0 if the
|
||||
// kernel doesn't support the feature.
|
||||
func GetMountID(dir Fd, path string) (uint64, error) {
|
||||
// If we don't have statx(STATX_MNT_ID*) support, we can't do anything.
|
||||
if !hasStatxMountID() {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
dirFd, fullPath := prepareAt(dir, path)
|
||||
|
||||
var stx unix.Statx_t
|
||||
err := unix.Statx(dirFd, path, unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW, wantStatxMntMask, &stx)
|
||||
if stx.Mask&wantStatxMntMask == 0 {
|
||||
// It's not a kernel limitation, for some reason we couldn't get a
|
||||
// mount ID. Assume it's some kind of attack.
|
||||
err = fmt.Errorf("could not get mount id: %w", err)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, &os.PathError{Op: "statx(STATX_MNT_ID_...)", Path: fullPath, Err: err}
|
||||
}
|
||||
runtime.KeepAlive(dir)
|
||||
return stx.Mnt_id, nil
|
||||
}
|
55
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go
generated
vendored
Normal file
55
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
// Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package fd provides a drop-in interface-based replacement of [*os.File] that
|
||||
// allows for things like noop-Close wrappers to be used.
|
||||
//
|
||||
// [*os.File]: https://pkg.go.dev/os#File
|
||||
package fd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Fd is an interface that mirrors most of the API of [*os.File], allowing you
|
||||
// to create wrappers that can be used in place of [*os.File].
|
||||
//
|
||||
// [*os.File]: https://pkg.go.dev/os#File
|
||||
type Fd interface {
|
||||
io.Closer
|
||||
Name() string
|
||||
Fd() uintptr
|
||||
}
|
||||
|
||||
// Compile-time interface checks.
|
||||
var (
|
||||
_ Fd = (*os.File)(nil)
|
||||
_ Fd = noClose{}
|
||||
)
|
||||
|
||||
type noClose struct{ inner Fd }
|
||||
|
||||
func (f noClose) Name() string { return f.inner.Name() }
|
||||
func (f noClose) Fd() uintptr { return f.inner.Fd() }
|
||||
|
||||
func (f noClose) Close() error { return nil }
|
||||
|
||||
// NopCloser returns an [*os.File]-like object where the [Close] method is now
|
||||
// a no-op.
|
||||
//
|
||||
// Note that for [*os.File] and similar objects, the Go garbage collector will
|
||||
// still call [Close] on the underlying file unless you use
|
||||
// [runtime.SetFinalizer] to disable this behaviour. This is up to the caller
|
||||
// to do (if necessary).
|
||||
//
|
||||
// [*os.File]: https://pkg.go.dev/os#File
|
||||
// [Close]: https://pkg.go.dev/io#Closer
|
||||
// [runtime.SetFinalizer]: https://pkg.go.dev/runtime#SetFinalizer
|
||||
func NopCloser(f Fd) Fd { return noClose{inner: f} }
|
78
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go
generated
vendored
Normal file
78
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package fd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal"
|
||||
)
|
||||
|
||||
// DupWithName creates a new file descriptor referencing the same underlying
|
||||
// file, but with the provided name instead of fd.Name().
|
||||
func DupWithName(fd Fd, name string) (*os.File, error) {
|
||||
fd2, err := unix.FcntlInt(fd.Fd(), unix.F_DUPFD_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("fcntl(F_DUPFD_CLOEXEC)", err)
|
||||
}
|
||||
runtime.KeepAlive(fd)
|
||||
return os.NewFile(uintptr(fd2), name), nil
|
||||
}
|
||||
|
||||
// Dup creates a new file description referencing the same underlying file.
|
||||
func Dup(fd Fd) (*os.File, error) {
|
||||
return DupWithName(fd, fd.Name())
|
||||
}
|
||||
|
||||
// Fstat is an [Fd]-based wrapper around unix.Fstat.
|
||||
func Fstat(fd Fd) (unix.Stat_t, error) {
|
||||
var stat unix.Stat_t
|
||||
if err := unix.Fstat(int(fd.Fd()), &stat); err != nil {
|
||||
return stat, &os.PathError{Op: "fstat", Path: fd.Name(), Err: err}
|
||||
}
|
||||
runtime.KeepAlive(fd)
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
// Fstatfs is an [Fd]-based wrapper around unix.Fstatfs.
|
||||
func Fstatfs(fd Fd) (unix.Statfs_t, error) {
|
||||
var statfs unix.Statfs_t
|
||||
if err := unix.Fstatfs(int(fd.Fd()), &statfs); err != nil {
|
||||
return statfs, &os.PathError{Op: "fstatfs", Path: fd.Name(), Err: err}
|
||||
}
|
||||
runtime.KeepAlive(fd)
|
||||
return statfs, nil
|
||||
}
|
||||
|
||||
// IsDeadInode detects whether the file has been unlinked from a filesystem and
|
||||
// is thus a "dead inode" from the kernel's perspective.
|
||||
func IsDeadInode(file Fd) error {
|
||||
// If the nlink of a file drops to 0, there is an attacker deleting
|
||||
// directories during our walk, which could result in weird /proc values.
|
||||
// It's better to error out in this case.
|
||||
stat, err := Fstat(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check for dead inode: %w", err)
|
||||
}
|
||||
if stat.Nlink == 0 {
|
||||
err := internal.ErrDeletedInode
|
||||
if stat.Mode&unix.S_IFMT == unix.S_IFDIR {
|
||||
err = internal.ErrInvalidDirectory
|
||||
}
|
||||
return fmt.Errorf("%w %q", err, file.Name())
|
||||
}
|
||||
return nil
|
||||
}
|
54
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go
generated
vendored
Normal file
54
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package fd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Fsopen is an [Fd]-based wrapper around unix.Fsopen.
|
||||
func Fsopen(fsName string, flags int) (*os.File, error) {
|
||||
// Make sure we always set O_CLOEXEC.
|
||||
flags |= unix.FSOPEN_CLOEXEC
|
||||
fd, err := unix.Fsopen(fsName, flags)
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("fsopen "+fsName, err)
|
||||
}
|
||||
return os.NewFile(uintptr(fd), "fscontext:"+fsName), nil
|
||||
}
|
||||
|
||||
// Fsmount is an [Fd]-based wrapper around unix.Fsmount.
|
||||
func Fsmount(ctx Fd, flags, mountAttrs int) (*os.File, error) {
|
||||
// Make sure we always set O_CLOEXEC.
|
||||
flags |= unix.FSMOUNT_CLOEXEC
|
||||
fd, err := unix.Fsmount(int(ctx.Fd()), flags, mountAttrs)
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("fsmount "+ctx.Name(), err)
|
||||
}
|
||||
return os.NewFile(uintptr(fd), "fsmount:"+ctx.Name()), nil
|
||||
}
|
||||
|
||||
// OpenTree is an [Fd]-based wrapper around unix.OpenTree.
|
||||
func OpenTree(dir Fd, path string, flags uint) (*os.File, error) {
|
||||
dirFd, fullPath := prepareAt(dir, path)
|
||||
// Make sure we always set O_CLOEXEC.
|
||||
flags |= unix.OPEN_TREE_CLOEXEC
|
||||
fd, err := unix.OpenTree(dirFd, path, flags)
|
||||
if err != nil {
|
||||
return nil, &os.PathError{Op: "open_tree", Path: fullPath, Err: err}
|
||||
}
|
||||
runtime.KeepAlive(dir)
|
||||
return os.NewFile(uintptr(fd), fullPath), nil
|
||||
}
|
62
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go
generated
vendored
Normal file
62
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package fd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal"
|
||||
)
|
||||
|
||||
func scopedLookupShouldRetry(how *unix.OpenHow, err error) bool {
|
||||
// RESOLVE_IN_ROOT (and RESOLVE_BENEATH) can return -EAGAIN if we resolve
|
||||
// ".." while a mount or rename occurs anywhere on the system. This could
|
||||
// happen spuriously, or as the result of an attacker trying to mess with
|
||||
// us during lookup.
|
||||
//
|
||||
// In addition, scoped lookups have a "safety check" at the end of
|
||||
// complete_walk which will return -EXDEV if the final path is not in the
|
||||
// root.
|
||||
return how.Resolve&(unix.RESOLVE_IN_ROOT|unix.RESOLVE_BENEATH) != 0 &&
|
||||
(errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EXDEV))
|
||||
}
|
||||
|
||||
const scopedLookupMaxRetries = 32
|
||||
|
||||
// Openat2 is an [Fd]-based wrapper around unix.Openat2, but with some retry
|
||||
// logic in case of EAGAIN errors.
|
||||
func Openat2(dir Fd, path string, how *unix.OpenHow) (*os.File, error) {
|
||||
dirFd, fullPath := prepareAt(dir, path)
|
||||
// Make sure we always set O_CLOEXEC.
|
||||
how.Flags |= unix.O_CLOEXEC
|
||||
var tries int
|
||||
for tries < scopedLookupMaxRetries {
|
||||
fd, err := unix.Openat2(dirFd, path, how)
|
||||
if err != nil {
|
||||
if scopedLookupShouldRetry(how, err) {
|
||||
// We retry a couple of times to avoid the spurious errors, and
|
||||
// if we are being attacked then returning -EAGAIN is the best
|
||||
// we can do.
|
||||
tries++
|
||||
continue
|
||||
}
|
||||
return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: err}
|
||||
}
|
||||
runtime.KeepAlive(dir)
|
||||
return os.NewFile(uintptr(fd), fullPath), nil
|
||||
}
|
||||
return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: internal.ErrPossibleAttack}
|
||||
}
|
10
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md
generated
vendored
Normal file
10
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
## gocompat ##
|
||||
|
||||
This directory contains backports of stdlib functions from later Go versions so
|
||||
the filepath-securejoin can continue to be used by projects that are stuck with
|
||||
Go 1.18 support. Note that often filepath-securejoin is added in security
|
||||
patches for old releases, so avoiding the need to bump Go compiler requirements
|
||||
is a huge plus to downstreams.
|
||||
|
||||
The source code is licensed under the same license as the Go stdlib. See the
|
||||
source files for the precise license information.
|
13
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go
generated
vendored
Normal file
13
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build linux && go1.20
|
||||
|
||||
// Copyright (C) 2025 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package gocompat includes compatibility shims (backported from future Go
|
||||
// stdlib versions) to permit filepath-securejoin to be used with older Go
|
||||
// versions (often filepath-securejoin is added in security patches for old
|
||||
// releases, so avoiding the need to bump Go compiler requirements is a huge
|
||||
// plus to downstreams).
|
||||
package gocompat
|
@ -1,18 +1,19 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build linux && go1.20
|
||||
|
||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package securejoin
|
||||
package gocompat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// wrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except
|
||||
// WrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except
|
||||
// that on pre-1.20 Go versions only errors.Is() works properly (errors.Unwrap)
|
||||
// is only guaranteed to give you baseErr.
|
||||
func wrapBaseError(baseErr, extraErr error) error {
|
||||
func WrapBaseError(baseErr, extraErr error) error {
|
||||
return fmt.Errorf("%w: %w", extraErr, baseErr)
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux && !go1.20
|
||||
|
||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package securejoin
|
||||
package gocompat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -27,10 +29,10 @@ func (err wrappedError) Error() string {
|
||||
return fmt.Sprintf("%v: %v", err.isError, err.inner)
|
||||
}
|
||||
|
||||
// wrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except
|
||||
// WrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except
|
||||
// that on pre-1.20 Go versions only errors.Is() works properly (errors.Unwrap)
|
||||
// is only guaranteed to give you baseErr.
|
||||
func wrapBaseError(baseErr, extraErr error) error {
|
||||
func WrapBaseError(baseErr, extraErr error) error {
|
||||
return wrappedError{
|
||||
inner: baseErr,
|
||||
isError: extraErr,
|
53
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_go121.go
generated
vendored
Normal file
53
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_go121.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux && go1.21
|
||||
|
||||
// Copyright (C) 2024-2025 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gocompat
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"slices"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// SlicesDeleteFunc is equivalent to Go 1.21's slices.DeleteFunc.
|
||||
func SlicesDeleteFunc[S ~[]E, E any](slice S, delFn func(E) bool) S {
|
||||
return slices.DeleteFunc(slice, delFn)
|
||||
}
|
||||
|
||||
// SlicesContains is equivalent to Go 1.21's slices.Contains.
|
||||
func SlicesContains[S ~[]E, E comparable](slice S, val E) bool {
|
||||
return slices.Contains(slice, val)
|
||||
}
|
||||
|
||||
// SlicesClone is equivalent to Go 1.21's slices.Clone.
|
||||
func SlicesClone[S ~[]E, E any](slice S) S {
|
||||
return slices.Clone(slice)
|
||||
}
|
||||
|
||||
// SyncOnceValue is equivalent to Go 1.21's sync.OnceValue.
|
||||
func SyncOnceValue[T any](f func() T) func() T {
|
||||
return sync.OnceValue(f)
|
||||
}
|
||||
|
||||
// SyncOnceValues is equivalent to Go 1.21's sync.OnceValues.
|
||||
func SyncOnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
|
||||
return sync.OnceValues(f)
|
||||
}
|
||||
|
||||
// CmpOrdered is equivalent to Go 1.21's cmp.Ordered generic type definition.
|
||||
type CmpOrdered = cmp.Ordered
|
||||
|
||||
// CmpCompare is equivalent to Go 1.21's cmp.Compare.
|
||||
func CmpCompare[T CmpOrdered](x, y T) int {
|
||||
return cmp.Compare(x, y)
|
||||
}
|
||||
|
||||
// Max2 is equivalent to Go 1.21's max builtin (but only for two parameters).
|
||||
func Max2[T CmpOrdered](x, y T) T {
|
||||
return max(x, y)
|
||||
}
|
187
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_unsupported.go
generated
vendored
Normal file
187
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux && !go1.21
|
||||
|
||||
// Copyright (C) 2021, 2022 The Go Authors. All rights reserved.
|
||||
// Copyright (C) 2024-2025 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE.BSD file.
|
||||
|
||||
package gocompat
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// These are very minimal implementations of functions that appear in Go 1.21's
|
||||
// stdlib, included so that we can build on older Go versions. Most are
|
||||
// borrowed directly from the stdlib, and a few are modified to be "obviously
|
||||
// correct" without needing to copy too many other helpers.
|
||||
|
||||
// clearSlice is equivalent to Go 1.21's builtin clear.
|
||||
// Copied from the Go 1.24 stdlib implementation.
|
||||
func clearSlice[S ~[]E, E any](slice S) {
|
||||
var zero E
|
||||
for i := range slice {
|
||||
slice[i] = zero
|
||||
}
|
||||
}
|
||||
|
||||
// slicesIndexFunc is equivalent to Go 1.21's slices.IndexFunc.
|
||||
// Copied from the Go 1.24 stdlib implementation.
|
||||
func slicesIndexFunc[S ~[]E, E any](s S, f func(E) bool) int {
|
||||
for i := range s {
|
||||
if f(s[i]) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// SlicesDeleteFunc is equivalent to Go 1.21's slices.DeleteFunc.
|
||||
// Copied from the Go 1.24 stdlib implementation.
|
||||
func SlicesDeleteFunc[S ~[]E, E any](s S, del func(E) bool) S {
|
||||
i := slicesIndexFunc(s, del)
|
||||
if i == -1 {
|
||||
return s
|
||||
}
|
||||
// Don't start copying elements until we find one to delete.
|
||||
for j := i + 1; j < len(s); j++ {
|
||||
if v := s[j]; !del(v) {
|
||||
s[i] = v
|
||||
i++
|
||||
}
|
||||
}
|
||||
clearSlice(s[i:]) // zero/nil out the obsolete elements, for GC
|
||||
return s[:i]
|
||||
}
|
||||
|
||||
// SlicesContains is equivalent to Go 1.21's slices.Contains.
|
||||
// Similar to the stdlib slices.Contains, except that we don't have
|
||||
// slices.Index so we need to use slices.IndexFunc for this non-Func helper.
|
||||
func SlicesContains[S ~[]E, E comparable](s S, v E) bool {
|
||||
return slicesIndexFunc(s, func(e E) bool { return e == v }) >= 0
|
||||
}
|
||||
|
||||
// SlicesClone is equivalent to Go 1.21's slices.Clone.
|
||||
// Copied from the Go 1.24 stdlib implementation.
|
||||
func SlicesClone[S ~[]E, E any](s S) S {
|
||||
// Preserve nil in case it matters.
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
return append(S([]E{}), s...)
|
||||
}
|
||||
|
||||
// SyncOnceValue is equivalent to Go 1.21's sync.OnceValue.
|
||||
// Copied from the Go 1.25 stdlib implementation.
|
||||
func SyncOnceValue[T any](f func() T) func() T {
|
||||
// Use a struct so that there's a single heap allocation.
|
||||
d := struct {
|
||||
f func() T
|
||||
once sync.Once
|
||||
valid bool
|
||||
p any
|
||||
result T
|
||||
}{
|
||||
f: f,
|
||||
}
|
||||
return func() T {
|
||||
d.once.Do(func() {
|
||||
defer func() {
|
||||
d.f = nil
|
||||
d.p = recover()
|
||||
if !d.valid {
|
||||
panic(d.p)
|
||||
}
|
||||
}()
|
||||
d.result = d.f()
|
||||
d.valid = true
|
||||
})
|
||||
if !d.valid {
|
||||
panic(d.p)
|
||||
}
|
||||
return d.result
|
||||
}
|
||||
}
|
||||
|
||||
// SyncOnceValues is equivalent to Go 1.21's sync.OnceValues.
|
||||
// Copied from the Go 1.25 stdlib implementation.
|
||||
func SyncOnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
|
||||
// Use a struct so that there's a single heap allocation.
|
||||
d := struct {
|
||||
f func() (T1, T2)
|
||||
once sync.Once
|
||||
valid bool
|
||||
p any
|
||||
r1 T1
|
||||
r2 T2
|
||||
}{
|
||||
f: f,
|
||||
}
|
||||
return func() (T1, T2) {
|
||||
d.once.Do(func() {
|
||||
defer func() {
|
||||
d.f = nil
|
||||
d.p = recover()
|
||||
if !d.valid {
|
||||
panic(d.p)
|
||||
}
|
||||
}()
|
||||
d.r1, d.r2 = d.f()
|
||||
d.valid = true
|
||||
})
|
||||
if !d.valid {
|
||||
panic(d.p)
|
||||
}
|
||||
return d.r1, d.r2
|
||||
}
|
||||
}
|
||||
|
||||
// CmpOrdered is equivalent to Go 1.21's cmp.Ordered generic type definition.
|
||||
// Copied from the Go 1.25 stdlib implementation.
|
||||
type CmpOrdered interface {
|
||||
~int | ~int8 | ~int16 | ~int32 | ~int64 |
|
||||
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
|
||||
~float32 | ~float64 |
|
||||
~string
|
||||
}
|
||||
|
||||
// isNaN reports whether x is a NaN without requiring the math package.
|
||||
// This will always return false if T is not floating-point.
|
||||
// Copied from the Go 1.25 stdlib implementation.
|
||||
func isNaN[T CmpOrdered](x T) bool {
|
||||
return x != x
|
||||
}
|
||||
|
||||
// CmpCompare is equivalent to Go 1.21's cmp.Compare.
|
||||
// Copied from the Go 1.25 stdlib implementation.
|
||||
func CmpCompare[T CmpOrdered](x, y T) int {
|
||||
xNaN := isNaN(x)
|
||||
yNaN := isNaN(y)
|
||||
if xNaN {
|
||||
if yNaN {
|
||||
return 0
|
||||
}
|
||||
return -1
|
||||
}
|
||||
if yNaN {
|
||||
return +1
|
||||
}
|
||||
if x < y {
|
||||
return -1
|
||||
}
|
||||
if x > y {
|
||||
return +1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Max2 is equivalent to Go 1.21's max builtin for two parameters.
|
||||
func Max2[T CmpOrdered](x, y T) T {
|
||||
m := x
|
||||
if y > m {
|
||||
m = y
|
||||
}
|
||||
return m
|
||||
}
|
123
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion/kernel_linux.go
generated
vendored
Normal file
123
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion/kernel_linux.go
generated
vendored
Normal file
@ -0,0 +1,123 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Copyright (C) 2022 The Go Authors. All rights reserved.
|
||||
// Copyright (C) 2025 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE.BSD file.
|
||||
|
||||
// The parsing logic is very loosely based on the Go stdlib's
|
||||
// src/internal/syscall/unix/kernel_version_linux.go but with an API that looks
|
||||
// a bit like runc's libcontainer/system/kernelversion.
|
||||
//
|
||||
// TODO(cyphar): This API has been copied around to a lot of different projects
|
||||
// (Docker, containerd, runc, and now filepath-securejoin) -- maybe we should
|
||||
// put it in a separate project?
|
||||
|
||||
// Package kernelversion provides a simple mechanism for checking whether the
|
||||
// running kernel is at least as new as some baseline kernel version. This is
|
||||
// often useful when checking for features that would be too complicated to
|
||||
// test support for (or in cases where we know that some kernel features in
|
||||
// backport-heavy kernels are broken and need to be avoided).
|
||||
package kernelversion
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
|
||||
)
|
||||
|
||||
// KernelVersion is a numeric representation of the key numerical elements of a
|
||||
// kernel version (for instance, "4.1.2-default-1" would be represented as
|
||||
// KernelVersion{4, 1, 2}).
|
||||
type KernelVersion []uint64
|
||||
|
||||
func (kver KernelVersion) String() string {
|
||||
var str strings.Builder
|
||||
for idx, elem := range kver {
|
||||
if idx != 0 {
|
||||
_, _ = str.WriteRune('.')
|
||||
}
|
||||
_, _ = str.WriteString(strconv.FormatUint(elem, 10))
|
||||
}
|
||||
return str.String()
|
||||
}
|
||||
|
||||
var errInvalidKernelVersion = errors.New("invalid kernel version")
|
||||
|
||||
// parseKernelVersion parses a string and creates a KernelVersion based on it.
|
||||
func parseKernelVersion(kverStr string) (KernelVersion, error) {
|
||||
kver := make(KernelVersion, 1, 3)
|
||||
for idx, ch := range kverStr {
|
||||
if '0' <= ch && ch <= '9' {
|
||||
v := &kver[len(kver)-1]
|
||||
*v = (*v * 10) + uint64(ch-'0')
|
||||
} else {
|
||||
if idx == 0 || kverStr[idx-1] < '0' || '9' < kverStr[idx-1] {
|
||||
// "." must be preceded by a digit while in version section
|
||||
return nil, fmt.Errorf("%w %q: kernel version has dot(s) followed by non-digit in version section", errInvalidKernelVersion, kverStr)
|
||||
}
|
||||
if ch != '.' {
|
||||
break
|
||||
}
|
||||
kver = append(kver, 0)
|
||||
}
|
||||
}
|
||||
if len(kver) < 2 {
|
||||
return nil, fmt.Errorf("%w %q: kernel versions must contain at least two components", errInvalidKernelVersion, kverStr)
|
||||
}
|
||||
return kver, nil
|
||||
}
|
||||
|
||||
// getKernelVersion gets the current kernel version.
|
||||
var getKernelVersion = gocompat.SyncOnceValues(func() (KernelVersion, error) {
|
||||
var uts unix.Utsname
|
||||
if err := unix.Uname(&uts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Remove the \x00 from the release.
|
||||
release := uts.Release[:]
|
||||
return parseKernelVersion(string(release[:bytes.IndexByte(release, 0)]))
|
||||
})
|
||||
|
||||
// GreaterEqualThan returns true if the the host kernel version is greater than
|
||||
// or equal to the provided [KernelVersion]. When doing this comparison, any
|
||||
// non-numerical suffixes of the host kernel version are ignored.
|
||||
//
|
||||
// If the number of components provided is not equal to the number of numerical
|
||||
// components of the host kernel version, any missing components are treated as
|
||||
// 0. This means that GreaterEqualThan(KernelVersion{4}) will be treated the
|
||||
// same as GreaterEqualThan(KernelVersion{4, 0, 0, ..., 0, 0}), and that if the
|
||||
// host kernel version is "4" then GreaterEqualThan(KernelVersion{4, 1}) will
|
||||
// return false (because the host version will be treated as "4.0").
|
||||
func GreaterEqualThan(wantKver KernelVersion) (bool, error) {
|
||||
hostKver, err := getKernelVersion()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Pad out the kernel version lengths to match one another.
|
||||
cmpLen := gocompat.Max2(len(hostKver), len(wantKver))
|
||||
hostKver = append(hostKver, make(KernelVersion, cmpLen-len(hostKver))...)
|
||||
wantKver = append(wantKver, make(KernelVersion, cmpLen-len(wantKver))...)
|
||||
|
||||
for i := 0; i < cmpLen; i++ {
|
||||
switch gocompat.CmpCompare(hostKver[i], wantKver[i]) {
|
||||
case -1:
|
||||
// host < want
|
||||
return false, nil
|
||||
case +1:
|
||||
// host > want
|
||||
return true, nil
|
||||
case 0:
|
||||
continue
|
||||
}
|
||||
}
|
||||
// equal version values
|
||||
return true, nil
|
||||
}
|
12
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go
generated
vendored
Normal file
12
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package linux returns information about what features are supported on the
|
||||
// running kernel.
|
||||
package linux
|
47
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go
generated
vendored
Normal file
47
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package linux
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion"
|
||||
)
|
||||
|
||||
// HasNewMountAPI returns whether the new fsopen(2) mount API is supported on
|
||||
// the running kernel.
|
||||
var HasNewMountAPI = gocompat.SyncOnceValue(func() bool {
|
||||
// All of the pieces of the new mount API we use (fsopen, fsconfig,
|
||||
// fsmount, open_tree) were added together in Linux 5.2[1,2], so we can
|
||||
// just check for one of the syscalls and the others should also be
|
||||
// available.
|
||||
//
|
||||
// Just try to use open_tree(2) to open a file without OPEN_TREE_CLONE.
|
||||
// This is equivalent to openat(2), but tells us if open_tree is
|
||||
// available (and thus all of the other basic new mount API syscalls).
|
||||
// open_tree(2) is most light-weight syscall to test here.
|
||||
//
|
||||
// [1]: merge commit 400913252d09
|
||||
// [2]: <https://lore.kernel.org/lkml/153754740781.17872.7869536526927736855.stgit@warthog.procyon.org.uk/>
|
||||
fd, err := unix.OpenTree(-int(unix.EBADF), "/", unix.OPEN_TREE_CLOEXEC)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_ = unix.Close(fd)
|
||||
|
||||
// RHEL 8 has a backport of fsopen(2) that appears to have some very
|
||||
// difficult to debug performance pathology. As such, it seems prudent to
|
||||
// simply reject pre-5.2 kernels.
|
||||
isNotBackport, _ := kernelversion.GreaterEqualThan(kernelversion.KernelVersion{5, 2})
|
||||
return isNotBackport
|
||||
})
|
31
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go
generated
vendored
Normal file
31
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package linux
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
|
||||
)
|
||||
|
||||
// HasOpenat2 returns whether openat2(2) is supported on the running kernel.
|
||||
var HasOpenat2 = gocompat.SyncOnceValue(func() bool {
|
||||
fd, err := unix.Openat2(unix.AT_FDCWD, ".", &unix.OpenHow{
|
||||
Flags: unix.O_PATH | unix.O_CLOEXEC,
|
||||
Resolve: unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_IN_ROOT,
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_ = unix.Close(fd)
|
||||
return true
|
||||
})
|
544
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go
generated
vendored
Normal file
544
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go
generated
vendored
Normal file
@ -0,0 +1,544 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package procfs provides a safe API for operating on /proc on Linux. Note
|
||||
// that this is the *internal* procfs API, mainy needed due to Go's
|
||||
// restrictions on cyclic dependencies and its incredibly minimal visibility
|
||||
// system without making a separate internal/ package.
|
||||
package procfs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux"
|
||||
)
|
||||
|
||||
// The kernel guarantees that the root inode of a procfs mount has an
|
||||
// f_type of PROC_SUPER_MAGIC and st_ino of PROC_ROOT_INO.
|
||||
const (
|
||||
procSuperMagic = 0x9fa0 // PROC_SUPER_MAGIC
|
||||
procRootIno = 1 // PROC_ROOT_INO
|
||||
)
|
||||
|
||||
// verifyProcHandle checks that the handle is from a procfs filesystem.
|
||||
// Contrast this to [verifyProcRoot], which also verifies that the handle is
|
||||
// the root of a procfs mount.
|
||||
func verifyProcHandle(procHandle fd.Fd) error {
|
||||
if statfs, err := fd.Fstatfs(procHandle); err != nil {
|
||||
return err
|
||||
} else if statfs.Type != procSuperMagic {
|
||||
return fmt.Errorf("%w: incorrect procfs root filesystem type 0x%x", errUnsafeProcfs, statfs.Type)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// verifyProcRoot verifies that the handle is the root of a procfs filesystem.
|
||||
// Contrast this to [verifyProcHandle], which only verifies if the handle is
|
||||
// some file on procfs (regardless of what file it is).
|
||||
func verifyProcRoot(procRoot fd.Fd) error {
|
||||
if err := verifyProcHandle(procRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
if stat, err := fd.Fstat(procRoot); err != nil {
|
||||
return err
|
||||
} else if stat.Ino != procRootIno {
|
||||
return fmt.Errorf("%w: incorrect procfs root inode number %d", errUnsafeProcfs, stat.Ino)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type procfsFeatures struct {
|
||||
// hasSubsetPid was added in Linux 5.8, along with hidepid=ptraceable (and
|
||||
// string-based hidepid= values). Before this patchset, it was not really
|
||||
// safe to try to modify procfs superblock flags because the superblock was
|
||||
// shared -- so if this feature is not available, **you should not set any
|
||||
// superblock flags**.
|
||||
//
|
||||
// 6814ef2d992a ("proc: add option to mount only a pids subset")
|
||||
// fa10fed30f25 ("proc: allow to mount many instances of proc in one pid namespace")
|
||||
// 24a71ce5c47f ("proc: instantiate only pids that we can ptrace on 'hidepid=4' mount option")
|
||||
// 1c6c4d112e81 ("proc: use human-readable values for hidepid")
|
||||
// 9ff7258575d5 ("Merge branch 'proc-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace")
|
||||
hasSubsetPid bool
|
||||
}
|
||||
|
||||
var getProcfsFeatures = gocompat.SyncOnceValue(func() procfsFeatures {
|
||||
if !linux.HasNewMountAPI() {
|
||||
return procfsFeatures{}
|
||||
}
|
||||
procfsCtx, err := fd.Fsopen("proc", unix.FSOPEN_CLOEXEC)
|
||||
if err != nil {
|
||||
return procfsFeatures{}
|
||||
}
|
||||
defer procfsCtx.Close() //nolint:errcheck // close failures aren't critical here
|
||||
|
||||
return procfsFeatures{
|
||||
hasSubsetPid: unix.FsconfigSetString(int(procfsCtx.Fd()), "subset", "pid") == nil,
|
||||
}
|
||||
})
|
||||
|
||||
func newPrivateProcMount(subset bool) (_ *Handle, Err error) {
|
||||
procfsCtx, err := fd.Fsopen("proc", unix.FSOPEN_CLOEXEC)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer procfsCtx.Close() //nolint:errcheck // close failures aren't critical here
|
||||
|
||||
if subset && getProcfsFeatures().hasSubsetPid {
|
||||
// Try to configure hidepid=ptraceable,subset=pid if possible, but
|
||||
// ignore errors.
|
||||
_ = unix.FsconfigSetString(int(procfsCtx.Fd()), "hidepid", "ptraceable")
|
||||
_ = unix.FsconfigSetString(int(procfsCtx.Fd()), "subset", "pid")
|
||||
}
|
||||
|
||||
// Get an actual handle.
|
||||
if err := unix.FsconfigCreate(int(procfsCtx.Fd())); err != nil {
|
||||
return nil, os.NewSyscallError("fsconfig create procfs", err)
|
||||
}
|
||||
// TODO: Output any information from the fscontext log to debug logs.
|
||||
procRoot, err := fd.Fsmount(procfsCtx, unix.FSMOUNT_CLOEXEC, unix.MS_NODEV|unix.MS_NOEXEC|unix.MS_NOSUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if Err != nil {
|
||||
_ = procRoot.Close()
|
||||
}
|
||||
}()
|
||||
return newHandle(procRoot)
|
||||
}
|
||||
|
||||
func clonePrivateProcMount() (_ *Handle, Err error) {
|
||||
// Try to make a clone without using AT_RECURSIVE if we can. If this works,
|
||||
// we can be sure there are no over-mounts and so if the root is valid then
|
||||
// we're golden. Otherwise, we have to deal with over-mounts.
|
||||
procRoot, err := fd.OpenTree(nil, "/proc", unix.OPEN_TREE_CLONE)
|
||||
if err != nil || hookForcePrivateProcRootOpenTreeAtRecursive(procRoot) {
|
||||
procRoot, err = fd.OpenTree(nil, "/proc", unix.OPEN_TREE_CLONE|unix.AT_RECURSIVE)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating a detached procfs clone: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if Err != nil {
|
||||
_ = procRoot.Close()
|
||||
}
|
||||
}()
|
||||
return newHandle(procRoot)
|
||||
}
|
||||
|
||||
func privateProcRoot(subset bool) (*Handle, error) {
|
||||
if !linux.HasNewMountAPI() || hookForceGetProcRootUnsafe() {
|
||||
return nil, fmt.Errorf("new mount api: %w", unix.ENOTSUP)
|
||||
}
|
||||
// Try to create a new procfs mount from scratch if we can. This ensures we
|
||||
// can get a procfs mount even if /proc is fake (for whatever reason).
|
||||
procRoot, err := newPrivateProcMount(subset)
|
||||
if err != nil || hookForcePrivateProcRootOpenTree(procRoot) {
|
||||
// Try to clone /proc then...
|
||||
procRoot, err = clonePrivateProcMount()
|
||||
}
|
||||
return procRoot, err
|
||||
}
|
||||
|
||||
func unsafeHostProcRoot() (_ *Handle, Err error) {
|
||||
procRoot, err := os.OpenFile("/proc", unix.O_PATH|unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if Err != nil {
|
||||
_ = procRoot.Close()
|
||||
}
|
||||
}()
|
||||
return newHandle(procRoot)
|
||||
}
|
||||
|
||||
// Handle is a wrapper around an *os.File handle to "/proc", which can be used
|
||||
// to do further procfs-related operations in a safe way.
|
||||
type Handle struct {
|
||||
Inner fd.Fd
|
||||
// Does this handle have subset=pid set?
|
||||
isSubset bool
|
||||
}
|
||||
|
||||
func newHandle(procRoot fd.Fd) (*Handle, error) {
|
||||
if err := verifyProcRoot(procRoot); err != nil {
|
||||
// This is only used in methods that
|
||||
_ = procRoot.Close()
|
||||
return nil, err
|
||||
}
|
||||
proc := &Handle{Inner: procRoot}
|
||||
// With subset=pid we can be sure that /proc/uptime will not exist.
|
||||
if err := fd.Faccessat(proc.Inner, "uptime", unix.F_OK, unix.AT_SYMLINK_NOFOLLOW); err != nil {
|
||||
proc.isSubset = errors.Is(err, os.ErrNotExist)
|
||||
}
|
||||
return proc, nil
|
||||
}
|
||||
|
||||
// Close closes the underlying file for the Handle.
|
||||
func (proc *Handle) Close() error { return proc.Inner.Close() }
|
||||
|
||||
var getCachedProcRoot = gocompat.SyncOnceValue(func() *Handle {
|
||||
procRoot, err := getProcRoot(true)
|
||||
if err != nil {
|
||||
return nil // just don't cache if we see an error
|
||||
}
|
||||
if !procRoot.isSubset {
|
||||
return nil // we only cache verified subset=pid handles
|
||||
}
|
||||
|
||||
// Disarm (*Handle).Close() to stop someone from accidentally closing
|
||||
// the global handle.
|
||||
procRoot.Inner = fd.NopCloser(procRoot.Inner)
|
||||
return procRoot
|
||||
})
|
||||
|
||||
// OpenProcRoot tries to open a "safer" handle to "/proc".
|
||||
func OpenProcRoot() (*Handle, error) {
|
||||
if proc := getCachedProcRoot(); proc != nil {
|
||||
return proc, nil
|
||||
}
|
||||
return getProcRoot(true)
|
||||
}
|
||||
|
||||
// OpenUnsafeProcRoot opens a handle to "/proc" without any overmounts or
|
||||
// masked paths (but also without "subset=pid").
|
||||
func OpenUnsafeProcRoot() (*Handle, error) { return getProcRoot(false) }
|
||||
|
||||
func getProcRoot(subset bool) (*Handle, error) {
|
||||
proc, err := privateProcRoot(subset)
|
||||
if err != nil {
|
||||
// Fall back to using a /proc handle if making a private mount failed.
|
||||
// If we have openat2, at least we can avoid some kinds of over-mount
|
||||
// attacks, but without openat2 there's not much we can do.
|
||||
proc, err = unsafeHostProcRoot()
|
||||
}
|
||||
return proc, err
|
||||
}
|
||||
|
||||
var hasProcThreadSelf = gocompat.SyncOnceValue(func() bool {
|
||||
return unix.Access("/proc/thread-self/", unix.F_OK) == nil
|
||||
})
|
||||
|
||||
var errUnsafeProcfs = errors.New("unsafe procfs detected")
|
||||
|
||||
// lookup is a very minimal wrapper around [procfsLookupInRoot] which is
|
||||
// intended to be called from the external API.
|
||||
func (proc *Handle) lookup(subpath string) (*os.File, error) {
|
||||
handle, err := procfsLookupInRoot(proc.Inner, subpath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
// procfsBase is an enum indicating the prefix of a subpath in operations
|
||||
// involving [Handle]s.
|
||||
type procfsBase string
|
||||
|
||||
const (
|
||||
// ProcRoot refers to the root of the procfs (i.e., "/proc/<subpath>").
|
||||
ProcRoot procfsBase = "/proc"
|
||||
// ProcSelf refers to the current process' subdirectory (i.e.,
|
||||
// "/proc/self/<subpath>").
|
||||
ProcSelf procfsBase = "/proc/self"
|
||||
// ProcThreadSelf refers to the current thread's subdirectory (i.e.,
|
||||
// "/proc/thread-self/<subpath>"). In multi-threaded programs (i.e., all Go
|
||||
// programs) where one thread has a different CLONE_FS, it is possible for
|
||||
// "/proc/self" to point the wrong thread and so "/proc/thread-self" may be
|
||||
// necessary. Note that on pre-3.17 kernels, "/proc/thread-self" doesn't
|
||||
// exist and so a fallback will be used in that case.
|
||||
ProcThreadSelf procfsBase = "/proc/thread-self"
|
||||
// TODO: Switch to an interface setup so we can have a more type-safe
|
||||
// version of ProcPid and remove the need to worry about invalid string
|
||||
// values.
|
||||
)
|
||||
|
||||
// prefix returns a prefix that can be used with the given [Handle].
|
||||
func (base procfsBase) prefix(proc *Handle) (string, error) {
|
||||
switch base {
|
||||
case ProcRoot:
|
||||
return ".", nil
|
||||
case ProcSelf:
|
||||
return "self", nil
|
||||
case ProcThreadSelf:
|
||||
threadSelf := "thread-self"
|
||||
if !hasProcThreadSelf() || hookForceProcSelfTask() {
|
||||
// Pre-3.17 kernels don't have /proc/thread-self, so do it
|
||||
// manually.
|
||||
threadSelf = "self/task/" + strconv.Itoa(unix.Gettid())
|
||||
if err := fd.Faccessat(proc.Inner, threadSelf, unix.F_OK, unix.AT_SYMLINK_NOFOLLOW); err != nil || hookForceProcSelf() {
|
||||
// In this case, we running in a pid namespace that doesn't
|
||||
// match the /proc mount we have. This can happen inside runc.
|
||||
//
|
||||
// Unfortunately, there is no nice way to get the correct TID
|
||||
// to use here because of the age of the kernel, so we have to
|
||||
// just use /proc/self and hope that it works.
|
||||
threadSelf = "self"
|
||||
}
|
||||
}
|
||||
return threadSelf, nil
|
||||
}
|
||||
return "", fmt.Errorf("invalid procfs base %q", base)
|
||||
}
|
||||
|
||||
// ProcThreadSelfCloser is a callback that needs to be called when you are done
|
||||
// operating on an [os.File] fetched using [ProcThreadSelf].
|
||||
//
|
||||
// [os.File]: https://pkg.go.dev/os#File
|
||||
type ProcThreadSelfCloser func()
|
||||
|
||||
// open is the core lookup operation for [Handle]. It returns a handle to
|
||||
// "/proc/<base>/<subpath>". If the returned [ProcThreadSelfCloser] is non-nil,
|
||||
// you should call it after you are done interacting with the returned handle.
|
||||
//
|
||||
// In general you should use prefer to use the other helpers, as they remove
|
||||
// the need to interact with [procfsBase] and do not return a nil
|
||||
// [ProcThreadSelfCloser] for [procfsBase] values other than [ProcThreadSelf]
|
||||
// where it is necessary.
|
||||
func (proc *Handle) open(base procfsBase, subpath string) (_ *os.File, closer ProcThreadSelfCloser, Err error) {
|
||||
prefix, err := base.prefix(proc)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
subpath = prefix + "/" + subpath
|
||||
|
||||
switch base {
|
||||
case ProcRoot:
|
||||
file, err := proc.lookup(subpath)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
// The Handle handle in use might be a subset=pid one, which will
|
||||
// result in spurious errors. In this case, just open a temporary
|
||||
// unmasked procfs handle for this operation.
|
||||
proc, err2 := OpenUnsafeProcRoot() // !subset=pid
|
||||
if err2 != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer proc.Close() //nolint:errcheck // close failures aren't critical here
|
||||
|
||||
file, err = proc.lookup(subpath)
|
||||
}
|
||||
return file, nil, err
|
||||
|
||||
case ProcSelf:
|
||||
file, err := proc.lookup(subpath)
|
||||
return file, nil, err
|
||||
|
||||
case ProcThreadSelf:
|
||||
// We need to lock our thread until the caller is done with the handle
|
||||
// because between getting the handle and using it we could get
|
||||
// interrupted by the Go runtime and hit the case where the underlying
|
||||
// thread is swapped out and the original thread is killed, resulting
|
||||
// in pull-your-hair-out-hard-to-debug issues in the caller.
|
||||
runtime.LockOSThread()
|
||||
defer func() {
|
||||
if Err != nil {
|
||||
runtime.UnlockOSThread()
|
||||
closer = nil
|
||||
}
|
||||
}()
|
||||
|
||||
file, err := proc.lookup(subpath)
|
||||
return file, runtime.UnlockOSThread, err
|
||||
}
|
||||
// should never be reached
|
||||
return nil, nil, fmt.Errorf("[internal error] invalid procfs base %q", base)
|
||||
}
|
||||
|
||||
// OpenThreadSelf returns a handle to "/proc/thread-self/<subpath>" (or an
|
||||
// equivalent handle on older kernels where "/proc/thread-self" doesn't exist).
|
||||
// Once finished with the handle, you must call the returned closer function
|
||||
// (runtime.UnlockOSThread). You must not pass the returned *os.File to other
|
||||
// Go threads or use the handle after calling the closer.
|
||||
func (proc *Handle) OpenThreadSelf(subpath string) (_ *os.File, _ ProcThreadSelfCloser, Err error) {
|
||||
return proc.open(ProcThreadSelf, subpath)
|
||||
}
|
||||
|
||||
// OpenSelf returns a handle to /proc/self/<subpath>.
|
||||
func (proc *Handle) OpenSelf(subpath string) (*os.File, error) {
|
||||
file, closer, err := proc.open(ProcSelf, subpath)
|
||||
assert.Assert(closer == nil, "closer for ProcSelf must be nil")
|
||||
return file, err
|
||||
}
|
||||
|
||||
// OpenRoot returns a handle to /proc/<subpath>.
|
||||
func (proc *Handle) OpenRoot(subpath string) (*os.File, error) {
|
||||
file, closer, err := proc.open(ProcRoot, subpath)
|
||||
assert.Assert(closer == nil, "closer for ProcRoot must be nil")
|
||||
return file, err
|
||||
}
|
||||
|
||||
// OpenPid returns a handle to /proc/$pid/<subpath> (pid can be a pid or tid).
|
||||
// This is mainly intended for usage when operating on other processes.
|
||||
func (proc *Handle) OpenPid(pid int, subpath string) (*os.File, error) {
|
||||
return proc.OpenRoot(strconv.Itoa(pid) + "/" + subpath)
|
||||
}
|
||||
|
||||
// checkSubpathOvermount checks if the dirfd and path combination is on the
|
||||
// same mount as the given root.
|
||||
func checkSubpathOvermount(root, dir fd.Fd, path string) error {
|
||||
// Get the mntID of our procfs handle.
|
||||
expectedMountID, err := fd.GetMountID(root, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("get root mount id: %w", err)
|
||||
}
|
||||
// Get the mntID of the target magic-link.
|
||||
gotMountID, err := fd.GetMountID(dir, path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get subpath mount id: %w", err)
|
||||
}
|
||||
// As long as the directory mount is alive, even with wrapping mount IDs,
|
||||
// we would expect to see a different mount ID here. (Of course, if we're
|
||||
// using unsafeHostProcRoot() then an attaker could change this after we
|
||||
// did this check.)
|
||||
if expectedMountID != gotMountID {
|
||||
return fmt.Errorf("%w: subpath %s/%s has an overmount obscuring the real path (mount ids do not match %d != %d)",
|
||||
errUnsafeProcfs, dir.Name(), path, expectedMountID, gotMountID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Readlink performs a readlink operation on "/proc/<base>/<subpath>" in a way
|
||||
// that should be free from race attacks. This is most commonly used to get the
|
||||
// real path of a file by looking at "/proc/self/fd/$n", with the same safety
|
||||
// protections as [Open] (as well as some additional checks against
|
||||
// overmounts).
|
||||
func (proc *Handle) Readlink(base procfsBase, subpath string) (string, error) {
|
||||
link, closer, err := proc.open(base, subpath)
|
||||
if closer != nil {
|
||||
defer closer()
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get safe %s/%s handle: %w", base, subpath, err)
|
||||
}
|
||||
defer link.Close() //nolint:errcheck // close failures aren't critical here
|
||||
|
||||
// Try to detect if there is a mount on top of the magic-link. This should
|
||||
// be safe in general (a mount on top of the path afterwards would not
|
||||
// affect the handle itself) and will definitely be safe if we are using
|
||||
// privateProcRoot() (at least since Linux 5.12[1], when anonymous mount
|
||||
// namespaces were completely isolated from external mounts including mount
|
||||
// propagation events).
|
||||
//
|
||||
// [1]: Linux commit ee2e3f50629f ("mount: fix mounting of detached mounts
|
||||
// onto targets that reside on shared mounts").
|
||||
if err := checkSubpathOvermount(proc.Inner, link, ""); err != nil {
|
||||
return "", fmt.Errorf("check safety of %s/%s magiclink: %w", base, subpath, err)
|
||||
}
|
||||
|
||||
// readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See Linux commit
|
||||
// 65cfc6722361 ("readlinkat(), fchownat() and fstatat() with empty
|
||||
// relative pathnames").
|
||||
return fd.Readlinkat(link, "")
|
||||
}
|
||||
|
||||
// ProcSelfFdReadlink gets the real path of the given file by looking at
|
||||
// readlink(/proc/thread-self/fd/$n).
|
||||
//
|
||||
// This is just a wrapper around [Handle.Readlink].
|
||||
func ProcSelfFdReadlink(fd fd.Fd) (string, error) {
|
||||
procRoot, err := OpenProcRoot() // subset=pid
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer procRoot.Close() //nolint:errcheck // close failures aren't critical here
|
||||
|
||||
fdPath := "fd/" + strconv.Itoa(int(fd.Fd()))
|
||||
return procRoot.Readlink(ProcThreadSelf, fdPath)
|
||||
}
|
||||
|
||||
// CheckProcSelfFdPath returns whether the given file handle matches the
|
||||
// expected path. (This is inherently racy.)
|
||||
func CheckProcSelfFdPath(path string, file fd.Fd) error {
|
||||
if err := fd.IsDeadInode(file); err != nil {
|
||||
return err
|
||||
}
|
||||
actualPath, err := ProcSelfFdReadlink(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get path of handle: %w", err)
|
||||
}
|
||||
if actualPath != path {
|
||||
return fmt.Errorf("%w: handle path %q doesn't match expected path %q", internal.ErrPossibleBreakout, actualPath, path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReopenFd takes an existing file descriptor and "re-opens" it through
|
||||
// /proc/thread-self/fd/<fd>. This allows for O_PATH file descriptors to be
|
||||
// upgraded to regular file descriptors, as well as changing the open mode of a
|
||||
// regular file descriptor. Some filesystems have unique handling of open(2)
|
||||
// which make this incredibly useful (such as /dev/ptmx).
|
||||
func ReopenFd(handle fd.Fd, flags int) (*os.File, error) {
|
||||
procRoot, err := OpenProcRoot() // subset=pid
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer procRoot.Close() //nolint:errcheck // close failures aren't critical here
|
||||
|
||||
// We can't operate on /proc/thread-self/fd/$n directly when doing a
|
||||
// re-open, so we need to open /proc/thread-self/fd and then open a single
|
||||
// final component.
|
||||
procFdDir, closer, err := procRoot.OpenThreadSelf("fd/")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get safe /proc/thread-self/fd handle: %w", err)
|
||||
}
|
||||
defer procFdDir.Close() //nolint:errcheck // close failures aren't critical here
|
||||
defer closer()
|
||||
|
||||
// Try to detect if there is a mount on top of the magic-link we are about
|
||||
// to open. If we are using unsafeHostProcRoot(), this could change after
|
||||
// we check it (and there's nothing we can do about that) but for
|
||||
// privateProcRoot() this should be guaranteed to be safe (at least since
|
||||
// Linux 5.12[1], when anonymous mount namespaces were completely isolated
|
||||
// from external mounts including mount propagation events).
|
||||
//
|
||||
// [1]: Linux commit ee2e3f50629f ("mount: fix mounting of detached mounts
|
||||
// onto targets that reside on shared mounts").
|
||||
fdStr := strconv.Itoa(int(handle.Fd()))
|
||||
if err := checkSubpathOvermount(procRoot.Inner, procFdDir, fdStr); err != nil {
|
||||
return nil, fmt.Errorf("check safety of /proc/thread-self/fd/%s magiclink: %w", fdStr, err)
|
||||
}
|
||||
|
||||
flags |= unix.O_CLOEXEC
|
||||
// Rather than just wrapping fd.Openat, open-code it so we can copy
|
||||
// handle.Name().
|
||||
reopenFd, err := unix.Openat(int(procFdDir.Fd()), fdStr, flags, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reopen fd %d: %w", handle.Fd(), err)
|
||||
}
|
||||
return os.NewFile(uintptr(reopenFd), handle.Name()), nil
|
||||
}
|
||||
|
||||
// Test hooks used in the procfs tests to verify that the fallback logic works.
|
||||
// See testing_mocks_linux_test.go and procfs_linux_test.go for more details.
|
||||
var (
|
||||
hookForcePrivateProcRootOpenTree = hookDummyFile
|
||||
hookForcePrivateProcRootOpenTreeAtRecursive = hookDummyFile
|
||||
hookForceGetProcRootUnsafe = hookDummy
|
||||
|
||||
hookForceProcSelfTask = hookDummy
|
||||
hookForceProcSelf = hookDummy
|
||||
)
|
||||
|
||||
func hookDummy() bool { return false }
|
||||
func hookDummyFile(_ io.Closer) bool { return false }
|
222
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_lookup_linux.go
generated
vendored
Normal file
222
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_lookup_linux.go
generated
vendored
Normal file
@ -0,0 +1,222 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
// This code is adapted to be a minimal version of the libpathrs proc resolver
|
||||
// <https://github.com/opensuse/libpathrs/blob/v0.1.3/src/resolvers/procfs.rs>.
|
||||
// As we only need O_PATH|O_NOFOLLOW support, this is not too much to port.
|
||||
|
||||
package procfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/internal/consts"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux"
|
||||
)
|
||||
|
||||
// procfsLookupInRoot is a stripped down version of completeLookupInRoot,
|
||||
// entirely designed to support the very small set of features necessary to
|
||||
// make procfs handling work. Unlike completeLookupInRoot, we always have
|
||||
// O_PATH|O_NOFOLLOW behaviour for trailing symlinks.
|
||||
//
|
||||
// The main restrictions are:
|
||||
//
|
||||
// - ".." is not supported (as it requires either os.Root-style replays,
|
||||
// which is more bug-prone; or procfs verification, which is not possible
|
||||
// due to re-entrancy issues).
|
||||
// - Absolute symlinks for the same reason (and all absolute symlinks in
|
||||
// procfs are magic-links, which we want to skip anyway).
|
||||
// - If statx is supported (checkSymlinkOvermount), any mount-point crossings
|
||||
// (which is the main attack of concern against /proc).
|
||||
// - Partial lookups are not supported, so the symlink stack is not needed.
|
||||
// - Trailing slash special handling is not necessary in most cases (if we
|
||||
// operating on procfs, it's usually with programmer-controlled strings
|
||||
// that will then be re-opened), so we skip it since whatever re-opens it
|
||||
// can deal with it. It's a creature comfort anyway.
|
||||
//
|
||||
// If the system supports openat2(), this is implemented using equivalent flags
|
||||
// (RESOLVE_BENEATH | RESOLVE_NO_XDEV | RESOLVE_NO_MAGICLINKS).
|
||||
func procfsLookupInRoot(procRoot fd.Fd, unsafePath string) (Handle *os.File, _ error) {
|
||||
unsafePath = filepath.ToSlash(unsafePath) // noop
|
||||
|
||||
// Make sure that an empty unsafe path still returns something sane, even
|
||||
// with openat2 (which doesn't have AT_EMPTY_PATH semantics yet).
|
||||
if unsafePath == "" {
|
||||
unsafePath = "."
|
||||
}
|
||||
|
||||
// This is already checked by getProcRoot, but make sure here since the
|
||||
// core security of this lookup is based on this assumption.
|
||||
if err := verifyProcRoot(procRoot); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if linux.HasOpenat2() {
|
||||
// We prefer being able to use RESOLVE_NO_XDEV if we can, to be
|
||||
// absolutely sure we are operating on a clean /proc handle that
|
||||
// doesn't have any cheeky overmounts that could trick us (including
|
||||
// symlink mounts on top of /proc/thread-self). RESOLVE_BENEATH isn't
|
||||
// strictly needed, but just use it since we have it.
|
||||
//
|
||||
// NOTE: /proc/self is technically a magic-link (the contents of the
|
||||
// symlink are generated dynamically), but it doesn't use
|
||||
// nd_jump_link() so RESOLVE_NO_MAGICLINKS allows it.
|
||||
//
|
||||
// TODO: It would be nice to have RESOLVE_NO_DOTDOT, purely for
|
||||
// self-consistency with the backup O_PATH resolver.
|
||||
handle, err := fd.Openat2(procRoot, unsafePath, &unix.OpenHow{
|
||||
Flags: unix.O_PATH | unix.O_NOFOLLOW | unix.O_CLOEXEC,
|
||||
Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_XDEV | unix.RESOLVE_NO_MAGICLINKS,
|
||||
})
|
||||
if err != nil {
|
||||
// TODO: Once we bump the minimum Go version to 1.20, we can use
|
||||
// multiple %w verbs for this wrapping. For now we need to use a
|
||||
// compatibility shim for older Go versions.
|
||||
// err = fmt.Errorf("%w: %w", errUnsafeProcfs, err)
|
||||
return nil, gocompat.WrapBaseError(err, errUnsafeProcfs)
|
||||
}
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
// To mirror openat2(RESOLVE_BENEATH), we need to return an error if the
|
||||
// path is absolute.
|
||||
if path.IsAbs(unsafePath) {
|
||||
return nil, fmt.Errorf("%w: cannot resolve absolute paths in procfs resolver", internal.ErrPossibleBreakout)
|
||||
}
|
||||
|
||||
currentDir, err := fd.Dup(procRoot)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("clone root fd: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
// If a handle is not returned, close the internal handle.
|
||||
if Handle == nil {
|
||||
_ = currentDir.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
var (
|
||||
linksWalked int
|
||||
currentPath string
|
||||
remainingPath = unsafePath
|
||||
)
|
||||
for remainingPath != "" {
|
||||
// Get the next path component.
|
||||
var part string
|
||||
if i := strings.IndexByte(remainingPath, '/'); i == -1 {
|
||||
part, remainingPath = remainingPath, ""
|
||||
} else {
|
||||
part, remainingPath = remainingPath[:i], remainingPath[i+1:]
|
||||
}
|
||||
if part == "" {
|
||||
// no-op component, but treat it the same as "."
|
||||
part = "."
|
||||
}
|
||||
if part == ".." {
|
||||
// not permitted
|
||||
return nil, fmt.Errorf("%w: cannot walk into '..' in procfs resolver", internal.ErrPossibleBreakout)
|
||||
}
|
||||
|
||||
// Apply the component lexically to the path we are building.
|
||||
// currentPath does not contain any symlinks, and we are lexically
|
||||
// dealing with a single component, so it's okay to do a filepath.Clean
|
||||
// here. (Not to mention that ".." isn't allowed.)
|
||||
nextPath := path.Join("/", currentPath, part)
|
||||
// If we logically hit the root, just clone the root rather than
|
||||
// opening the part and doing all of the other checks.
|
||||
if nextPath == "/" {
|
||||
// Jump to root.
|
||||
rootClone, err := fd.Dup(procRoot)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("clone root fd: %w", err)
|
||||
}
|
||||
_ = currentDir.Close()
|
||||
currentDir = rootClone
|
||||
currentPath = nextPath
|
||||
continue
|
||||
}
|
||||
|
||||
// Try to open the next component.
|
||||
nextDir, err := fd.Openat(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Make sure we are still on procfs and haven't crossed mounts.
|
||||
if err := verifyProcHandle(nextDir); err != nil {
|
||||
_ = nextDir.Close()
|
||||
return nil, fmt.Errorf("check %q component is on procfs: %w", part, err)
|
||||
}
|
||||
if err := checkSubpathOvermount(procRoot, nextDir, ""); err != nil {
|
||||
_ = nextDir.Close()
|
||||
return nil, fmt.Errorf("check %q component is not overmounted: %w", part, err)
|
||||
}
|
||||
|
||||
// We are emulating O_PATH|O_NOFOLLOW, so we only need to traverse into
|
||||
// trailing symlinks if we are not the final component. Otherwise we
|
||||
// can just return the currentDir.
|
||||
if remainingPath != "" {
|
||||
st, err := nextDir.Stat()
|
||||
if err != nil {
|
||||
_ = nextDir.Close()
|
||||
return nil, fmt.Errorf("stat component %q: %w", part, err)
|
||||
}
|
||||
|
||||
if st.Mode()&os.ModeType == os.ModeSymlink {
|
||||
// readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See
|
||||
// Linux commit 65cfc6722361 ("readlinkat(), fchownat() and
|
||||
// fstatat() with empty relative pathnames").
|
||||
linkDest, err := fd.Readlinkat(nextDir, "")
|
||||
// We don't need the handle anymore.
|
||||
_ = nextDir.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
linksWalked++
|
||||
if linksWalked > consts.MaxSymlinkLimit {
|
||||
return nil, &os.PathError{Op: "securejoin.procfsLookupInRoot", Path: "/proc/" + unsafePath, Err: unix.ELOOP}
|
||||
}
|
||||
|
||||
// Update our logical remaining path.
|
||||
remainingPath = linkDest + "/" + remainingPath
|
||||
// Absolute symlinks are probably magiclinks, we reject them.
|
||||
if path.IsAbs(linkDest) {
|
||||
return nil, fmt.Errorf("%w: cannot jump to / in procfs resolver -- possible magiclink", internal.ErrPossibleBreakout)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Walk into the next component.
|
||||
_ = currentDir.Close()
|
||||
currentDir = nextDir
|
||||
currentPath = nextPath
|
||||
}
|
||||
|
||||
// One final sanity-check.
|
||||
if err := verifyProcHandle(currentDir); err != nil {
|
||||
return nil, fmt.Errorf("check final handle is on procfs: %w", err)
|
||||
}
|
||||
if err := checkSubpathOvermount(procRoot, currentDir, ""); err != nil {
|
||||
return nil, fmt.Errorf("check final handle is not overmounted: %w", err)
|
||||
}
|
||||
return currentDir, nil
|
||||
}
|
@ -1,10 +1,15 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package securejoin
|
||||
package pathrs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@ -15,6 +20,12 @@ import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/internal/consts"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs"
|
||||
)
|
||||
|
||||
type symlinkStackEntry struct {
|
||||
@ -112,12 +123,12 @@ func (s *symlinkStack) push(dir *os.File, remainingPath, linkTarget string) erro
|
||||
return nil
|
||||
}
|
||||
// Split the link target and clean up any "" parts.
|
||||
linkTargetParts := slices_DeleteFunc(
|
||||
linkTargetParts := gocompat.SlicesDeleteFunc(
|
||||
strings.Split(linkTarget, "/"),
|
||||
func(part string) bool { return part == "" || part == "." })
|
||||
|
||||
// Copy the directory so the caller doesn't close our copy.
|
||||
dirCopy, err := dupFile(dir)
|
||||
dirCopy, err := fd.Dup(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -159,11 +170,11 @@ func (s *symlinkStack) PopTopSymlink() (*os.File, string, bool) {
|
||||
// within the provided root (a-la RESOLVE_IN_ROOT) and opens the final existing
|
||||
// component of the requested path, returning a file handle to the final
|
||||
// existing component and a string containing the remaining path components.
|
||||
func partialLookupInRoot(root *os.File, unsafePath string) (*os.File, string, error) {
|
||||
func partialLookupInRoot(root fd.Fd, unsafePath string) (*os.File, string, error) {
|
||||
return lookupInRoot(root, unsafePath, true)
|
||||
}
|
||||
|
||||
func completeLookupInRoot(root *os.File, unsafePath string) (*os.File, error) {
|
||||
func completeLookupInRoot(root fd.Fd, unsafePath string) (*os.File, error) {
|
||||
handle, remainingPath, err := lookupInRoot(root, unsafePath, false)
|
||||
if remainingPath != "" && err == nil {
|
||||
// should never happen
|
||||
@ -174,7 +185,7 @@ func completeLookupInRoot(root *os.File, unsafePath string) (*os.File, error) {
|
||||
return handle, err
|
||||
}
|
||||
|
||||
func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.File, _ string, _ error) {
|
||||
func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File, _ string, _ error) {
|
||||
unsafePath = filepath.ToSlash(unsafePath) // noop
|
||||
|
||||
// This is very similar to SecureJoin, except that we operate on the
|
||||
@ -182,20 +193,20 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi
|
||||
// managed open, along with the remaining path components not opened.
|
||||
|
||||
// Try to use openat2 if possible.
|
||||
if hasOpenat2() {
|
||||
if linux.HasOpenat2() {
|
||||
return lookupOpenat2(root, unsafePath, partial)
|
||||
}
|
||||
|
||||
// Get the "actual" root path from /proc/self/fd. This is necessary if the
|
||||
// root is some magic-link like /proc/$pid/root, in which case we want to
|
||||
// make sure when we do checkProcSelfFdPath that we are using the correct
|
||||
// root path.
|
||||
logicalRootPath, err := procSelfFdReadlink(root)
|
||||
// make sure when we do procfs.CheckProcSelfFdPath that we are using the
|
||||
// correct root path.
|
||||
logicalRootPath, err := procfs.ProcSelfFdReadlink(root)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("get real root path: %w", err)
|
||||
}
|
||||
|
||||
currentDir, err := dupFile(root)
|
||||
currentDir, err := fd.Dup(root)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("clone root fd: %w", err)
|
||||
}
|
||||
@ -260,7 +271,7 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi
|
||||
return nil, "", fmt.Errorf("walking into root with part %q failed: %w", part, err)
|
||||
}
|
||||
// Jump to root.
|
||||
rootClone, err := dupFile(root)
|
||||
rootClone, err := fd.Dup(root)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("clone root fd: %w", err)
|
||||
}
|
||||
@ -271,21 +282,21 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi
|
||||
}
|
||||
|
||||
// Try to open the next component.
|
||||
nextDir, err := openatFile(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
||||
switch {
|
||||
case err == nil:
|
||||
nextDir, err := fd.Openat(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
||||
switch err {
|
||||
case nil:
|
||||
st, err := nextDir.Stat()
|
||||
if err != nil {
|
||||
_ = nextDir.Close()
|
||||
return nil, "", fmt.Errorf("stat component %q: %w", part, err)
|
||||
}
|
||||
|
||||
switch st.Mode() & os.ModeType {
|
||||
switch st.Mode() & os.ModeType { //nolint:exhaustive // just a glorified if statement
|
||||
case os.ModeSymlink:
|
||||
// readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See
|
||||
// Linux commit 65cfc6722361 ("readlinkat(), fchownat() and
|
||||
// fstatat() with empty relative pathnames").
|
||||
linkDest, err := readlinkatFile(nextDir, "")
|
||||
linkDest, err := fd.Readlinkat(nextDir, "")
|
||||
// We don't need the handle anymore.
|
||||
_ = nextDir.Close()
|
||||
if err != nil {
|
||||
@ -293,7 +304,7 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi
|
||||
}
|
||||
|
||||
linksWalked++
|
||||
if linksWalked > maxSymlinkLimit {
|
||||
if linksWalked > consts.MaxSymlinkLimit {
|
||||
return nil, "", &os.PathError{Op: "securejoin.lookupInRoot", Path: logicalRootPath + "/" + unsafePath, Err: unix.ELOOP}
|
||||
}
|
||||
|
||||
@ -307,7 +318,7 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi
|
||||
// Absolute symlinks reset any work we've already done.
|
||||
if path.IsAbs(linkDest) {
|
||||
// Jump to root.
|
||||
rootClone, err := dupFile(root)
|
||||
rootClone, err := fd.Dup(root)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("clone root fd: %w", err)
|
||||
}
|
||||
@ -335,12 +346,12 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi
|
||||
// rename or mount on the system.
|
||||
if part == ".." {
|
||||
// Make sure the root hasn't moved.
|
||||
if err := checkProcSelfFdPath(logicalRootPath, root); err != nil {
|
||||
if err := procfs.CheckProcSelfFdPath(logicalRootPath, root); err != nil {
|
||||
return nil, "", fmt.Errorf("root path moved during lookup: %w", err)
|
||||
}
|
||||
// Make sure the path is what we expect.
|
||||
fullPath := logicalRootPath + nextPath
|
||||
if err := checkProcSelfFdPath(fullPath, currentDir); err != nil {
|
||||
if err := procfs.CheckProcSelfFdPath(fullPath, currentDir); err != nil {
|
||||
return nil, "", fmt.Errorf("walking into %q had unexpected result: %w", part, err)
|
||||
}
|
||||
}
|
||||
@ -371,7 +382,7 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi
|
||||
// context of openat2, a trailing slash and a trailing "/." are completely
|
||||
// equivalent.
|
||||
if strings.HasSuffix(unsafePath, "/") {
|
||||
nextDir, err := openatFile(currentDir, ".", unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
||||
nextDir, err := fd.Openat(currentDir, ".", unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
if !partial {
|
||||
_ = currentDir.Close()
|
@ -1,10 +1,15 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package securejoin
|
||||
package pathrs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@ -14,12 +19,13 @@ import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidMode = errors.New("invalid permission mode")
|
||||
errPossibleAttack = errors.New("possible attack detected")
|
||||
)
|
||||
var errInvalidMode = errors.New("invalid permission mode")
|
||||
|
||||
// modePermExt is like os.ModePerm except that it also includes the set[ug]id
|
||||
// and sticky bits.
|
||||
@ -66,6 +72,8 @@ func toUnixMode(mode os.FileMode) (uint32, error) {
|
||||
// a brand new lookup of unsafePath (such as with [SecureJoin] or openat2) after
|
||||
// doing [MkdirAll]. If you intend to open the directory after creating it, you
|
||||
// should use MkdirAllHandle.
|
||||
//
|
||||
// [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin
|
||||
func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.File, Err error) {
|
||||
unixMode, err := toUnixMode(mode)
|
||||
if err != nil {
|
||||
@ -102,7 +110,7 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F
|
||||
//
|
||||
// This is mostly a quality-of-life check, because mkdir will simply fail
|
||||
// later if the attacker deletes the tree after this check.
|
||||
if err := isDeadInode(currentDir); err != nil {
|
||||
if err := fd.IsDeadInode(currentDir); err != nil {
|
||||
return nil, fmt.Errorf("finding existing subpath of %q: %w", unsafePath, err)
|
||||
}
|
||||
|
||||
@ -113,13 +121,13 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F
|
||||
return nil, fmt.Errorf("cannot create subdirectories in %q: %w", currentDir.Name(), unix.ENOTDIR)
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("re-opening handle to %q: %w", currentDir.Name(), err)
|
||||
} else {
|
||||
} else { //nolint:revive // indent-error-flow lint doesn't make sense here
|
||||
_ = currentDir.Close()
|
||||
currentDir = reopenDir
|
||||
}
|
||||
|
||||
remainingParts := strings.Split(remainingPath, string(filepath.Separator))
|
||||
if slices_Contains(remainingParts, "..") {
|
||||
if gocompat.SlicesContains(remainingParts, "..") {
|
||||
// The path contained ".." components after the end of the "real"
|
||||
// components. We could try to safely resolve ".." here but that would
|
||||
// add a bunch of extra logic for something that it's not clear even
|
||||
@ -150,12 +158,12 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F
|
||||
if err := unix.Mkdirat(int(currentDir.Fd()), part, unixMode); err != nil && !errors.Is(err, unix.EEXIST) {
|
||||
err = &os.PathError{Op: "mkdirat", Path: currentDir.Name() + "/" + part, Err: err}
|
||||
// Make the error a bit nicer if the directory is dead.
|
||||
if deadErr := isDeadInode(currentDir); deadErr != nil {
|
||||
if deadErr := fd.IsDeadInode(currentDir); deadErr != nil {
|
||||
// TODO: Once we bump the minimum Go version to 1.20, we can use
|
||||
// multiple %w verbs for this wrapping. For now we need to use a
|
||||
// compatibility shim for older Go versions.
|
||||
// err = fmt.Errorf("%w (%w)", err, deadErr)
|
||||
err = wrapBaseError(err, deadErr)
|
||||
err = gocompat.WrapBaseError(err, deadErr)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@ -163,13 +171,13 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F
|
||||
// Get a handle to the next component. O_DIRECTORY means we don't need
|
||||
// to use O_PATH.
|
||||
var nextDir *os.File
|
||||
if hasOpenat2() {
|
||||
nextDir, err = openat2File(currentDir, part, &unix.OpenHow{
|
||||
if linux.HasOpenat2() {
|
||||
nextDir, err = openat2(currentDir, part, &unix.OpenHow{
|
||||
Flags: unix.O_NOFOLLOW | unix.O_DIRECTORY | unix.O_CLOEXEC,
|
||||
Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_XDEV,
|
||||
})
|
||||
} else {
|
||||
nextDir, err = openatFile(currentDir, part, unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
||||
nextDir, err = fd.Openat(currentDir, part, unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -220,12 +228,14 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F
|
||||
// If you plan to open the directory after you have created it or want to use
|
||||
// an open directory handle as the root, you should use [MkdirAllHandle] instead.
|
||||
// This function is a wrapper around [MkdirAllHandle].
|
||||
//
|
||||
// [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin
|
||||
func MkdirAll(root, unsafePath string, mode os.FileMode) error {
|
||||
rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rootDir.Close()
|
||||
defer rootDir.Close() //nolint:errcheck // close failures aren't critical here
|
||||
|
||||
f, err := MkdirAllHandle(rootDir, unsafePath, mode)
|
||||
if err != nil {
|
@ -1,17 +1,22 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package securejoin
|
||||
package pathrs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs"
|
||||
)
|
||||
|
||||
// OpenatInRoot is equivalent to [OpenInRoot], except that the root is provided
|
||||
@ -40,12 +45,14 @@ func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) {
|
||||
// disconnected TTY that could cause a DoS, or some other issue). In order to
|
||||
// use the returned handle, you can "upgrade" it to a proper handle using
|
||||
// [Reopen].
|
||||
//
|
||||
// [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin
|
||||
func OpenInRoot(root, unsafePath string) (*os.File, error) {
|
||||
rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rootDir.Close()
|
||||
defer rootDir.Close() //nolint:errcheck // close failures aren't critical here
|
||||
return OpenatInRoot(rootDir, unsafePath)
|
||||
}
|
||||
|
||||
@ -63,41 +70,5 @@ func OpenInRoot(root, unsafePath string) (*os.File, error) {
|
||||
//
|
||||
// [CVE-2019-19921]: https://github.com/advisories/GHSA-fh74-hm69-rqjw
|
||||
func Reopen(handle *os.File, flags int) (*os.File, error) {
|
||||
procRoot, err := getProcRoot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We can't operate on /proc/thread-self/fd/$n directly when doing a
|
||||
// re-open, so we need to open /proc/thread-self/fd and then open a single
|
||||
// final component.
|
||||
procFdDir, closer, err := procThreadSelf(procRoot, "fd/")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get safe /proc/thread-self/fd handle: %w", err)
|
||||
}
|
||||
defer procFdDir.Close()
|
||||
defer closer()
|
||||
|
||||
// Try to detect if there is a mount on top of the magic-link we are about
|
||||
// to open. If we are using unsafeHostProcRoot(), this could change after
|
||||
// we check it (and there's nothing we can do about that) but for
|
||||
// privateProcRoot() this should be guaranteed to be safe (at least since
|
||||
// Linux 5.12[1], when anonymous mount namespaces were completely isolated
|
||||
// from external mounts including mount propagation events).
|
||||
//
|
||||
// [1]: Linux commit ee2e3f50629f ("mount: fix mounting of detached mounts
|
||||
// onto targets that reside on shared mounts").
|
||||
fdStr := strconv.Itoa(int(handle.Fd()))
|
||||
if err := checkSymlinkOvermount(procRoot, procFdDir, fdStr); err != nil {
|
||||
return nil, fmt.Errorf("check safety of /proc/thread-self/fd/%s magiclink: %w", fdStr, err)
|
||||
}
|
||||
|
||||
flags |= unix.O_CLOEXEC
|
||||
// Rather than just wrapping openatFile, open-code it so we can copy
|
||||
// handle.Name().
|
||||
reopenFd, err := unix.Openat(int(procFdDir.Fd()), fdStr, flags, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reopen fd %d: %w", handle.Fd(), err)
|
||||
}
|
||||
return os.NewFile(uintptr(reopenFd), handle.Name()), nil
|
||||
return procfs.ReopenFd(handle, flags)
|
||||
}
|
101
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/openat2_linux.go
generated
vendored
Normal file
101
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/openat2_linux.go
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package pathrs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/procfs"
|
||||
)
|
||||
|
||||
func openat2(dir fd.Fd, path string, how *unix.OpenHow) (*os.File, error) {
|
||||
file, err := fd.Openat2(dir, path, how)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If we are using RESOLVE_IN_ROOT, the name we generated may be wrong.
|
||||
if how.Resolve&unix.RESOLVE_IN_ROOT == unix.RESOLVE_IN_ROOT {
|
||||
if actualPath, err := procfs.ProcSelfFdReadlink(file); err == nil {
|
||||
// TODO: Ideally we would not need to dup the fd, but you cannot
|
||||
// easily just swap an *os.File with one from the same fd
|
||||
// (the GC will close the old one, and you cannot clear the
|
||||
// finaliser easily because it is associated with an internal
|
||||
// field of *os.File not *os.File itself).
|
||||
newFile, err := fd.DupWithName(file, actualPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file = newFile
|
||||
}
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func lookupOpenat2(root fd.Fd, unsafePath string, partial bool) (*os.File, string, error) {
|
||||
if !partial {
|
||||
file, err := openat2(root, unsafePath, &unix.OpenHow{
|
||||
Flags: unix.O_PATH | unix.O_CLOEXEC,
|
||||
Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS,
|
||||
})
|
||||
return file, "", err
|
||||
}
|
||||
return partialLookupOpenat2(root, unsafePath)
|
||||
}
|
||||
|
||||
// partialLookupOpenat2 is an alternative implementation of
|
||||
// partialLookupInRoot, using openat2(RESOLVE_IN_ROOT) to more safely get a
|
||||
// handle to the deepest existing child of the requested path within the root.
|
||||
func partialLookupOpenat2(root fd.Fd, unsafePath string) (*os.File, string, error) {
|
||||
// TODO: Implement this as a git-bisect-like binary search.
|
||||
|
||||
unsafePath = filepath.ToSlash(unsafePath) // noop
|
||||
endIdx := len(unsafePath)
|
||||
var lastError error
|
||||
for endIdx > 0 {
|
||||
subpath := unsafePath[:endIdx]
|
||||
|
||||
handle, err := openat2(root, subpath, &unix.OpenHow{
|
||||
Flags: unix.O_PATH | unix.O_CLOEXEC,
|
||||
Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS,
|
||||
})
|
||||
if err == nil {
|
||||
// Jump over the slash if we have a non-"" remainingPath.
|
||||
if endIdx < len(unsafePath) {
|
||||
endIdx++
|
||||
}
|
||||
// We found a subpath!
|
||||
return handle, unsafePath[endIdx:], lastError
|
||||
}
|
||||
if errors.Is(err, unix.ENOENT) || errors.Is(err, unix.ENOTDIR) {
|
||||
// That path doesn't exist, let's try the next directory up.
|
||||
endIdx = strings.LastIndexByte(subpath, '/')
|
||||
lastError = err
|
||||
continue
|
||||
}
|
||||
return nil, "", fmt.Errorf("open subpath: %w", err)
|
||||
}
|
||||
// If we couldn't open anything, the whole subpath is missing. Return a
|
||||
// copy of the root fd so that the caller doesn't close this one by
|
||||
// accident.
|
||||
rootClone, err := fd.Dup(root)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return rootClone, unsafePath, lastError
|
||||
}
|
157
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_linux.go
generated
vendored
Normal file
157
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_linux.go
generated
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package procfs provides a safe API for operating on /proc on Linux.
|
||||
package procfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs"
|
||||
)
|
||||
|
||||
// This package mostly just wraps internal/procfs APIs. This is necessary
|
||||
// because we are forced to export some things from internal/procfs in order to
|
||||
// avoid some dependency cycle issues, but we don't want users to see or use
|
||||
// them.
|
||||
|
||||
// ProcThreadSelfCloser is a callback that needs to be called when you are done
|
||||
// operating on an [os.File] fetched using [Handle.OpenThreadSelf].
|
||||
//
|
||||
// [os.File]: https://pkg.go.dev/os#File
|
||||
type ProcThreadSelfCloser = procfs.ProcThreadSelfCloser
|
||||
|
||||
// Handle is a wrapper around an *os.File handle to "/proc", which can be used
|
||||
// to do further procfs-related operations in a safe way.
|
||||
type Handle struct {
|
||||
inner *procfs.Handle
|
||||
}
|
||||
|
||||
// Close close the resources associated with this [Handle]. Note that if this
|
||||
// [Handle] was created with [OpenProcRoot], on some kernels the underlying
|
||||
// procfs handle is cached and so this Close operation may be a no-op. However,
|
||||
// you should always call Close on [Handle]s once you are done with them.
|
||||
func (proc *Handle) Close() error { return proc.inner.Close() }
|
||||
|
||||
// OpenProcRoot tries to open a "safer" handle to "/proc" (i.e., one with the
|
||||
// "subset=pid" mount option applied, available from Linux 5.8). Unless you
|
||||
// plan to do many [Handle.OpenRoot] operations, users should prefer to use
|
||||
// this over [OpenUnsafeProcRoot] which is far more dangerous to keep open.
|
||||
//
|
||||
// If a safe handle cannot be opened, OpenProcRoot will fall back to opening a
|
||||
// regular "/proc" handle.
|
||||
//
|
||||
// Note that using [Handle.OpenRoot] will still work with handles returned by
|
||||
// this function. If a subpath cannot be operated on with a safe "/proc"
|
||||
// handle, then [OpenUnsafeProcRoot] will be called internally and a temporary
|
||||
// unsafe handle will be used.
|
||||
func OpenProcRoot() (*Handle, error) {
|
||||
proc, err := procfs.OpenProcRoot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Handle{inner: proc}, nil
|
||||
}
|
||||
|
||||
// OpenUnsafeProcRoot opens a handle to "/proc" without any overmounts or
|
||||
// masked paths. You must be extremely careful to make sure this handle is
|
||||
// never leaked to a container and that you program cannot be tricked into
|
||||
// writing to arbitrary paths within it.
|
||||
//
|
||||
// This is not necessary if you just wish to use [Handle.OpenRoot], as handles
|
||||
// returned by [OpenProcRoot] will fall back to using a *temporary* unsafe
|
||||
// handle in that case. You should only really use this if you need to do many
|
||||
// operations with [Handle.OpenRoot] and the performance overhead of making
|
||||
// many procfs handles is an issue. If you do use OpenUnsafeProcRoot, you
|
||||
// should make sure to close the handle as soon as possible to avoid
|
||||
// known-fd-number attacks.
|
||||
func OpenUnsafeProcRoot() (*Handle, error) {
|
||||
proc, err := procfs.OpenUnsafeProcRoot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Handle{inner: proc}, nil
|
||||
}
|
||||
|
||||
// OpenThreadSelf returns a handle to "/proc/thread-self/<subpath>" (or an
|
||||
// equivalent handle on older kernels where "/proc/thread-self" doesn't exist).
|
||||
// Once finished with the handle, you must call the returned closer function
|
||||
// ([runtime.UnlockOSThread]). You must not pass the returned *os.File to other
|
||||
// Go threads or use the handle after calling the closer.
|
||||
//
|
||||
// [runtime.UnlockOSThread]: https://pkg.go.dev/runtime#UnlockOSThread
|
||||
func (proc *Handle) OpenThreadSelf(subpath string) (*os.File, ProcThreadSelfCloser, error) {
|
||||
return proc.inner.OpenThreadSelf(subpath)
|
||||
}
|
||||
|
||||
// OpenSelf returns a handle to /proc/self/<subpath>.
|
||||
//
|
||||
// Note that in Go programs with non-homogenous threads, this may result in
|
||||
// spurious errors. If you are monkeying around with APIs that are
|
||||
// thread-specific, you probably want to use [Handle.OpenThreadSelf] instead
|
||||
// which will guarantee that the handle refers to the same thread as the caller
|
||||
// is executing on.
|
||||
func (proc *Handle) OpenSelf(subpath string) (*os.File, error) {
|
||||
return proc.inner.OpenSelf(subpath)
|
||||
}
|
||||
|
||||
// OpenRoot returns a handle to /proc/<subpath>.
|
||||
//
|
||||
// You should only use this when you need to operate on global procfs files
|
||||
// (such as sysctls in /proc/sys). Unlike [Handle.OpenThreadSelf],
|
||||
// [Handle.OpenSelf], and [Handle.OpenPid], the procfs handle used internally
|
||||
// for this operation will never use "subset=pid", which makes it a more juicy
|
||||
// target for [CVE-2024-21626]-style attacks (and doing something like opening
|
||||
// a directory with OpenRoot effectively leaks [OpenUnsafeProcRoot] as long as
|
||||
// the file descriptor is open).
|
||||
//
|
||||
// [CVE-2024-21626]: https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv
|
||||
func (proc *Handle) OpenRoot(subpath string) (*os.File, error) {
|
||||
return proc.inner.OpenRoot(subpath)
|
||||
}
|
||||
|
||||
// OpenPid returns a handle to /proc/$pid/<subpath> (pid can be a pid or tid).
|
||||
// This is mainly intended for usage when operating on other processes.
|
||||
//
|
||||
// You should not use this for the current thread, as special handling is
|
||||
// needed for /proc/thread-self (or /proc/self/task/<tid>) when dealing with
|
||||
// goroutine scheduling -- use [Handle.OpenThreadSelf] instead.
|
||||
//
|
||||
// To refer to the current thread-group, you should use prefer
|
||||
// [Handle.OpenSelf] to passing os.Getpid as the pid argument.
|
||||
func (proc *Handle) OpenPid(pid int, subpath string) (*os.File, error) {
|
||||
return proc.inner.OpenPid(pid, subpath)
|
||||
}
|
||||
|
||||
// ProcSelfFdReadlink gets the real path of the given file by looking at
|
||||
// /proc/self/fd/<fd> with [readlink]. It is effectively just shorthand for
|
||||
// something along the lines of:
|
||||
//
|
||||
// proc, err := procfs.OpenProcRoot()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// link, err := proc.OpenThreadSelf(fmt.Sprintf("fd/%d", f.Fd()))
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// defer link.Close()
|
||||
// var buf [4096]byte
|
||||
// n, err := unix.Readlinkat(int(link.Fd()), "", buf[:])
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// pathname := buf[:n]
|
||||
//
|
||||
// [readlink]: https://pkg.go.dev/golang.org/x/sys/unix#Readlinkat
|
||||
func ProcSelfFdReadlink(f *os.File) (string, error) {
|
||||
return procfs.ProcSelfFdReadlink(f)
|
||||
}
|
452
vendor/github.com/cyphar/filepath-securejoin/procfs_linux.go
generated
vendored
452
vendor/github.com/cyphar/filepath-securejoin/procfs_linux.go
generated
vendored
@ -1,452 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package securejoin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func fstat(f *os.File) (unix.Stat_t, error) {
|
||||
var stat unix.Stat_t
|
||||
if err := unix.Fstat(int(f.Fd()), &stat); err != nil {
|
||||
return stat, &os.PathError{Op: "fstat", Path: f.Name(), Err: err}
|
||||
}
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
func fstatfs(f *os.File) (unix.Statfs_t, error) {
|
||||
var statfs unix.Statfs_t
|
||||
if err := unix.Fstatfs(int(f.Fd()), &statfs); err != nil {
|
||||
return statfs, &os.PathError{Op: "fstatfs", Path: f.Name(), Err: err}
|
||||
}
|
||||
return statfs, nil
|
||||
}
|
||||
|
||||
// The kernel guarantees that the root inode of a procfs mount has an
|
||||
// f_type of PROC_SUPER_MAGIC and st_ino of PROC_ROOT_INO.
|
||||
const (
|
||||
procSuperMagic = 0x9fa0 // PROC_SUPER_MAGIC
|
||||
procRootIno = 1 // PROC_ROOT_INO
|
||||
)
|
||||
|
||||
func verifyProcRoot(procRoot *os.File) error {
|
||||
if statfs, err := fstatfs(procRoot); err != nil {
|
||||
return err
|
||||
} else if statfs.Type != procSuperMagic {
|
||||
return fmt.Errorf("%w: incorrect procfs root filesystem type 0x%x", errUnsafeProcfs, statfs.Type)
|
||||
}
|
||||
if stat, err := fstat(procRoot); err != nil {
|
||||
return err
|
||||
} else if stat.Ino != procRootIno {
|
||||
return fmt.Errorf("%w: incorrect procfs root inode number %d", errUnsafeProcfs, stat.Ino)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var hasNewMountApi = sync_OnceValue(func() bool {
|
||||
// All of the pieces of the new mount API we use (fsopen, fsconfig,
|
||||
// fsmount, open_tree) were added together in Linux 5.1[1,2], so we can
|
||||
// just check for one of the syscalls and the others should also be
|
||||
// available.
|
||||
//
|
||||
// Just try to use open_tree(2) to open a file without OPEN_TREE_CLONE.
|
||||
// This is equivalent to openat(2), but tells us if open_tree is
|
||||
// available (and thus all of the other basic new mount API syscalls).
|
||||
// open_tree(2) is most light-weight syscall to test here.
|
||||
//
|
||||
// [1]: merge commit 400913252d09
|
||||
// [2]: <https://lore.kernel.org/lkml/153754740781.17872.7869536526927736855.stgit@warthog.procyon.org.uk/>
|
||||
fd, err := unix.OpenTree(-int(unix.EBADF), "/", unix.OPEN_TREE_CLOEXEC)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_ = unix.Close(fd)
|
||||
return true
|
||||
})
|
||||
|
||||
func fsopen(fsName string, flags int) (*os.File, error) {
|
||||
// Make sure we always set O_CLOEXEC.
|
||||
flags |= unix.FSOPEN_CLOEXEC
|
||||
fd, err := unix.Fsopen(fsName, flags)
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("fsopen "+fsName, err)
|
||||
}
|
||||
return os.NewFile(uintptr(fd), "fscontext:"+fsName), nil
|
||||
}
|
||||
|
||||
func fsmount(ctx *os.File, flags, mountAttrs int) (*os.File, error) {
|
||||
// Make sure we always set O_CLOEXEC.
|
||||
flags |= unix.FSMOUNT_CLOEXEC
|
||||
fd, err := unix.Fsmount(int(ctx.Fd()), flags, mountAttrs)
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("fsmount "+ctx.Name(), err)
|
||||
}
|
||||
return os.NewFile(uintptr(fd), "fsmount:"+ctx.Name()), nil
|
||||
}
|
||||
|
||||
func newPrivateProcMount() (*os.File, error) {
|
||||
procfsCtx, err := fsopen("proc", unix.FSOPEN_CLOEXEC)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer procfsCtx.Close()
|
||||
|
||||
// Try to configure hidepid=ptraceable,subset=pid if possible, but ignore errors.
|
||||
_ = unix.FsconfigSetString(int(procfsCtx.Fd()), "hidepid", "ptraceable")
|
||||
_ = unix.FsconfigSetString(int(procfsCtx.Fd()), "subset", "pid")
|
||||
|
||||
// Get an actual handle.
|
||||
if err := unix.FsconfigCreate(int(procfsCtx.Fd())); err != nil {
|
||||
return nil, os.NewSyscallError("fsconfig create procfs", err)
|
||||
}
|
||||
return fsmount(procfsCtx, unix.FSMOUNT_CLOEXEC, unix.MS_RDONLY|unix.MS_NODEV|unix.MS_NOEXEC|unix.MS_NOSUID)
|
||||
}
|
||||
|
||||
func openTree(dir *os.File, path string, flags uint) (*os.File, error) {
|
||||
dirFd := -int(unix.EBADF)
|
||||
dirName := "."
|
||||
if dir != nil {
|
||||
dirFd = int(dir.Fd())
|
||||
dirName = dir.Name()
|
||||
}
|
||||
// Make sure we always set O_CLOEXEC.
|
||||
flags |= unix.OPEN_TREE_CLOEXEC
|
||||
fd, err := unix.OpenTree(dirFd, path, flags)
|
||||
if err != nil {
|
||||
return nil, &os.PathError{Op: "open_tree", Path: path, Err: err}
|
||||
}
|
||||
return os.NewFile(uintptr(fd), dirName+"/"+path), nil
|
||||
}
|
||||
|
||||
func clonePrivateProcMount() (_ *os.File, Err error) {
|
||||
// Try to make a clone without using AT_RECURSIVE if we can. If this works,
|
||||
// we can be sure there are no over-mounts and so if the root is valid then
|
||||
// we're golden. Otherwise, we have to deal with over-mounts.
|
||||
procfsHandle, err := openTree(nil, "/proc", unix.OPEN_TREE_CLONE)
|
||||
if err != nil || hookForcePrivateProcRootOpenTreeAtRecursive(procfsHandle) {
|
||||
procfsHandle, err = openTree(nil, "/proc", unix.OPEN_TREE_CLONE|unix.AT_RECURSIVE)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating a detached procfs clone: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if Err != nil {
|
||||
_ = procfsHandle.Close()
|
||||
}
|
||||
}()
|
||||
if err := verifyProcRoot(procfsHandle); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return procfsHandle, nil
|
||||
}
|
||||
|
||||
func privateProcRoot() (*os.File, error) {
|
||||
if !hasNewMountApi() || hookForceGetProcRootUnsafe() {
|
||||
return nil, fmt.Errorf("new mount api: %w", unix.ENOTSUP)
|
||||
}
|
||||
// Try to create a new procfs mount from scratch if we can. This ensures we
|
||||
// can get a procfs mount even if /proc is fake (for whatever reason).
|
||||
procRoot, err := newPrivateProcMount()
|
||||
if err != nil || hookForcePrivateProcRootOpenTree(procRoot) {
|
||||
// Try to clone /proc then...
|
||||
procRoot, err = clonePrivateProcMount()
|
||||
}
|
||||
return procRoot, err
|
||||
}
|
||||
|
||||
func unsafeHostProcRoot() (_ *os.File, Err error) {
|
||||
procRoot, err := os.OpenFile("/proc", unix.O_PATH|unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if Err != nil {
|
||||
_ = procRoot.Close()
|
||||
}
|
||||
}()
|
||||
if err := verifyProcRoot(procRoot); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return procRoot, nil
|
||||
}
|
||||
|
||||
func doGetProcRoot() (*os.File, error) {
|
||||
procRoot, err := privateProcRoot()
|
||||
if err != nil {
|
||||
// Fall back to using a /proc handle if making a private mount failed.
|
||||
// If we have openat2, at least we can avoid some kinds of over-mount
|
||||
// attacks, but without openat2 there's not much we can do.
|
||||
procRoot, err = unsafeHostProcRoot()
|
||||
}
|
||||
return procRoot, err
|
||||
}
|
||||
|
||||
var getProcRoot = sync_OnceValues(func() (*os.File, error) {
|
||||
return doGetProcRoot()
|
||||
})
|
||||
|
||||
var hasProcThreadSelf = sync_OnceValue(func() bool {
|
||||
return unix.Access("/proc/thread-self/", unix.F_OK) == nil
|
||||
})
|
||||
|
||||
var errUnsafeProcfs = errors.New("unsafe procfs detected")
|
||||
|
||||
type procThreadSelfCloser func()
|
||||
|
||||
// procThreadSelf returns a handle to /proc/thread-self/<subpath> (or an
|
||||
// equivalent handle on older kernels where /proc/thread-self doesn't exist).
|
||||
// Once finished with the handle, you must call the returned closer function
|
||||
// (runtime.UnlockOSThread). You must not pass the returned *os.File to other
|
||||
// Go threads or use the handle after calling the closer.
|
||||
//
|
||||
// This is similar to ProcThreadSelf from runc, but with extra hardening
|
||||
// applied and using *os.File.
|
||||
func procThreadSelf(procRoot *os.File, subpath string) (_ *os.File, _ procThreadSelfCloser, Err error) {
|
||||
// We need to lock our thread until the caller is done with the handle
|
||||
// because between getting the handle and using it we could get interrupted
|
||||
// by the Go runtime and hit the case where the underlying thread is
|
||||
// swapped out and the original thread is killed, resulting in
|
||||
// pull-your-hair-out-hard-to-debug issues in the caller.
|
||||
runtime.LockOSThread()
|
||||
defer func() {
|
||||
if Err != nil {
|
||||
runtime.UnlockOSThread()
|
||||
}
|
||||
}()
|
||||
|
||||
// Figure out what prefix we want to use.
|
||||
threadSelf := "thread-self/"
|
||||
if !hasProcThreadSelf() || hookForceProcSelfTask() {
|
||||
/// Pre-3.17 kernels don't have /proc/thread-self, so do it manually.
|
||||
threadSelf = "self/task/" + strconv.Itoa(unix.Gettid()) + "/"
|
||||
if _, err := fstatatFile(procRoot, threadSelf, unix.AT_SYMLINK_NOFOLLOW); err != nil || hookForceProcSelf() {
|
||||
// In this case, we running in a pid namespace that doesn't match
|
||||
// the /proc mount we have. This can happen inside runc.
|
||||
//
|
||||
// Unfortunately, there is no nice way to get the correct TID to
|
||||
// use here because of the age of the kernel, so we have to just
|
||||
// use /proc/self and hope that it works.
|
||||
threadSelf = "self/"
|
||||
}
|
||||
}
|
||||
|
||||
// Grab the handle.
|
||||
var (
|
||||
handle *os.File
|
||||
err error
|
||||
)
|
||||
if hasOpenat2() {
|
||||
// We prefer being able to use RESOLVE_NO_XDEV if we can, to be
|
||||
// absolutely sure we are operating on a clean /proc handle that
|
||||
// doesn't have any cheeky overmounts that could trick us (including
|
||||
// symlink mounts on top of /proc/thread-self). RESOLVE_BENEATH isn't
|
||||
// strictly needed, but just use it since we have it.
|
||||
//
|
||||
// NOTE: /proc/self is technically a magic-link (the contents of the
|
||||
// symlink are generated dynamically), but it doesn't use
|
||||
// nd_jump_link() so RESOLVE_NO_MAGICLINKS allows it.
|
||||
//
|
||||
// NOTE: We MUST NOT use RESOLVE_IN_ROOT here, as openat2File uses
|
||||
// procSelfFdReadlink to clean up the returned f.Name() if we use
|
||||
// RESOLVE_IN_ROOT (which would lead to an infinite recursion).
|
||||
handle, err = openat2File(procRoot, threadSelf+subpath, &unix.OpenHow{
|
||||
Flags: unix.O_PATH | unix.O_NOFOLLOW | unix.O_CLOEXEC,
|
||||
Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_XDEV | unix.RESOLVE_NO_MAGICLINKS,
|
||||
})
|
||||
if err != nil {
|
||||
// TODO: Once we bump the minimum Go version to 1.20, we can use
|
||||
// multiple %w verbs for this wrapping. For now we need to use a
|
||||
// compatibility shim for older Go versions.
|
||||
//err = fmt.Errorf("%w: %w", errUnsafeProcfs, err)
|
||||
return nil, nil, wrapBaseError(err, errUnsafeProcfs)
|
||||
}
|
||||
} else {
|
||||
handle, err = openatFile(procRoot, threadSelf+subpath, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
// TODO: Once we bump the minimum Go version to 1.20, we can use
|
||||
// multiple %w verbs for this wrapping. For now we need to use a
|
||||
// compatibility shim for older Go versions.
|
||||
//err = fmt.Errorf("%w: %w", errUnsafeProcfs, err)
|
||||
return nil, nil, wrapBaseError(err, errUnsafeProcfs)
|
||||
}
|
||||
defer func() {
|
||||
if Err != nil {
|
||||
_ = handle.Close()
|
||||
}
|
||||
}()
|
||||
// We can't detect bind-mounts of different parts of procfs on top of
|
||||
// /proc (a-la RESOLVE_NO_XDEV), but we can at least be sure that we
|
||||
// aren't on the wrong filesystem here.
|
||||
if statfs, err := fstatfs(handle); err != nil {
|
||||
return nil, nil, err
|
||||
} else if statfs.Type != procSuperMagic {
|
||||
return nil, nil, fmt.Errorf("%w: incorrect /proc/self/fd filesystem type 0x%x", errUnsafeProcfs, statfs.Type)
|
||||
}
|
||||
}
|
||||
return handle, runtime.UnlockOSThread, nil
|
||||
}
|
||||
|
||||
// STATX_MNT_ID_UNIQUE is provided in golang.org/x/sys@v0.20.0, but in order to
|
||||
// avoid bumping the requirement for a single constant we can just define it
|
||||
// ourselves.
|
||||
const STATX_MNT_ID_UNIQUE = 0x4000
|
||||
|
||||
var hasStatxMountId = sync_OnceValue(func() bool {
|
||||
var (
|
||||
stx unix.Statx_t
|
||||
// We don't care which mount ID we get. The kernel will give us the
|
||||
// unique one if it is supported.
|
||||
wantStxMask uint32 = STATX_MNT_ID_UNIQUE | unix.STATX_MNT_ID
|
||||
)
|
||||
err := unix.Statx(-int(unix.EBADF), "/", 0, int(wantStxMask), &stx)
|
||||
return err == nil && stx.Mask&wantStxMask != 0
|
||||
})
|
||||
|
||||
func getMountId(dir *os.File, path string) (uint64, error) {
|
||||
// If we don't have statx(STATX_MNT_ID*) support, we can't do anything.
|
||||
if !hasStatxMountId() {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
var (
|
||||
stx unix.Statx_t
|
||||
// We don't care which mount ID we get. The kernel will give us the
|
||||
// unique one if it is supported.
|
||||
wantStxMask uint32 = STATX_MNT_ID_UNIQUE | unix.STATX_MNT_ID
|
||||
)
|
||||
|
||||
err := unix.Statx(int(dir.Fd()), path, unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW, int(wantStxMask), &stx)
|
||||
if stx.Mask&wantStxMask == 0 {
|
||||
// It's not a kernel limitation, for some reason we couldn't get a
|
||||
// mount ID. Assume it's some kind of attack.
|
||||
err = fmt.Errorf("%w: could not get mount id", errUnsafeProcfs)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, &os.PathError{Op: "statx(STATX_MNT_ID_...)", Path: dir.Name() + "/" + path, Err: err}
|
||||
}
|
||||
return stx.Mnt_id, nil
|
||||
}
|
||||
|
||||
func checkSymlinkOvermount(procRoot *os.File, dir *os.File, path string) error {
|
||||
// Get the mntId of our procfs handle.
|
||||
expectedMountId, err := getMountId(procRoot, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Get the mntId of the target magic-link.
|
||||
gotMountId, err := getMountId(dir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// As long as the directory mount is alive, even with wrapping mount IDs,
|
||||
// we would expect to see a different mount ID here. (Of course, if we're
|
||||
// using unsafeHostProcRoot() then an attaker could change this after we
|
||||
// did this check.)
|
||||
if expectedMountId != gotMountId {
|
||||
return fmt.Errorf("%w: symlink %s/%s has an overmount obscuring the real link (mount ids do not match %d != %d)", errUnsafeProcfs, dir.Name(), path, expectedMountId, gotMountId)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func doRawProcSelfFdReadlink(procRoot *os.File, fd int) (string, error) {
|
||||
fdPath := fmt.Sprintf("fd/%d", fd)
|
||||
procFdLink, closer, err := procThreadSelf(procRoot, fdPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get safe /proc/thread-self/%s handle: %w", fdPath, err)
|
||||
}
|
||||
defer procFdLink.Close()
|
||||
defer closer()
|
||||
|
||||
// Try to detect if there is a mount on top of the magic-link. Since we use the handle directly
|
||||
// provide to the closure. If the closure uses the handle directly, this
|
||||
// should be safe in general (a mount on top of the path afterwards would
|
||||
// not affect the handle itself) and will definitely be safe if we are
|
||||
// using privateProcRoot() (at least since Linux 5.12[1], when anonymous
|
||||
// mount namespaces were completely isolated from external mounts including
|
||||
// mount propagation events).
|
||||
//
|
||||
// [1]: Linux commit ee2e3f50629f ("mount: fix mounting of detached mounts
|
||||
// onto targets that reside on shared mounts").
|
||||
if err := checkSymlinkOvermount(procRoot, procFdLink, ""); err != nil {
|
||||
return "", fmt.Errorf("check safety of /proc/thread-self/fd/%d magiclink: %w", fd, err)
|
||||
}
|
||||
|
||||
// readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See Linux commit
|
||||
// 65cfc6722361 ("readlinkat(), fchownat() and fstatat() with empty
|
||||
// relative pathnames").
|
||||
return readlinkatFile(procFdLink, "")
|
||||
}
|
||||
|
||||
func rawProcSelfFdReadlink(fd int) (string, error) {
|
||||
procRoot, err := getProcRoot()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return doRawProcSelfFdReadlink(procRoot, fd)
|
||||
}
|
||||
|
||||
func procSelfFdReadlink(f *os.File) (string, error) {
|
||||
return rawProcSelfFdReadlink(int(f.Fd()))
|
||||
}
|
||||
|
||||
var (
|
||||
errPossibleBreakout = errors.New("possible breakout detected")
|
||||
errInvalidDirectory = errors.New("wandered into deleted directory")
|
||||
errDeletedInode = errors.New("cannot verify path of deleted inode")
|
||||
)
|
||||
|
||||
func isDeadInode(file *os.File) error {
|
||||
// If the nlink of a file drops to 0, there is an attacker deleting
|
||||
// directories during our walk, which could result in weird /proc values.
|
||||
// It's better to error out in this case.
|
||||
stat, err := fstat(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check for dead inode: %w", err)
|
||||
}
|
||||
if stat.Nlink == 0 {
|
||||
err := errDeletedInode
|
||||
if stat.Mode&unix.S_IFMT == unix.S_IFDIR {
|
||||
err = errInvalidDirectory
|
||||
}
|
||||
return fmt.Errorf("%w %q", err, file.Name())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkProcSelfFdPath(path string, file *os.File) error {
|
||||
if err := isDeadInode(file); err != nil {
|
||||
return err
|
||||
}
|
||||
actualPath, err := procSelfFdReadlink(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get path of handle: %w", err)
|
||||
}
|
||||
if actualPath != path {
|
||||
return fmt.Errorf("%w: handle path %q doesn't match expected path %q", errPossibleBreakout, actualPath, path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Test hooks used in the procfs tests to verify that the fallback logic works.
|
||||
// See testing_mocks_linux_test.go and procfs_linux_test.go for more details.
|
||||
var (
|
||||
hookForcePrivateProcRootOpenTree = hookDummyFile
|
||||
hookForcePrivateProcRootOpenTreeAtRecursive = hookDummyFile
|
||||
hookForceGetProcRootUnsafe = hookDummy
|
||||
|
||||
hookForceProcSelfTask = hookDummy
|
||||
hookForceProcSelf = hookDummy
|
||||
)
|
||||
|
||||
func hookDummy() bool { return false }
|
||||
func hookDummyFile(_ *os.File) bool { return false }
|
2
vendor/github.com/cyphar/filepath-securejoin/vfs.go
generated
vendored
2
vendor/github.com/cyphar/filepath-securejoin/vfs.go
generated
vendored
@ -1,3 +1,5 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Copyright (C) 2017-2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
24
vendor/github.com/docker/cli/cli/cobra.go
generated
vendored
24
vendor/github.com/docker/cli/cli/cobra.go
generated
vendored
@ -168,34 +168,30 @@ func (tcmd *TopLevelCommand) Initialize(ops ...command.CLIOption) error {
|
||||
}
|
||||
|
||||
// VisitAll will traverse all commands from the root.
|
||||
// This is different from the VisitAll of cobra.Command where only parents
|
||||
// are checked.
|
||||
//
|
||||
// Deprecated: this utility was only used internally and will be removed in the next release.
|
||||
func VisitAll(root *cobra.Command, fn func(*cobra.Command)) {
|
||||
visitAll(root, fn)
|
||||
}
|
||||
|
||||
func visitAll(root *cobra.Command, fn func(*cobra.Command)) {
|
||||
for _, cmd := range root.Commands() {
|
||||
VisitAll(cmd, fn)
|
||||
visitAll(cmd, fn)
|
||||
}
|
||||
fn(root)
|
||||
}
|
||||
|
||||
// DisableFlagsInUseLine sets the DisableFlagsInUseLine flag on all
|
||||
// commands within the tree rooted at cmd.
|
||||
//
|
||||
// Deprecated: this utility was only used internally and will be removed in the next release.
|
||||
func DisableFlagsInUseLine(cmd *cobra.Command) {
|
||||
VisitAll(cmd, func(ccmd *cobra.Command) {
|
||||
visitAll(cmd, func(ccmd *cobra.Command) {
|
||||
// do not add a `[flags]` to the end of the usage line.
|
||||
ccmd.DisableFlagsInUseLine = true
|
||||
})
|
||||
}
|
||||
|
||||
// HasCompletionArg returns true if a cobra completion arg request is found.
|
||||
func HasCompletionArg(args []string) bool {
|
||||
for _, arg := range args {
|
||||
if arg == cobra.ShellCompRequestCmd || arg == cobra.ShellCompNoDescRequestCmd {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var helpCommand = &cobra.Command{
|
||||
Use: "help [command]",
|
||||
Short: "Help about the command",
|
||||
|
62
vendor/github.com/docker/cli/cli/command/cli.go
generated
vendored
62
vendor/github.com/docker/cli/cli/command/cli.go
generated
vendored
@ -282,6 +282,17 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
|
||||
}
|
||||
filterResourceAttributesEnvvar()
|
||||
|
||||
// early return if GODEBUG is already set or the docker context is
|
||||
// the default context, i.e. is a virtual context where we won't override
|
||||
// any GODEBUG values.
|
||||
if v := os.Getenv("GODEBUG"); cli.currentContext == DefaultContextName || v != "" {
|
||||
return nil
|
||||
}
|
||||
meta, err := cli.contextStore.GetMetadata(cli.currentContext)
|
||||
if err == nil {
|
||||
setGoDebug(meta)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -475,6 +486,57 @@ func (cli *DockerCli) getDockerEndPoint() (ep docker.Endpoint, err error) {
|
||||
return resolveDockerEndpoint(cli.contextStore, cn)
|
||||
}
|
||||
|
||||
// setGoDebug is an escape hatch that sets the GODEBUG environment
|
||||
// variable value using docker context metadata.
|
||||
//
|
||||
// {
|
||||
// "Name": "my-context",
|
||||
// "Metadata": { "GODEBUG": "x509negativeserial=1" }
|
||||
// }
|
||||
//
|
||||
// WARNING: Setting x509negativeserial=1 allows Go's x509 library to accept
|
||||
// X.509 certificates with negative serial numbers.
|
||||
// This behavior is deprecated and non-compliant with current security
|
||||
// standards (RFC 5280). Accepting negative serial numbers can introduce
|
||||
// serious security vulnerabilities, including the risk of certificate
|
||||
// collision or bypass attacks.
|
||||
// This option should only be used for legacy compatibility and never in
|
||||
// production environments.
|
||||
// Use at your own risk.
|
||||
func setGoDebug(meta store.Metadata) {
|
||||
fieldName := "GODEBUG"
|
||||
godebugEnv := os.Getenv(fieldName)
|
||||
// early return if GODEBUG is already set. We don't want to override what
|
||||
// the user already sets.
|
||||
if godebugEnv != "" {
|
||||
return
|
||||
}
|
||||
|
||||
var cfg any
|
||||
var ok bool
|
||||
switch m := meta.Metadata.(type) {
|
||||
case DockerContext:
|
||||
cfg, ok = m.AdditionalFields[fieldName]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
case map[string]any:
|
||||
cfg, ok = m[fieldName]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
v, ok := cfg.(string)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// set the GODEBUG environment variable with whatever was in the context
|
||||
_ = os.Setenv(fieldName, v)
|
||||
}
|
||||
|
||||
func (cli *DockerCli) initialize() error {
|
||||
cli.init.Do(func() {
|
||||
cli.dockerEndpoint, cli.initErr = cli.getDockerEndPoint()
|
||||
|
62
vendor/github.com/docker/cli/cli/command/registry.go
generated
vendored
62
vendor/github.com/docker/cli/cli/command/registry.go
generated
vendored
@ -31,11 +31,13 @@ const (
|
||||
// authConfigKey is the key used to store credentials for Docker Hub. It is
|
||||
// a copy of [registry.IndexServer].
|
||||
//
|
||||
// [registry.IndexServer]: https://pkg.go.dev/github.com/docker/docker/registry#IndexServer
|
||||
// [registry.IndexServer]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/registry#IndexServer
|
||||
const authConfigKey = "https://index.docker.io/v1/"
|
||||
|
||||
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
|
||||
// for the given command to prompt the user for username and password.
|
||||
//
|
||||
// Deprecated: this function is no longer used and will be removed in the next release.
|
||||
func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) registrytypes.RequestAuthConfig {
|
||||
configKey := getAuthConfigKey(index.Name)
|
||||
isDefaultRegistry := configKey == authConfigKey || index.Official
|
||||
@ -66,6 +68,8 @@ func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInf
|
||||
//
|
||||
// It is similar to [registry.ResolveAuthConfig], but uses the credentials-
|
||||
// store, instead of looking up credentials from a map.
|
||||
//
|
||||
// [registry.ResolveAuthConfig]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/registry#ResolveAuthConfig
|
||||
func ResolveAuthConfig(cfg *configfile.ConfigFile, index *registrytypes.IndexInfo) registrytypes.AuthConfig {
|
||||
configKey := index.Name
|
||||
if index.Official {
|
||||
@ -97,23 +101,6 @@ func GetDefaultAuthConfig(cfg *configfile.ConfigFile, checkCredStore bool, serve
|
||||
return registrytypes.AuthConfig(authconfig), nil
|
||||
}
|
||||
|
||||
// ConfigureAuth handles prompting of user's username and password if needed.
|
||||
//
|
||||
// Deprecated: use [PromptUserForCredentials] instead.
|
||||
func ConfigureAuth(ctx context.Context, cli Cli, flUser, flPassword string, authConfig *registrytypes.AuthConfig, _ bool) error {
|
||||
defaultUsername := authConfig.Username
|
||||
serverAddress := authConfig.ServerAddress
|
||||
|
||||
newAuthConfig, err := PromptUserForCredentials(ctx, cli, flUser, flPassword, defaultUsername, serverAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authConfig.Username = newAuthConfig.Username
|
||||
authConfig.Password = newAuthConfig.Password
|
||||
return nil
|
||||
}
|
||||
|
||||
// PromptUserForCredentials handles the CLI prompt for the user to input
|
||||
// credentials.
|
||||
// If argUser is not empty, then the user is only prompted for their password.
|
||||
@ -209,47 +196,38 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete
|
||||
// image. The auth configuration is serialized as a base64url encoded RFC4648,
|
||||
// section 5) JSON string for sending through the X-Registry-Auth header.
|
||||
// RetrieveAuthTokenFromImage retrieves an encoded auth token given a
|
||||
// complete image reference. The auth configuration is serialized as a
|
||||
// base64url encoded ([RFC 4648, Section 5]) JSON string for sending through
|
||||
// the "X-Registry-Auth" header.
|
||||
//
|
||||
// For details on base64url encoding, see:
|
||||
// - RFC4648, section 5: https://tools.ietf.org/html/rfc4648#section-5
|
||||
// [RFC 4648, Section 5]: https://tools.ietf.org/html/rfc4648#section-5
|
||||
func RetrieveAuthTokenFromImage(cfg *configfile.ConfigFile, image string) (string, error) {
|
||||
// Retrieve encoded auth token from the image reference
|
||||
authConfig, err := resolveAuthConfigFromImage(cfg, image)
|
||||
registryRef, err := reference.ParseNormalizedNamed(image)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig)
|
||||
configKey := getAuthConfigKey(reference.Domain(registryRef))
|
||||
authConfig, err := cfg.GetAuthConfig(configKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
encodedAuth, err := registrytypes.EncodeAuthConfig(registrytypes.AuthConfig(authConfig))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return encodedAuth, nil
|
||||
}
|
||||
|
||||
// resolveAuthConfigFromImage retrieves that AuthConfig using the image string
|
||||
func resolveAuthConfigFromImage(cfg *configfile.ConfigFile, image string) (registrytypes.AuthConfig, error) {
|
||||
registryRef, err := reference.ParseNormalizedNamed(image)
|
||||
if err != nil {
|
||||
return registrytypes.AuthConfig{}, err
|
||||
}
|
||||
configKey := getAuthConfigKey(reference.Domain(registryRef))
|
||||
a, err := cfg.GetAuthConfig(configKey)
|
||||
if err != nil {
|
||||
return registrytypes.AuthConfig{}, err
|
||||
}
|
||||
return registrytypes.AuthConfig(a), nil
|
||||
}
|
||||
|
||||
// getAuthConfigKey special-cases using the full index address of the official
|
||||
// index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
|
||||
//
|
||||
// It is similar to [registry.GetAuthConfigKey], but does not require on
|
||||
// [registrytypes.IndexInfo] as intermediate.
|
||||
//
|
||||
// [registry.GetAuthConfigKey]: https://pkg.go.dev/github.com/docker/docker/registry#GetAuthConfigKey
|
||||
// [registrytypes.IndexInfo]:https://pkg.go.dev/github.com/docker/docker/api/types/registry#IndexInfo
|
||||
// [registry.GetAuthConfigKey]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/registry#GetAuthConfigKey
|
||||
// [registrytypes.IndexInfo]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/api/types/registry#IndexInfo
|
||||
func getAuthConfigKey(domainName string) string {
|
||||
if domainName == "docker.io" || domainName == "index.docker.io" {
|
||||
return authConfigKey
|
||||
|
12
vendor/github.com/docker/cli/cli/command/stack/formatter/formatter.go
generated
vendored
12
vendor/github.com/docker/cli/cli/command/stack/formatter/formatter.go
generated
vendored
@ -8,21 +8,31 @@ import (
|
||||
|
||||
const (
|
||||
// SwarmStackTableFormat is the default Swarm stack format
|
||||
//
|
||||
// Deprecated: this type was for internal use and will be removed in the next release.
|
||||
SwarmStackTableFormat formatter.Format = "table {{.Name}}\t{{.Services}}"
|
||||
|
||||
stackServicesHeader = "SERVICES"
|
||||
|
||||
// TableFormatKey is an alias for formatter.TableFormatKey
|
||||
//
|
||||
// Deprecated: this type was for internal use and will be removed in the next release.
|
||||
TableFormatKey = formatter.TableFormatKey
|
||||
)
|
||||
|
||||
// Context is an alias for formatter.Context
|
||||
//
|
||||
// Deprecated: this type was for internal use and will be removed in the next release.
|
||||
type Context = formatter.Context
|
||||
|
||||
// Format is an alias for formatter.Format
|
||||
//
|
||||
// Deprecated: this type was for internal use and will be removed in the next release.
|
||||
type Format = formatter.Format
|
||||
|
||||
// Stack contains deployed stack information.
|
||||
//
|
||||
// Deprecated: this type was for internal use and will be removed in the next release.
|
||||
type Stack struct {
|
||||
// Name is the name of the stack
|
||||
Name string
|
||||
@ -31,6 +41,8 @@ type Stack struct {
|
||||
}
|
||||
|
||||
// StackWrite writes formatted stacks using the Context
|
||||
//
|
||||
// Deprecated: this function was for internal use and will be removed in the next release.
|
||||
func StackWrite(ctx formatter.Context, stacks []*Stack) error {
|
||||
render := func(format func(subContext formatter.SubContext) error) error {
|
||||
for _, stack := range stacks {
|
||||
|
15
vendor/github.com/docker/cli/cli/command/trust.go
generated
vendored
15
vendor/github.com/docker/cli/cli/command/trust.go
generated
vendored
@ -1,15 +0,0 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// AddTrustVerificationFlags adds content trust flags to the provided flagset
|
||||
func AddTrustVerificationFlags(fs *pflag.FlagSet, v *bool, trusted bool) {
|
||||
fs.BoolVar(v, "disable-content-trust", !trusted, "Skip image verification")
|
||||
}
|
||||
|
||||
// AddTrustSigningFlags adds "signing" flags to the provided flagset
|
||||
func AddTrustSigningFlags(fs *pflag.FlagSet, v *bool, trusted bool) {
|
||||
fs.BoolVar(v, "disable-content-trust", !trusted, "Skip image signing")
|
||||
}
|
28
vendor/github.com/docker/cli/cli/command/utils.go
generated
vendored
28
vendor/github.com/docker/cli/cli/command/utils.go
generated
vendored
@ -14,30 +14,20 @@ import (
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/cli/internal/prompt"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/moby/sys/atomicwriter"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// CopyToFile writes the content of the reader to the specified file
|
||||
// ErrPromptTerminated is returned if the user terminated the prompt.
|
||||
//
|
||||
// Deprecated: use [atomicwriter.New].
|
||||
func CopyToFile(outfile string, r io.Reader) error {
|
||||
writer, err := atomicwriter.New(outfile, 0o600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer writer.Close()
|
||||
_, err = io.Copy(writer, r)
|
||||
return err
|
||||
}
|
||||
|
||||
// Deprecated: this error is for internal use and will be removed in the next release.
|
||||
const ErrPromptTerminated = prompt.ErrTerminated
|
||||
|
||||
// DisableInputEcho disables input echo on the provided streams.In.
|
||||
// This is useful when the user provides sensitive information like passwords.
|
||||
// The function returns a restore function that should be called to restore the
|
||||
// terminal state.
|
||||
//
|
||||
// Deprecated: this function is for internal use and will be removed in the next release.
|
||||
func DisableInputEcho(ins *streams.In) (restore func() error, err error) {
|
||||
return prompt.DisableInputEcho(ins)
|
||||
}
|
||||
@ -49,6 +39,8 @@ func DisableInputEcho(ins *streams.In) (restore func() error, err error) {
|
||||
// When the prompt returns an error, the caller should propagate the error up
|
||||
// the stack and close the io.Reader used for the prompt which will prevent the
|
||||
// background goroutine from blocking indefinitely.
|
||||
//
|
||||
// Deprecated: this function is for internal use and will be removed in the next release.
|
||||
func PromptForInput(ctx context.Context, in io.Reader, out io.Writer, message string) (string, error) {
|
||||
return prompt.ReadInput(ctx, in, out, message)
|
||||
}
|
||||
@ -63,6 +55,8 @@ func PromptForInput(ctx context.Context, in io.Reader, out io.Writer, message st
|
||||
// When the prompt returns an error, the caller should propagate the error up
|
||||
// the stack and close the io.Reader used for the prompt which will prevent the
|
||||
// background goroutine from blocking indefinitely.
|
||||
//
|
||||
// Deprecated: this function is for internal use and will be removed in the next release.
|
||||
func PromptForConfirmation(ctx context.Context, ins io.Reader, outs io.Writer, message string) (bool, error) {
|
||||
return prompt.Confirm(ctx, ins, outs, message)
|
||||
}
|
||||
@ -108,12 +102,6 @@ func PruneFilters(dockerCLI config.Provider, pruneFilters filters.Args) filters.
|
||||
return pruneFilters
|
||||
}
|
||||
|
||||
// AddPlatformFlag adds `platform` to a set of flags for API version 1.32 and later.
|
||||
func AddPlatformFlag(flags *pflag.FlagSet, target *string) {
|
||||
flags.StringVar(target, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable")
|
||||
_ = flags.SetAnnotation("platform", "version", []string{"1.32"})
|
||||
}
|
||||
|
||||
// ValidateOutputPath validates the output paths of the "docker cp" command.
|
||||
func ValidateOutputPath(path string) error {
|
||||
dir := filepath.Dir(filepath.Clean(path))
|
||||
|
@ -1,4 +1,4 @@
|
||||
package opts
|
||||
package loader
|
||||
|
||||
import (
|
||||
"os"
|
||||
@ -6,19 +6,21 @@ import (
|
||||
"github.com/docker/cli/pkg/kvfile"
|
||||
)
|
||||
|
||||
// ParseEnvFile reads a file with environment variables enumerated by lines
|
||||
// parseEnvFile reads a file with environment variables enumerated by lines
|
||||
//
|
||||
// “Environment variable names used by the utilities in the Shell and
|
||||
// Utilities volume of IEEE Std 1003.1-2001 consist solely of uppercase
|
||||
// Utilities volume of [IEEE Std 1003.1-2001] consist solely of uppercase
|
||||
// letters, digits, and the '_' (underscore) from the characters defined in
|
||||
// Portable Character Set and do not begin with a digit. *But*, other
|
||||
// characters may be permitted by an implementation; applications shall
|
||||
// tolerate the presence of such names.”
|
||||
// -- http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html
|
||||
//
|
||||
// As of #16585, it's up to application inside docker to validate or not
|
||||
// As of [moby-16585], it's up to application inside docker to validate or not
|
||||
// environment variables, that's why we just strip leading whitespace and
|
||||
// nothing more.
|
||||
func ParseEnvFile(filename string) ([]string, error) {
|
||||
//
|
||||
// [IEEE Std 1003.1-2001]: http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html
|
||||
// [moby-16585]: https://github.com/moby/moby/issues/16585
|
||||
func parseEnvFile(filename string) ([]string, error) {
|
||||
return kvfile.Parse(filename, os.LookupEnv)
|
||||
}
|
12
vendor/github.com/docker/cli/cli/compose/loader/loader.go
generated
vendored
12
vendor/github.com/docker/cli/cli/compose/loader/loader.go
generated
vendored
@ -17,6 +17,7 @@ import (
|
||||
"github.com/docker/cli/cli/compose/schema"
|
||||
"github.com/docker/cli/cli/compose/template"
|
||||
"github.com/docker/cli/cli/compose/types"
|
||||
"github.com/docker/cli/internal/volumespec"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/cli/opts/swarmopts"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
@ -41,6 +42,13 @@ type Options struct {
|
||||
discardEnvFiles bool
|
||||
}
|
||||
|
||||
// ParseVolume parses a volume spec without any knowledge of the target platform.
|
||||
//
|
||||
// This function is unused, but kept for backward-compatibility for external users.
|
||||
func ParseVolume(spec string) (types.ServiceVolumeConfig, error) {
|
||||
return volumespec.Parse(spec)
|
||||
}
|
||||
|
||||
// WithDiscardEnvFiles sets the Options to discard the `env_file` section after resolving to
|
||||
// the `environment` section
|
||||
func WithDiscardEnvFiles(options *Options) {
|
||||
@ -460,7 +468,7 @@ func resolveEnvironment(serviceConfig *types.ServiceConfig, workingDir string, l
|
||||
|
||||
for _, file := range serviceConfig.EnvFile {
|
||||
filePath := absPath(workingDir, file)
|
||||
fileVars, err := opts.ParseEnvFile(filePath)
|
||||
fileVars, err := parseEnvFile(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -756,7 +764,7 @@ var transformBuildConfig TransformerFunc = func(data any) (any, error) {
|
||||
var transformServiceVolumeConfig TransformerFunc = func(data any) (any, error) {
|
||||
switch value := data.(type) {
|
||||
case string:
|
||||
return ParseVolume(value)
|
||||
return volumespec.Parse(value)
|
||||
case map[string]any:
|
||||
return data, nil
|
||||
default:
|
||||
|
34
vendor/github.com/docker/cli/cli/compose/types/types.go
generated
vendored
34
vendor/github.com/docker/cli/cli/compose/types/types.go
generated
vendored
@ -8,6 +8,8 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/internal/volumespec"
|
||||
)
|
||||
|
||||
// UnsupportedProperties not yet supported by this implementation of the compose file
|
||||
@ -390,43 +392,23 @@ type ServicePortConfig struct {
|
||||
}
|
||||
|
||||
// ServiceVolumeConfig are references to a volume used by a service
|
||||
type ServiceVolumeConfig struct {
|
||||
Type string `yaml:",omitempty" json:"type,omitempty"`
|
||||
Source string `yaml:",omitempty" json:"source,omitempty"`
|
||||
Target string `yaml:",omitempty" json:"target,omitempty"`
|
||||
ReadOnly bool `mapstructure:"read_only" yaml:"read_only,omitempty" json:"read_only,omitempty"`
|
||||
Consistency string `yaml:",omitempty" json:"consistency,omitempty"`
|
||||
Bind *ServiceVolumeBind `yaml:",omitempty" json:"bind,omitempty"`
|
||||
Volume *ServiceVolumeVolume `yaml:",omitempty" json:"volume,omitempty"`
|
||||
Image *ServiceVolumeImage `yaml:",omitempty" json:"image,omitempty"`
|
||||
Tmpfs *ServiceVolumeTmpfs `yaml:",omitempty" json:"tmpfs,omitempty"`
|
||||
Cluster *ServiceVolumeCluster `yaml:",omitempty" json:"cluster,omitempty"`
|
||||
}
|
||||
type ServiceVolumeConfig = volumespec.VolumeConfig
|
||||
|
||||
// ServiceVolumeBind are options for a service volume of type bind
|
||||
type ServiceVolumeBind struct {
|
||||
Propagation string `yaml:",omitempty" json:"propagation,omitempty"`
|
||||
}
|
||||
type ServiceVolumeBind = volumespec.BindOpts
|
||||
|
||||
// ServiceVolumeVolume are options for a service volume of type volume
|
||||
type ServiceVolumeVolume struct {
|
||||
NoCopy bool `mapstructure:"nocopy" yaml:"nocopy,omitempty" json:"nocopy,omitempty"`
|
||||
Subpath string `mapstructure:"subpath" yaml:"subpath,omitempty" json:"subpath,omitempty"`
|
||||
}
|
||||
type ServiceVolumeVolume = volumespec.VolumeOpts
|
||||
|
||||
// ServiceVolumeImage are options for a service volume of type image
|
||||
type ServiceVolumeImage struct {
|
||||
Subpath string `mapstructure:"subpath" yaml:"subpath,omitempty" json:"subpath,omitempty"`
|
||||
}
|
||||
type ServiceVolumeImage = volumespec.ImageOpts
|
||||
|
||||
// ServiceVolumeTmpfs are options for a service volume of type tmpfs
|
||||
type ServiceVolumeTmpfs struct {
|
||||
Size int64 `yaml:",omitempty" json:"size,omitempty"`
|
||||
}
|
||||
type ServiceVolumeTmpfs = volumespec.TmpFsOpts
|
||||
|
||||
// ServiceVolumeCluster are options for a service volume of type cluster.
|
||||
// Deliberately left blank for future options, but unused now.
|
||||
type ServiceVolumeCluster struct{}
|
||||
type ServiceVolumeCluster = volumespec.ClusterOpts
|
||||
|
||||
// FileReferenceConfig for a reference to a swarm file object
|
||||
type FileReferenceConfig struct {
|
||||
|
4
vendor/github.com/docker/cli/cli/config/types/authconfig.go
generated
vendored
4
vendor/github.com/docker/cli/cli/config/types/authconfig.go
generated
vendored
@ -7,8 +7,8 @@ type AuthConfig struct {
|
||||
Auth string `json:"auth,omitempty"`
|
||||
|
||||
// Email is an optional value associated with the username.
|
||||
// This field is deprecated and will be removed in a later
|
||||
// version of docker.
|
||||
//
|
||||
// Deprecated: This field is deprecated since docker 1.11 (API v1.23) and will be removed in the next release.
|
||||
Email string `json:"email,omitempty"`
|
||||
|
||||
ServerAddress string `json:"serveraddress,omitempty"`
|
||||
|
6
vendor/github.com/docker/cli/cli/context/store/errors.go
generated
vendored
6
vendor/github.com/docker/cli/cli/context/store/errors.go
generated
vendored
@ -1,9 +1,9 @@
|
||||
package store
|
||||
|
||||
import cerrdefs "github.com/containerd/errdefs"
|
||||
import "github.com/containerd/errdefs"
|
||||
|
||||
func invalidParameter(err error) error {
|
||||
if err == nil || cerrdefs.IsInvalidArgument(err) {
|
||||
if err == nil || errdefs.IsInvalidArgument(err) {
|
||||
return err
|
||||
}
|
||||
return invalidParameterErr{err}
|
||||
@ -14,7 +14,7 @@ type invalidParameterErr struct{ error }
|
||||
func (invalidParameterErr) InvalidParameter() {}
|
||||
|
||||
func notFound(err error) error {
|
||||
if err == nil || cerrdefs.IsNotFound(err) {
|
||||
if err == nil || errdefs.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return notFoundErr{err}
|
||||
|
77
vendor/github.com/docker/cli/cli/flags/options.go
generated
vendored
77
vendor/github.com/docker/cli/cli/flags/options.go
generated
vendored
@ -1,12 +1,12 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/cli/cli/config"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -54,6 +54,39 @@ var (
|
||||
dockerTLS = os.Getenv(EnvEnableTLS) != ""
|
||||
)
|
||||
|
||||
// hostVar is used for the '--host' / '-H' flag to set [ClientOptions.Hosts].
|
||||
// The [ClientOptions.Hosts] field is a slice because it was originally shared
|
||||
// with the daemon config. However, the CLI only allows for a single host to
|
||||
// be specified.
|
||||
//
|
||||
// hostVar presents itself as a "string", but stores the value in a string
|
||||
// slice. It produces an error when trying to set multiple values, matching
|
||||
// the check in [getServerHost].
|
||||
//
|
||||
// [getServerHost]: https://github.com/docker/cli/blob/7eab668982645def1cd46fe1b60894cba6fd17a4/cli/command/cli.go#L542-L551
|
||||
type hostVar struct {
|
||||
dst *[]string
|
||||
set bool
|
||||
}
|
||||
|
||||
func (h *hostVar) String() string {
|
||||
if h.dst == nil || len(*h.dst) == 0 {
|
||||
return ""
|
||||
}
|
||||
return (*h.dst)[0]
|
||||
}
|
||||
|
||||
func (h *hostVar) Set(s string) error {
|
||||
if h.set {
|
||||
return errors.New("specify only one -H")
|
||||
}
|
||||
*h.dst = []string{s}
|
||||
h.set = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*hostVar) Type() string { return "string" }
|
||||
|
||||
// ClientOptions are the options used to configure the client cli.
|
||||
type ClientOptions struct {
|
||||
Debug bool
|
||||
@ -90,13 +123,13 @@ func (o *ClientOptions) InstallFlags(flags *pflag.FlagSet) {
|
||||
KeyFile: filepath.Join(dockerCertPath, DefaultKeyFile),
|
||||
}
|
||||
tlsOptions := o.TLSOptions
|
||||
flags.Var(opts.NewQuotedString(&tlsOptions.CAFile), "tlscacert", "Trust certs signed only by this CA")
|
||||
flags.Var(opts.NewQuotedString(&tlsOptions.CertFile), "tlscert", "Path to TLS certificate file")
|
||||
flags.Var(opts.NewQuotedString(&tlsOptions.KeyFile), "tlskey", "Path to TLS key file")
|
||||
flags.Var("edString{&tlsOptions.CAFile}, "tlscacert", "Trust certs signed only by this CA")
|
||||
flags.Var("edString{&tlsOptions.CertFile}, "tlscert", "Path to TLS certificate file")
|
||||
flags.Var("edString{&tlsOptions.KeyFile}, "tlskey", "Path to TLS key file")
|
||||
|
||||
// opts.ValidateHost is not used here, so as to allow connection helpers
|
||||
hostOpt := opts.NewNamedListOptsRef("hosts", &o.Hosts, nil)
|
||||
flags.VarP(hostOpt, "host", "H", "Daemon socket to connect to")
|
||||
// TODO(thaJeztah): show the default host.
|
||||
// TODO(thaJeztah): this should be a string, not an "array" as we only allow a single host.
|
||||
flags.VarP(&hostVar{dst: &o.Hosts}, "host", "H", "Daemon socket to connect to")
|
||||
flags.StringVarP(&o.Context, "context", "c", "",
|
||||
`Name of the context to use to connect to the daemon (overrides `+client.EnvOverrideHost+` env var and default context set with "docker context use")`)
|
||||
}
|
||||
@ -146,3 +179,33 @@ func SetLogLevel(logLevel string) {
|
||||
logrus.SetLevel(logrus.InfoLevel)
|
||||
}
|
||||
}
|
||||
|
||||
type quotedString struct {
|
||||
value *string
|
||||
}
|
||||
|
||||
func (s *quotedString) Set(val string) error {
|
||||
*s.value = trimQuotes(val)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*quotedString) Type() string {
|
||||
return "string"
|
||||
}
|
||||
|
||||
func (s *quotedString) String() string {
|
||||
return *s.value
|
||||
}
|
||||
|
||||
func trimQuotes(value string) string {
|
||||
if len(value) < 2 {
|
||||
return value
|
||||
}
|
||||
lastIndex := len(value) - 1
|
||||
for _, char := range []byte{'\'', '"'} {
|
||||
if value[0] == char && value[lastIndex] == char {
|
||||
return value[1:lastIndex]
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
40
vendor/github.com/docker/cli/internal/volumespec/types.go
generated
vendored
Normal file
40
vendor/github.com/docker/cli/internal/volumespec/types.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
package volumespec
|
||||
|
||||
// VolumeConfig are references to a volume used by a service
|
||||
type VolumeConfig struct {
|
||||
Type string `yaml:",omitempty" json:"type,omitempty"`
|
||||
Source string `yaml:",omitempty" json:"source,omitempty"`
|
||||
Target string `yaml:",omitempty" json:"target,omitempty"`
|
||||
ReadOnly bool `mapstructure:"read_only" yaml:"read_only,omitempty" json:"read_only,omitempty"`
|
||||
Consistency string `yaml:",omitempty" json:"consistency,omitempty"`
|
||||
Bind *BindOpts `yaml:",omitempty" json:"bind,omitempty"`
|
||||
Volume *VolumeOpts `yaml:",omitempty" json:"volume,omitempty"`
|
||||
Image *ImageOpts `yaml:",omitempty" json:"image,omitempty"`
|
||||
Tmpfs *TmpFsOpts `yaml:",omitempty" json:"tmpfs,omitempty"`
|
||||
Cluster *ClusterOpts `yaml:",omitempty" json:"cluster,omitempty"`
|
||||
}
|
||||
|
||||
// BindOpts are options for a service volume of type bind
|
||||
type BindOpts struct {
|
||||
Propagation string `yaml:",omitempty" json:"propagation,omitempty"`
|
||||
}
|
||||
|
||||
// VolumeOpts are options for a service volume of type volume
|
||||
type VolumeOpts struct {
|
||||
NoCopy bool `mapstructure:"nocopy" yaml:"nocopy,omitempty" json:"nocopy,omitempty"`
|
||||
Subpath string `mapstructure:"subpath" yaml:"subpath,omitempty" json:"subpath,omitempty"`
|
||||
}
|
||||
|
||||
// ImageOpts are options for a service volume of type image
|
||||
type ImageOpts struct {
|
||||
Subpath string `mapstructure:"subpath" yaml:"subpath,omitempty" json:"subpath,omitempty"`
|
||||
}
|
||||
|
||||
// TmpFsOpts are options for a service volume of type tmpfs
|
||||
type TmpFsOpts struct {
|
||||
Size int64 `yaml:",omitempty" json:"size,omitempty"`
|
||||
}
|
||||
|
||||
// ClusterOpts are options for a service volume of type cluster.
|
||||
// Deliberately left blank for future options, but unused now.
|
||||
type ClusterOpts struct{}
|
@ -1,20 +1,19 @@
|
||||
package loader
|
||||
package volumespec
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/docker/cli/cli/compose/types"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const endOfSpec = rune(0)
|
||||
|
||||
// ParseVolume parses a volume spec without any knowledge of the target platform
|
||||
func ParseVolume(spec string) (types.ServiceVolumeConfig, error) {
|
||||
volume := types.ServiceVolumeConfig{}
|
||||
// Parse parses a volume spec without any knowledge of the target platform
|
||||
func Parse(spec string) (VolumeConfig, error) {
|
||||
volume := VolumeConfig{}
|
||||
|
||||
switch len(spec) {
|
||||
case 0:
|
||||
@ -49,7 +48,7 @@ func isWindowsDrive(buffer []rune, char rune) bool {
|
||||
return char == ':' && len(buffer) == 1 && unicode.IsLetter(buffer[0])
|
||||
}
|
||||
|
||||
func populateFieldFromBuffer(char rune, buffer []rune, volume *types.ServiceVolumeConfig) error {
|
||||
func populateFieldFromBuffer(char rune, buffer []rune, volume *VolumeConfig) error {
|
||||
strBuffer := string(buffer)
|
||||
switch {
|
||||
case len(buffer) == 0:
|
||||
@ -74,10 +73,10 @@ func populateFieldFromBuffer(char rune, buffer []rune, volume *types.ServiceVolu
|
||||
case "rw":
|
||||
volume.ReadOnly = false
|
||||
case "nocopy":
|
||||
volume.Volume = &types.ServiceVolumeVolume{NoCopy: true}
|
||||
volume.Volume = &VolumeOpts{NoCopy: true}
|
||||
default:
|
||||
if isBindOption(option) {
|
||||
volume.Bind = &types.ServiceVolumeBind{Propagation: option}
|
||||
volume.Bind = &BindOpts{Propagation: option}
|
||||
}
|
||||
// ignore unknown options
|
||||
}
|
||||
@ -94,7 +93,7 @@ func isBindOption(option string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func populateType(volume *types.ServiceVolumeConfig) {
|
||||
func populateType(volume *VolumeConfig) {
|
||||
switch {
|
||||
// Anonymous volume
|
||||
case volume.Source == "":
|
11
vendor/github.com/docker/cli/opts/env.go
generated
vendored
11
vendor/github.com/docker/cli/opts/env.go
generated
vendored
@ -7,13 +7,14 @@ import (
|
||||
)
|
||||
|
||||
// ValidateEnv validates an environment variable and returns it.
|
||||
// If no value is specified, it obtains its value from the current environment
|
||||
// If no value is specified, it obtains its value from the current environment.
|
||||
//
|
||||
// As on ParseEnvFile and related to #16585, environment variable names
|
||||
// are not validated, and it's up to the application inside the container
|
||||
// to validate them or not.
|
||||
// Environment variable names are not validated, and it's up to the application
|
||||
// inside the container to validate them (see [moby-16585]). The only validation
|
||||
// here is to check if name is empty, per [moby-25099].
|
||||
//
|
||||
// The only validation here is to check if name is empty, per #25099
|
||||
// [moby-16585]: https://github.com/moby/moby/issues/16585
|
||||
// [moby-25099]: https://github.com/moby/moby/issues/25099
|
||||
func ValidateEnv(val string) (string, error) {
|
||||
k, _, hasValue := strings.Cut(val, "=")
|
||||
if k == "" {
|
||||
|
14
vendor/github.com/docker/cli/opts/envfile_deprecated.go
generated
vendored
Normal file
14
vendor/github.com/docker/cli/opts/envfile_deprecated.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
package opts
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/docker/cli/pkg/kvfile"
|
||||
)
|
||||
|
||||
// ParseEnvFile reads a file with environment variables enumerated by lines
|
||||
//
|
||||
// Deprecated: use [kvfile.Parse] and pass [os.LookupEnv] to lookup env-vars from the current environment.
|
||||
func ParseEnvFile(filename string) ([]string, error) {
|
||||
return kvfile.Parse(filename, os.LookupEnv)
|
||||
}
|
2
vendor/github.com/docker/cli/opts/hosts.go
generated
vendored
2
vendor/github.com/docker/cli/opts/hosts.go
generated
vendored
@ -34,7 +34,7 @@ const (
|
||||
|
||||
// ValidateHost validates that the specified string is a valid host and returns it.
|
||||
//
|
||||
// TODO(thaJeztah): ValidateHost appears to be unused; deprecate it.
|
||||
// Deprecated: this function is no longer used, and will be removed in the next release.
|
||||
func ValidateHost(val string) (string, error) {
|
||||
host := strings.TrimSpace(val)
|
||||
// The empty string means default and is not handled by parseDockerDaemonHost
|
||||
|
14
vendor/github.com/docker/cli/opts/opts.go
generated
vendored
14
vendor/github.com/docker/cli/opts/opts.go
generated
vendored
@ -134,6 +134,8 @@ func (opts *ListOpts) WithValidator(validator ValidatorFctType) *ListOpts {
|
||||
|
||||
// NamedOption is an interface that list and map options
|
||||
// with names implement.
|
||||
//
|
||||
// Deprecated: NamedOption is no longer used and will be removed in the next release.
|
||||
type NamedOption interface {
|
||||
Name() string
|
||||
}
|
||||
@ -141,6 +143,8 @@ type NamedOption interface {
|
||||
// NamedListOpts is a ListOpts with a configuration name.
|
||||
// This struct is useful to keep reference to the assigned
|
||||
// field name in the internal configuration struct.
|
||||
//
|
||||
// Deprecated: NamedListOpts is no longer used and will be removed in the next release.
|
||||
type NamedListOpts struct {
|
||||
name string
|
||||
ListOpts
|
||||
@ -149,6 +153,8 @@ type NamedListOpts struct {
|
||||
var _ NamedOption = &NamedListOpts{}
|
||||
|
||||
// NewNamedListOptsRef creates a reference to a new NamedListOpts struct.
|
||||
//
|
||||
// Deprecated: NewNamedListOptsRef is no longer used and will be removed in the next release.
|
||||
func NewNamedListOptsRef(name string, values *[]string, validator ValidatorFctType) *NamedListOpts {
|
||||
return &NamedListOpts{
|
||||
name: name,
|
||||
@ -157,6 +163,8 @@ func NewNamedListOptsRef(name string, values *[]string, validator ValidatorFctTy
|
||||
}
|
||||
|
||||
// Name returns the name of the NamedListOpts in the configuration.
|
||||
//
|
||||
// Deprecated: NamedListOpts is no longer used and will be removed in the next release.
|
||||
func (o *NamedListOpts) Name() string {
|
||||
return o.name
|
||||
}
|
||||
@ -210,6 +218,8 @@ func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts {
|
||||
// NamedMapOpts is a MapOpts struct with a configuration name.
|
||||
// This struct is useful to keep reference to the assigned
|
||||
// field name in the internal configuration struct.
|
||||
//
|
||||
// Deprecated: NamedMapOpts is no longer used and will be removed in the next release.
|
||||
type NamedMapOpts struct {
|
||||
name string
|
||||
MapOpts
|
||||
@ -218,6 +228,8 @@ type NamedMapOpts struct {
|
||||
var _ NamedOption = &NamedMapOpts{}
|
||||
|
||||
// NewNamedMapOpts creates a reference to a new NamedMapOpts struct.
|
||||
//
|
||||
// Deprecated: NamedMapOpts is no longer used and will be removed in the next release.
|
||||
func NewNamedMapOpts(name string, values map[string]string, validator ValidatorFctType) *NamedMapOpts {
|
||||
return &NamedMapOpts{
|
||||
name: name,
|
||||
@ -226,6 +238,8 @@ func NewNamedMapOpts(name string, values map[string]string, validator ValidatorF
|
||||
}
|
||||
|
||||
// Name returns the name of the NamedMapOpts in the configuration.
|
||||
//
|
||||
// Deprecated: NamedMapOpts is no longer used and will be removed in the next release.
|
||||
func (o *NamedMapOpts) Name() string {
|
||||
return o.name
|
||||
}
|
||||
|
4
vendor/github.com/docker/cli/opts/quotedstring.go
generated
vendored
4
vendor/github.com/docker/cli/opts/quotedstring.go
generated
vendored
@ -2,6 +2,8 @@ package opts
|
||||
|
||||
// QuotedString is a string that may have extra quotes around the value. The
|
||||
// quotes are stripped from the value.
|
||||
//
|
||||
// Deprecated: This option type is no longer used and will be removed in the next release.
|
||||
type QuotedString struct {
|
||||
value *string
|
||||
}
|
||||
@ -35,6 +37,8 @@ func trimQuotes(value string) string {
|
||||
}
|
||||
|
||||
// NewQuotedString returns a new quoted string option
|
||||
//
|
||||
// Deprecated: This option type is no longer used and will be removed in the next release.
|
||||
func NewQuotedString(value *string) *QuotedString {
|
||||
return &QuotedString{value: value}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user