forked from toolshed/abra
		
	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 | 	coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca | ||||||
| 	git.coopcloud.tech/toolshed/godotenv v1.5.2-0.20250103171850-4d0ca41daa5c | 	git.coopcloud.tech/toolshed/godotenv v1.5.2-0.20250103171850-4d0ca41daa5c | ||||||
| 	github.com/AlecAivazis/survey/v2 v2.3.7 | 	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/lipgloss v1.1.0 | ||||||
| 	github.com/charmbracelet/log v0.4.2 | 	github.com/charmbracelet/log v0.4.2 | ||||||
| 	github.com/distribution/reference v0.6.0 | 	github.com/distribution/reference v0.6.0 | ||||||
| 	github.com/docker/cli v28.3.3+incompatible | 	github.com/docker/cli v28.4.0+incompatible | ||||||
| 	github.com/docker/docker v28.3.3+incompatible | 	github.com/docker/docker v28.4.0+incompatible | ||||||
| 	github.com/docker/go-units v0.5.0 | 	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/go-git/go-git/v5 v5.16.2 | ||||||
| 	github.com/google/go-cmp v0.7.0 | 	github.com/google/go-cmp v0.7.0 | ||||||
| 	github.com/leonelquinteros/gotext v1.7.2 | 	github.com/leonelquinteros/gotext v1.7.2 | ||||||
| @ -22,7 +24,7 @@ require ( | |||||||
| 	github.com/moby/term v0.5.2 | 	github.com/moby/term v0.5.2 | ||||||
| 	github.com/pkg/errors v0.9.1 | 	github.com/pkg/errors v0.9.1 | ||||||
| 	github.com/schollz/progressbar/v3 v3.18.0 | 	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 | 	gopkg.in/yaml.v3 v3.0.1 | ||||||
| 	gotest.tools/v3 v3.5.2 | 	gotest.tools/v3 v3.5.2 | ||||||
| ) | ) | ||||||
| @ -33,22 +35,24 @@ require ( | |||||||
| 	github.com/BurntSushi/toml v1.5.0 // indirect | 	github.com/BurntSushi/toml v1.5.0 // indirect | ||||||
| 	github.com/Microsoft/go-winio v0.6.2 // indirect | 	github.com/Microsoft/go-winio v0.6.2 // indirect | ||||||
| 	github.com/ProtonMail/go-crypto v1.3.0 // 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/aymanbagabas/go-osc52/v2 v2.0.1 // indirect | ||||||
| 	github.com/beorn7/perks v1.0.1 // indirect | 	github.com/beorn7/perks v1.0.1 // indirect | ||||||
| 	github.com/cenkalti/backoff/v4 v4.3.0 // indirect | 	github.com/cenkalti/backoff/v4 v4.3.0 // indirect | ||||||
| 	github.com/cenkalti/backoff/v5 v5.0.3 // indirect | 	github.com/cenkalti/backoff/v5 v5.0.3 // indirect | ||||||
| 	github.com/cespare/xxhash/v2 v2.3.0 // indirect | 	github.com/cespare/xxhash/v2 v2.3.0 // indirect | ||||||
| 	github.com/charmbracelet/colorprofile v0.3.2 // 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/cellbuf v0.0.13 // indirect | ||||||
| 	github.com/charmbracelet/x/term v0.2.1 // 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/cloudflare/circl v1.6.1 // indirect | ||||||
| 	github.com/containerd/errdefs v1.0.0 // indirect | 	github.com/containerd/errdefs v1.0.0 // indirect | ||||||
| 	github.com/containerd/errdefs/pkg v0.3.0 // indirect | 	github.com/containerd/errdefs/pkg v0.3.0 // indirect | ||||||
| 	github.com/containerd/log v0.1.0 // indirect | 	github.com/containerd/log v0.1.0 // indirect | ||||||
| 	github.com/containerd/platforms v0.2.1 // indirect | 	github.com/containerd/platforms v0.2.1 // indirect | ||||||
| 	github.com/cpuguy83/go-md2man/v2 v2.0.7 // 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/davecgh/go-spew v1.1.1 // indirect | ||||||
| 	github.com/docker/distribution v2.8.3+incompatible // indirect | 	github.com/docker/distribution v2.8.3+incompatible // indirect | ||||||
| 	github.com/docker/go-connections v0.6.0 // 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/logr v1.4.3 // indirect | ||||||
| 	github.com/go-logr/stdr v1.2.2 // indirect | 	github.com/go-logr/stdr v1.2.2 // indirect | ||||||
| 	github.com/go-viper/mapstructure/v2 v2.4.0 // 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/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect | ||||||
| 	github.com/google/uuid v1.6.0 // 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/hashicorp/go-cleanhttp v0.5.2 // indirect | ||||||
| 	github.com/inconshreveable/mousetrap v1.1.0 // indirect | 	github.com/inconshreveable/mousetrap v1.1.0 // indirect | ||||||
| 	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // 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/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/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-colorable v0.1.14 // indirect | ||||||
| 	github.com/mattn/go-isatty v0.0.20 // indirect | 	github.com/mattn/go-isatty v0.0.20 // indirect | ||||||
| 	github.com/mattn/go-localereader v0.0.1 // 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/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect | ||||||
| 	github.com/miekg/pkcs11 v1.1.1 // indirect | 	github.com/miekg/pkcs11 v1.1.1 // indirect | ||||||
| 	github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // 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/morikuni/aec v1.0.0 // indirect | ||||||
| 	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect | 	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect | ||||||
| 	github.com/muesli/cancelreader v0.2.2 // 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/muesli/termenv v0.16.0 // indirect | ||||||
| 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect | 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect | ||||||
| 	github.com/opencontainers/go-digest v1.0.0 // indirect | 	github.com/opencontainers/go-digest v1.0.0 // indirect | ||||||
| 	github.com/opencontainers/runc v1.1.13 // indirect | 	github.com/opencontainers/runc v1.1.13 // indirect | ||||||
| 	github.com/opencontainers/runtime-spec v1.1.0 // 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/pmezard/go-difflib v1.0.0 // indirect | ||||||
| 	github.com/prometheus/client_model v0.6.2 // 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/prometheus/procfs v0.17.0 // indirect | ||||||
| 	github.com/rivo/uniseg v0.4.7 // indirect | 	github.com/rivo/uniseg v0.4.7 // indirect | ||||||
| 	github.com/russross/blackfriday/v2 v2.1.0 // indirect | 	github.com/russross/blackfriday/v2 v2.1.0 // indirect | ||||||
| 	github.com/sirupsen/logrus v1.9.3 // indirect | 	github.com/sirupsen/logrus v1.9.3 // indirect | ||||||
| 	github.com/skeema/knownhosts v1.3.1 // 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/xanzy/ssh-agent v0.3.3 // indirect | ||||||
| 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect | 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect | ||||||
| 	github.com/xeipuuv/gojsonschema v1.2.0 // indirect | 	github.com/xeipuuv/gojsonschema v1.2.0 // indirect | ||||||
| 	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect | 	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect | ||||||
| 	go.opentelemetry.io/auto/sdk v1.1.0 // indirect | 	go.opentelemetry.io/auto/sdk v1.2.1 // indirect | ||||||
| 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect | 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect | ||||||
| 	go.opentelemetry.io/otel v1.37.0 // indirect | 	go.opentelemetry.io/otel v1.38.0 // indirect | ||||||
| 	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 // indirect | 	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect | ||||||
| 	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect | 	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect | ||||||
| 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.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/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect | ||||||
| 	go.opentelemetry.io/otel/metric v1.37.0 // indirect | 	go.opentelemetry.io/otel/metric v1.38.0 // indirect | ||||||
| 	go.opentelemetry.io/otel/sdk v1.37.0 // indirect | 	go.opentelemetry.io/otel/sdk v1.38.0 // indirect | ||||||
| 	go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect | 	go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect | ||||||
| 	go.opentelemetry.io/otel/trace v1.37.0 // indirect | 	go.opentelemetry.io/otel/trace v1.38.0 // indirect | ||||||
| 	go.opentelemetry.io/proto/otlp v1.7.1 // indirect | 	go.opentelemetry.io/proto/otlp v1.8.0 // indirect | ||||||
| 	golang.org/x/crypto v0.41.0 // indirect | 	go.yaml.in/yaml/v2 v2.4.3 // indirect | ||||||
| 	golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect | 	golang.org/x/crypto v0.42.0 // indirect | ||||||
| 	golang.org/x/net v0.43.0 // indirect | 	golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect | ||||||
| 	golang.org/x/sync v0.16.0 // indirect | 	golang.org/x/net v0.44.0 // indirect | ||||||
| 	golang.org/x/text v0.28.0 // indirect | 	golang.org/x/text v0.29.0 // indirect | ||||||
| 	golang.org/x/time v0.12.0 // indirect | 	golang.org/x/time v0.13.0 // indirect | ||||||
| 	google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a // indirect | 	google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 // indirect | ||||||
| 	google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // indirect | 	google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // indirect | ||||||
| 	google.golang.org/grpc v1.74.2 // indirect | 	google.golang.org/grpc v1.75.1 // indirect | ||||||
| 	google.golang.org/protobuf v1.36.7 // indirect | 	google.golang.org/protobuf v1.36.9 // indirect | ||||||
| 	gopkg.in/warnings.v0 v0.1.2 // indirect | 	gopkg.in/warnings.v0 v0.1.2 // indirect | ||||||
| 	gopkg.in/yaml.v2 v2.4.0 // 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/patternmatcher v0.6.0 // indirect | ||||||
| 	github.com/moby/sys/sequential v0.6.0 // indirect | 	github.com/moby/sys/sequential v0.6.0 // indirect | ||||||
| 	github.com/opencontainers/image-spec v1.1.1 // 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/sergi/go-diff v1.4.0 // indirect | ||||||
| 	github.com/spf13/cobra v1.9.1 | 	github.com/spf13/cobra v1.10.1 | ||||||
| 	github.com/stretchr/testify v1.10.0 | 	github.com/stretchr/testify v1.11.1 | ||||||
| 	github.com/theupdateframework/notary v0.7.0 // indirect | 	github.com/theupdateframework/notary v0.7.0 // indirect | ||||||
| 	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // 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 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= | ||||||
| github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= | 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/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/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 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= | ||||||
| github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= | 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.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 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= | ||||||
| github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | 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/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= | ||||||
| github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc= | 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 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI= | ||||||
| github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI= | 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 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= | ||||||
| github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= | 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 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig= | ||||||
| github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= | 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.2 h1:ith2ArZS0CJG30cIUfID1LXN7ZFXRCww6RUvAPA+Pzw= | ||||||
| github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= | 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 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= | ||||||
| github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= | 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-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= | ||||||
| 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/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= | ||||||
| github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= | github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= | ||||||
| github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= | 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= | 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.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= | ||||||
| github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= | 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/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/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 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= | ||||||
| github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= | 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/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.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= | ||||||
| github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= | 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.5.0 h1:hIAhkRBMQ8nIeuVwcAoymp7MY4oherZdAxD+m0u9zaw= | ||||||
| github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= | 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/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/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= | ||||||
| github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= | 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/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/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 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.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY= | ||||||
| github.com/docker/cli v28.3.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= | 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 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-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.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 h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= | ||||||
| github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= | 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 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.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk= | ||||||
| github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= | 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.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 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= | ||||||
| github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= | 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/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/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/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.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 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= | ||||||
| github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= | 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.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.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= | ||||||
| github.com/gogo/protobuf v1.3.1/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/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-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= | 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.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.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 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.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= | ||||||
| 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/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= | ||||||
| github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= | 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 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= | 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/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 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= | ||||||
| github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= | 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.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= | ||||||
| github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= | 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.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.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= | ||||||
| github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= | 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.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 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= | ||||||
| github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= | 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/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.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= | 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/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/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/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.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= | ||||||
| github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= | 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.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= | ||||||
| github.com/magiconair/properties v1.8.0/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= | 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 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= | ||||||
| github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= | 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.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= | ||||||
| github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= | ||||||
| github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | 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.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.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= | ||||||
| github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= | 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/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 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= | ||||||
| github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= | 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 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= | ||||||
| github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= | 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= | 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.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= | ||||||
| github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= | 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/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.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= | ||||||
| github.com/pjbgf/sha1cd v0.4.0/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= | 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.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-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||||
| github.com/pkg/errors v0.8.1/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.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.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.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.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= | ||||||
| github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= | 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-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-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= | ||||||
| github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | 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.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.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.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.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= | ||||||
| github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= | 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-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-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= | ||||||
| github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= | 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 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= | ||||||
| github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= | 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/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.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||||||
| github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= | ||||||
| github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= | 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.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= | ||||||
| github.com/spf13/cobra v0.0.3/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.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= | ||||||
| github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= | github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= | ||||||
| github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= | 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 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/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= | ||||||
| github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= | 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.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= | ||||||
| github.com/spf13/pflag v1.0.3/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.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||||
| github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||||
| github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= | github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= | ||||||
| github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | 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 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/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= | 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.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.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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||||
| github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= | ||||||
| github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | 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-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-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= | ||||||
| github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI= | 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.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= | ||||||
| go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= | ||||||
| go.opencensus.io v0.22.3/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.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= | ||||||
| go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= | go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= | ||||||
| go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= | ||||||
| go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= | ||||||
| go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= | go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= | ||||||
| go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= | go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= | ||||||
| go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 h1:zG8GlgXCJQd5BU98C0hZnBbElszTmUgCNCfYneaDL0A= | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= | ||||||
| go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0/go.mod h1:hOfBCz8kv/wuq73Mx2H2QnWokh/kHZxkh6SNF2bdKtw= | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= | ||||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= | ||||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= | ||||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= | ||||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= | 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 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= | ||||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= | 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.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= | ||||||
| go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= | go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= | ||||||
| go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= | go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= | ||||||
| go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= | go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= | ||||||
| go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= | go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= | ||||||
| go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= | go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= | ||||||
| go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= | go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= | ||||||
| go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= | 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 v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= | ||||||
| go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= | go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE= | ||||||
| go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= | 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.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/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 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= | ||||||
| go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= | ||||||
| go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= | 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.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-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-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||||||
| golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/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-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-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.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.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= | ||||||
| golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= | 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-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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||||
| golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= | 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-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-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-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-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= | ||||||
| golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= | 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-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/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= | 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-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-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.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | ||||||
| golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= | golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= | ||||||
| golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= | 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-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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||||
| golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/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-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-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.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-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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
| golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= | golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= | ||||||
| golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= | 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-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-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.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.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= | ||||||
| golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= | 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.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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||||
| golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.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.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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||||||
| golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= | golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= | ||||||
| golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= | 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-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-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-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-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-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.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.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= | ||||||
| golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= | 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-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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||||
| golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/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-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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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.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.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= | ||||||
| google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= | 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-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-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 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-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU= | ||||||
| google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a/go.mod h1:y2yVLIE/CSMCPXaHnSKXxu1spLPnglFLegmgdY23uuE= | 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-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc= | google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 h1:i8QOKZfYg6AbGVZzUAY3LrNWCKF8O6zFisU9Wl9RER4= | ||||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= | 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 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.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= | ||||||
| google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= | 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.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= | ||||||
| google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= | 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.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= | ||||||
| google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= | google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= | ||||||
| google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= | 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-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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= | ||||||
| google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= | 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-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.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | ||||||
| google.golang.org/protobuf v1.27.1/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.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= | ||||||
| google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= | 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/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/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= | 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 | Redistribution and use in source and binary forms, with or without | ||||||
| modification, are permitted provided that the following conditions are | 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 | copyright notice, this list of conditions and the following disclaimer | ||||||
| in the documentation and/or other materials provided with the | in the documentation and/or other materials provided with the | ||||||
| distribution. | 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 | contributors may be used to endorse or promote products derived from | ||||||
| this software without specific prior written permission. | 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 |     - whitespace | ||||||
|     - wrapcheck |     - wrapcheck | ||||||
|   exclusions: |   exclusions: | ||||||
|  |     rules: | ||||||
|  |       - text: '(slog|log)\.\w+' | ||||||
|  |         linters: | ||||||
|  |           - noctx | ||||||
|     generated: lax |     generated: lax | ||||||
|     presets: |     presets: | ||||||
|       - common-false-positives |       - 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 | 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 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
| of this software and associated documentation files (the "Software"), to deal | 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> |     <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://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://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> | </p> | ||||||
|  |  | ||||||
| The fun, functional and stateful way to build terminal apps. A Go framework | 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). | 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 • نحنُ نحب المصادر المفتوحة | 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) | //		       return tea.Batch(someCommand, someOtherCommand) | ||||||
| //	    } | //	    } | ||||||
| func Batch(cmds ...Cmd) Cmd { | 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 | 	var validCmds []Cmd //nolint:prealloc | ||||||
| 	for _, c := range cmds { | 	for _, c := range cmds { | ||||||
| 		if c == nil { | 		if c == nil { | ||||||
| @ -27,26 +48,11 @@ func Batch(cmds ...Cmd) Cmd { | |||||||
| 		return validCmds[0] | 		return validCmds[0] | ||||||
| 	default: | 	default: | ||||||
| 		return func() Msg { | 		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 | // 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 | // 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. | // 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 | 	return originalMode, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // cancelMixin represents a goroutine-safe cancelation status. | // cancelMixin represents a goroutine-safe cancellation status. | ||||||
| type cancelMixin struct { | type cancelMixin struct { | ||||||
| 	unsafeCanceled bool | 	unsafeCanceled bool | ||||||
| 	lock           sync.Mutex | 	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 | 	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 { | func intToUint32OrDie(i int) uint32 { | ||||||
| 	if i < 0 { | 	if i < 0 { | ||||||
| 		panic("cannot convert numEvents " + fmt.Sprint(i) + " to uint32") | 		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. | // 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 | 		return button, action | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	switch { | 	switch btn { | ||||||
| 	case btn == coninput.FROM_LEFT_1ST_BUTTON_PRESSED: // left button | 	case coninput.FROM_LEFT_1ST_BUTTON_PRESSED: // left button | ||||||
| 		button = MouseButtonLeft | 		button = MouseButtonLeft | ||||||
| 	case btn == coninput.RIGHTMOST_BUTTON_PRESSED: // right button | 	case coninput.RIGHTMOST_BUTTON_PRESSED: // right button | ||||||
| 		button = MouseButtonRight | 		button = MouseButtonRight | ||||||
| 	case btn == coninput.FROM_LEFT_2ND_BUTTON_PRESSED: // middle button | 	case coninput.FROM_LEFT_2ND_BUTTON_PRESSED: // middle button | ||||||
| 		button = MouseButtonMiddle | 		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 | 		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 | 		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{} | type enableBracketedPasteMsg struct{} | ||||||
|  |  | ||||||
| // DisableBracketedPaste is a special command that tells the Bubble Tea program | // 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 | // Note that bracketed paste will be automatically disabled when the | ||||||
| // program quits. | // 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. | 		// using the full terminal window. | ||||||
| 		buf.WriteString(ansi.CursorPosition(0, len(newLines))) | 		buf.WriteString(ansi.CursorPosition(0, len(newLines))) | ||||||
| 	} else { | 	} else { | ||||||
| 		buf.WriteString(ansi.CursorBackward(r.width)) | 		buf.WriteByte('\r') | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	_, _ = r.out.Write(buf.Bytes()) | 	_, _ = r.out.Write(buf.Bytes()) | ||||||
|  | |||||||
							
								
								
									
										109
									
								
								vendor/github.com/charmbracelet/bubbletea/tea.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										109
									
								
								vendor/github.com/charmbracelet/bubbletea/tea.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -24,7 +24,6 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/charmbracelet/x/term" | 	"github.com/charmbracelet/x/term" | ||||||
| 	"github.com/muesli/cancelreader" | 	"github.com/muesli/cancelreader" | ||||||
| 	"golang.org/x/sync/errgroup" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // ErrProgramPanic is returned by [Program.Run] when the program recovers from a panic. | // ErrProgramPanic is returned by [Program.Run] when the program recovers from a panic. | ||||||
| @ -73,7 +72,7 @@ const ( | |||||||
| 	customInput | 	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. | // be used in testing. | ||||||
| func (i inputType) String() string { | func (i inputType) String() string { | ||||||
| 	return [...]string{ | 	return [...]string{ | ||||||
| @ -220,7 +219,7 @@ func Suspend() Msg { | |||||||
| // You can send this message with [Suspend()]. | // You can send this message with [Suspend()]. | ||||||
| type SuspendMsg struct{} | 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. | // from a suspend state. | ||||||
| type ResumeMsg struct{} | type ResumeMsg struct{} | ||||||
|  |  | ||||||
| @ -472,42 +471,12 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) { | |||||||
| 				p.exec(msg.cmd, msg.fn) | 				p.exec(msg.cmd, msg.fn) | ||||||
|  |  | ||||||
| 			case BatchMsg: | 			case BatchMsg: | ||||||
| 				for _, cmd := range msg { | 				go p.execBatchMsg(msg) | ||||||
| 					select { |  | ||||||
| 					case <-p.ctx.Done(): |  | ||||||
| 						return model, nil |  | ||||||
| 					case cmds <- cmd: |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 				continue | 				continue | ||||||
|  |  | ||||||
| 			case sequenceMsg: | 			case sequenceMsg: | ||||||
| 				go func() { | 				go p.execSequenceMsg(msg) | ||||||
| 					// Execute commands one at a time, in order. | 				continue | ||||||
| 					for _, cmd := range msg { |  | ||||||
| 						if cmd == nil { |  | ||||||
| 							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: | 			case setWindowTitleMsg: | ||||||
| 				p.SetWindowTitle(string(msg)) | 				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 | // Run initializes the program and runs its event loops, blocking until it gets | ||||||
| // terminated by either [Program.Quit], [Program.Kill], or its signal handler. | // terminated by either [Program.Quit], [Program.Kill], or its signal handler. | ||||||
| // Returns the final model. | // 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. | // Open the Windows equivalent of a TTY. | ||||||
| func openInputTTY() (*os.File, error) { | 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 { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("error opening file: %w", err) | 		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 ( | const ( | ||||||
| 	// ResizeWindowWinOp is a window operation that resizes the terminal | 	// ResizeWindowWinOp is a window operation that resizes the terminal | ||||||
| 	// window. | 	// window. | ||||||
|  | 	// | ||||||
|  | 	// Deprecated: Use constant number directly with [WindowOp]. | ||||||
| 	ResizeWindowWinOp = 4 | 	ResizeWindowWinOp = 4 | ||||||
|  |  | ||||||
| 	// RequestWindowSizeWinOp is a window operation that requests a report of | 	// RequestWindowSizeWinOp is a window operation that requests a report of | ||||||
| 	// the size of the terminal window in pixels. The response is in the form: | 	// the size of the terminal window in pixels. The response is in the form: | ||||||
| 	//  CSI 4 ; height ; width t | 	//  CSI 4 ; height ; width t | ||||||
|  | 	// | ||||||
|  | 	// Deprecated: Use constant number directly with [WindowOp]. | ||||||
| 	RequestWindowSizeWinOp = 14 | 	RequestWindowSizeWinOp = 14 | ||||||
|  |  | ||||||
| 	// RequestCellSizeWinOp is a window operation that requests a report of | 	// 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: | 	// the size of the terminal cell size in pixels. The response is in the form: | ||||||
| 	//  CSI 6 ; height ; width t | 	//  CSI 6 ; height ; width t | ||||||
|  | 	// | ||||||
|  | 	// Deprecated: Use constant number directly with [WindowOp]. | ||||||
| 	RequestCellSizeWinOp = 16 | 	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] ## | ## [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 ## | ## [0.4.1] - 2025-01-28 ## | ||||||
|  |  | ||||||
| ### Fixed ### | ### 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 |   safe to start migrating to as we have extensive tests ensuring they behave | ||||||
|   correctly and are safe against various races and other attacks. |   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 | [open.2]: https://www.man7.org/linux/man-pages/man2/open.2.html | ||||||
|  |  | ||||||
| ## [0.2.5] - 2024-05-03 ## | ## [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 | 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). | 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.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.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 | [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 | [libpathrs]: https://github.com/openSUSE/libpathrs | ||||||
| [go#20126]: https://github.com/golang/go/issues/20126 | [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 | 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 | stable release, some methods implemented by libpathrs have been ported to this | ||||||
| @ -165,5 +166,19 @@ after `MkdirAll`). | |||||||
|  |  | ||||||
| ### License ### | ### License ### | ||||||
|  |  | ||||||
| The license of this project is the same as Go, which is a BSD 3-clause license | `SPDX-License-Identifier: BSD-3-Clause AND MPL-2.0` | ||||||
| available in the `LICENSE` file. |  | ||||||
|  | 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) 2014-2015 Docker Inc & Go Authors. All rights reserved. | ||||||
| // Copyright (C) 2017-2024 SUSE LLC. All rights reserved. | // Copyright (C) 2017-2024 SUSE LLC. All rights reserved. | ||||||
| // Use of this source code is governed by a BSD-style | // 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 | // **not** safe against race conditions where an attacker changes the | ||||||
| // filesystem after (or during) the [SecureJoin] operation. | // filesystem after (or during) the [SecureJoin] operation. | ||||||
| // | // | ||||||
| // The new API is made up of [OpenInRoot] and [MkdirAll] (and derived | // The new API is available in the [pathrs-lite] subpackage, and provide | ||||||
| // functions). These are safe against racing attackers and have several other | // protections against racing attackers as well as several other key | ||||||
| // protections that are not provided by the legacy API. There are many more | // protections against attacks often seen by container runtimes. As the name | ||||||
| // operations that most programs expect to be able to do safely, but we do not | // suggests, [pathrs-lite] is a stripped down (pure Go) reimplementation of | ||||||
| // provide explicit support for them because we want to encourage users to | // [libpathrs]. The main APIs provided are [OpenInRoot], [MkdirAll], and | ||||||
| // switch to [libpathrs](https://github.com/openSUSE/libpathrs) which is a | // [procfs.Handle] -- other APIs are not planned to be ported. The long-term | ||||||
| // cross-language next-generation library that is entirely designed around | // goal is for users to migrate to [libpathrs] which is more fully-featured. | ||||||
| // operating on paths safely. |  | ||||||
| // | // | ||||||
| // securejoin has been used by several container runtimes (Docker, runc, | // securejoin has been used by several container runtimes (Docker, runc, | ||||||
| // Kubernetes, etc) for quite a few years as a de-facto standard for operating | // 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). | // API as soon as possible (or even better, switch to libpathrs). | ||||||
| // | // | ||||||
| // This project was initially intended to be included in the Go standard | // 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 | // library, but it was rejected (see https://go.dev/issue/20126). Much later, | ||||||
| // [new Go proposal](https://go.dev/issue/67002) for a safe path resolution API | // [os.Root] was added to the Go stdlib that shares some of the goals of | ||||||
| // that shares some of the goals of filepath-securejoin. However, that design | // filepath-securejoin. However, its design is intended to work like | ||||||
| // is intended to work like `openat2(RESOLVE_BENEATH)` which does not fit the | // openat2(RESOLVE_BENEATH) which does not fit the usecase of container | ||||||
| // usecase of container runtimes and most system tools. | // 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 | 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) 2014-2015 Docker Inc & Go Authors. All rights reserved. | ||||||
| // Copyright (C) 2017-2025 SUSE LLC. All rights reserved. | // Copyright (C) 2017-2025 SUSE LLC. All rights reserved. | ||||||
| // Use of this source code is governed by a BSD-style | // Use of this source code is governed by a BSD-style | ||||||
| @ -11,9 +13,9 @@ import ( | |||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"syscall" | 	"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 | // 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 | // 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+"/", "/../") | 	return strings.Contains("/"+path+"/", "/../") | ||||||
| } | } | ||||||
|  |  | ||||||
| // SecureJoinVFS joins the two given path components (similar to [filepath.Join]) except | // SecureJoinVFS joins the two given path components (similar to | ||||||
| // that the returned path is guaranteed to be scoped inside the provided root | // [filepath.Join]) except that the returned path is guaranteed to be scoped | ||||||
| // path (when evaluated). Any symbolic links in the path are evaluated with the | // inside the provided root path (when evaluated). Any symbolic links in the | ||||||
| // given root treated as the root of the filesystem, similar to a chroot. The | // path are evaluated with the given root treated as the root of the | ||||||
| // filesystem state is evaluated through the given [VFS] interface (if nil, the | // filesystem, similar to a chroot. The filesystem state is evaluated through | ||||||
| // standard [os].* family of functions are used). | // 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 | // 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 | // 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 | // fully resolved using [filepath.EvalSymlinks] or otherwise constructed to | ||||||
| // avoid containing symlink components. Of course, the root also *must not* be | // avoid containing symlink components. Of course, the root also *must not* be | ||||||
| // attacker-controlled. | // 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 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 | 	// 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 | 	// 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 | 		// It's a symlink, so get its contents and expand it by prepending it | ||||||
| 		// to the yet-unparsed path. | 		// to the yet-unparsed path. | ||||||
| 		linksWalked++ | 		linksWalked++ | ||||||
| 		if linksWalked > maxSymlinkLimit { | 		if linksWalked > consts.MaxSymlinkLimit { | ||||||
| 			return "", &os.PathError{Op: "SecureJoin", Path: root + string(filepath.Separator) + unsafePath, Err: syscall.ELOOP} | 			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 | //go:build linux && go1.20 | ||||||
| 
 | 
 | ||||||
| // Copyright (C) 2024 SUSE LLC. All rights reserved. | // Copyright (C) 2024 SUSE LLC. All rights reserved. | ||||||
| // Use of this source code is governed by a BSD-style | // Use of this source code is governed by a BSD-style | ||||||
| // license that can be found in the LICENSE file. | // license that can be found in the LICENSE file. | ||||||
| 
 | 
 | ||||||
| package securejoin | package gocompat | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"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) | // that on pre-1.20 Go versions only errors.Is() works properly (errors.Unwrap) | ||||||
| // is only guaranteed to give you baseErr. | // 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) | 	return fmt.Errorf("%w: %w", extraErr, baseErr) | ||||||
| } | } | ||||||
| @ -1,10 +1,12 @@ | |||||||
|  | // SPDX-License-Identifier: BSD-3-Clause | ||||||
|  | 
 | ||||||
| //go:build linux && !go1.20 | //go:build linux && !go1.20 | ||||||
| 
 | 
 | ||||||
| // Copyright (C) 2024 SUSE LLC. All rights reserved. | // Copyright (C) 2024 SUSE LLC. All rights reserved. | ||||||
| // Use of this source code is governed by a BSD-style | // Use of this source code is governed by a BSD-style | ||||||
| // license that can be found in the LICENSE file. | // license that can be found in the LICENSE file. | ||||||
| 
 | 
 | ||||||
| package securejoin | package gocompat | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| @ -27,10 +29,10 @@ func (err wrappedError) Error() string { | |||||||
| 	return fmt.Sprintf("%v: %v", err.isError, err.inner) | 	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) | // that on pre-1.20 Go versions only errors.Is() works properly (errors.Unwrap) | ||||||
| // is only guaranteed to give you baseErr. | // is only guaranteed to give you baseErr. | ||||||
| func wrapBaseError(baseErr, extraErr error) error { | func WrapBaseError(baseErr, extraErr error) error { | ||||||
| 	return wrappedError{ | 	return wrappedError{ | ||||||
| 		inner:   baseErr, | 		inner:   baseErr, | ||||||
| 		isError: extraErr, | 		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 | //go:build linux | ||||||
| 
 | 
 | ||||||
| // Copyright (C) 2024 SUSE LLC. All rights reserved. | // Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com> | ||||||
| // Use of this source code is governed by a BSD-style | // Copyright (C) 2024-2025 SUSE LLC | ||||||
| // license that can be found in the LICENSE file. | // | ||||||
|  | // 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 ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| @ -15,6 +20,12 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"golang.org/x/sys/unix" | 	"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 { | type symlinkStackEntry struct { | ||||||
| @ -112,12 +123,12 @@ func (s *symlinkStack) push(dir *os.File, remainingPath, linkTarget string) erro | |||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	// Split the link target and clean up any "" parts. | 	// Split the link target and clean up any "" parts. | ||||||
| 	linkTargetParts := slices_DeleteFunc( | 	linkTargetParts := gocompat.SlicesDeleteFunc( | ||||||
| 		strings.Split(linkTarget, "/"), | 		strings.Split(linkTarget, "/"), | ||||||
| 		func(part string) bool { return part == "" || part == "." }) | 		func(part string) bool { return part == "" || part == "." }) | ||||||
| 
 | 
 | ||||||
| 	// Copy the directory so the caller doesn't close our copy. | 	// Copy the directory so the caller doesn't close our copy. | ||||||
| 	dirCopy, err := dupFile(dir) | 	dirCopy, err := fd.Dup(dir) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		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 | // 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 | // component of the requested path, returning a file handle to the final | ||||||
| // existing component and a string containing the remaining path components. | // 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) | 	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) | 	handle, remainingPath, err := lookupInRoot(root, unsafePath, false) | ||||||
| 	if remainingPath != "" && err == nil { | 	if remainingPath != "" && err == nil { | ||||||
| 		// should never happen | 		// should never happen | ||||||
| @ -174,7 +185,7 @@ func completeLookupInRoot(root *os.File, unsafePath string) (*os.File, error) { | |||||||
| 	return handle, err | 	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 | 	unsafePath = filepath.ToSlash(unsafePath) // noop | ||||||
| 
 | 
 | ||||||
| 	// This is very similar to SecureJoin, except that we operate on the | 	// 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. | 	// managed open, along with the remaining path components not opened. | ||||||
| 
 | 
 | ||||||
| 	// Try to use openat2 if possible. | 	// Try to use openat2 if possible. | ||||||
| 	if hasOpenat2() { | 	if linux.HasOpenat2() { | ||||||
| 		return lookupOpenat2(root, unsafePath, partial) | 		return lookupOpenat2(root, unsafePath, partial) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Get the "actual" root path from /proc/self/fd. This is necessary if the | 	// 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 | 	// 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 | 	// make sure when we do procfs.CheckProcSelfFdPath that we are using the | ||||||
| 	// root path. | 	// correct root path. | ||||||
| 	logicalRootPath, err := procSelfFdReadlink(root) | 	logicalRootPath, err := procfs.ProcSelfFdReadlink(root) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, "", fmt.Errorf("get real root path: %w", err) | 		return nil, "", fmt.Errorf("get real root path: %w", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	currentDir, err := dupFile(root) | 	currentDir, err := fd.Dup(root) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, "", fmt.Errorf("clone root fd: %w", err) | 		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) | 				return nil, "", fmt.Errorf("walking into root with part %q failed: %w", part, err) | ||||||
| 			} | 			} | ||||||
| 			// Jump to root. | 			// Jump to root. | ||||||
| 			rootClone, err := dupFile(root) | 			rootClone, err := fd.Dup(root) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil, "", fmt.Errorf("clone root fd: %w", err) | 				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. | 		// Try to open the next component. | ||||||
| 		nextDir, err := openatFile(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) | 		nextDir, err := fd.Openat(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) | ||||||
| 		switch { | 		switch err { | ||||||
| 		case err == nil: | 		case nil: | ||||||
| 			st, err := nextDir.Stat() | 			st, err := nextDir.Stat() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				_ = nextDir.Close() | 				_ = nextDir.Close() | ||||||
| 				return nil, "", fmt.Errorf("stat component %q: %w", part, err) | 				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: | 			case os.ModeSymlink: | ||||||
| 				// readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See | 				// readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See | ||||||
| 				// Linux commit 65cfc6722361 ("readlinkat(), fchownat() and | 				// Linux commit 65cfc6722361 ("readlinkat(), fchownat() and | ||||||
| 				// fstatat() with empty relative pathnames"). | 				// fstatat() with empty relative pathnames"). | ||||||
| 				linkDest, err := readlinkatFile(nextDir, "") | 				linkDest, err := fd.Readlinkat(nextDir, "") | ||||||
| 				// We don't need the handle anymore. | 				// We don't need the handle anymore. | ||||||
| 				_ = nextDir.Close() | 				_ = nextDir.Close() | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| @ -293,7 +304,7 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi | |||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				linksWalked++ | 				linksWalked++ | ||||||
| 				if linksWalked > maxSymlinkLimit { | 				if linksWalked > consts.MaxSymlinkLimit { | ||||||
| 					return nil, "", &os.PathError{Op: "securejoin.lookupInRoot", Path: logicalRootPath + "/" + unsafePath, Err: unix.ELOOP} | 					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. | 				// Absolute symlinks reset any work we've already done. | ||||||
| 				if path.IsAbs(linkDest) { | 				if path.IsAbs(linkDest) { | ||||||
| 					// Jump to root. | 					// Jump to root. | ||||||
| 					rootClone, err := dupFile(root) | 					rootClone, err := fd.Dup(root) | ||||||
| 					if err != nil { | 					if err != nil { | ||||||
| 						return nil, "", fmt.Errorf("clone root fd: %w", err) | 						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. | 				// rename or mount on the system. | ||||||
| 				if part == ".." { | 				if part == ".." { | ||||||
| 					// Make sure the root hasn't moved. | 					// 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) | 						return nil, "", fmt.Errorf("root path moved during lookup: %w", err) | ||||||
| 					} | 					} | ||||||
| 					// Make sure the path is what we expect. | 					// Make sure the path is what we expect. | ||||||
| 					fullPath := logicalRootPath + nextPath | 					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) | 						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 | 	// context of openat2, a trailing slash and a trailing "/." are completely | ||||||
| 	// equivalent. | 	// equivalent. | ||||||
| 	if strings.HasSuffix(unsafePath, "/") { | 	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 err != nil { | ||||||
| 			if !partial { | 			if !partial { | ||||||
| 				_ = currentDir.Close() | 				_ = currentDir.Close() | ||||||
| @ -1,10 +1,15 @@ | |||||||
|  | // SPDX-License-Identifier: MPL-2.0 | ||||||
|  | 
 | ||||||
| //go:build linux | //go:build linux | ||||||
| 
 | 
 | ||||||
| // Copyright (C) 2024 SUSE LLC. All rights reserved. | // Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com> | ||||||
| // Use of this source code is governed by a BSD-style | // Copyright (C) 2024-2025 SUSE LLC | ||||||
| // license that can be found in the LICENSE file. | // | ||||||
|  | // 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 ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| @ -14,12 +19,13 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"golang.org/x/sys/unix" | 	"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 ( | var errInvalidMode = errors.New("invalid permission mode") | ||||||
| 	errInvalidMode    = errors.New("invalid permission mode") |  | ||||||
| 	errPossibleAttack = errors.New("possible attack detected") |  | ||||||
| ) |  | ||||||
| 
 | 
 | ||||||
| // modePermExt is like os.ModePerm except that it also includes the set[ug]id | // modePermExt is like os.ModePerm except that it also includes the set[ug]id | ||||||
| // and sticky bits. | // 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 | // 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 | // doing [MkdirAll]. If you intend to open the directory after creating it, you | ||||||
| // should use MkdirAllHandle. | // 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) { | func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.File, Err error) { | ||||||
| 	unixMode, err := toUnixMode(mode) | 	unixMode, err := toUnixMode(mode) | ||||||
| 	if err != nil { | 	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 | 	// This is mostly a quality-of-life check, because mkdir will simply fail | ||||||
| 	// later if the attacker deletes the tree after this check. | 	// 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) | 		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) | 		return nil, fmt.Errorf("cannot create subdirectories in %q: %w", currentDir.Name(), unix.ENOTDIR) | ||||||
| 	} else if err != nil { | 	} else if err != nil { | ||||||
| 		return nil, fmt.Errorf("re-opening handle to %q: %w", currentDir.Name(), err) | 		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.Close() | ||||||
| 		currentDir = reopenDir | 		currentDir = reopenDir | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	remainingParts := strings.Split(remainingPath, string(filepath.Separator)) | 	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" | 		// The path contained ".." components after the end of the "real" | ||||||
| 		// components. We could try to safely resolve ".." here but that would | 		// 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 | 		// 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) { | 		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} | 			err = &os.PathError{Op: "mkdirat", Path: currentDir.Name() + "/" + part, Err: err} | ||||||
| 			// Make the error a bit nicer if the directory is dead. | 			// 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 | 				// 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 | 				// multiple %w verbs for this wrapping. For now we need to use a | ||||||
| 				// compatibility shim for older Go versions. | 				// compatibility shim for older Go versions. | ||||||
| 				//err = fmt.Errorf("%w (%w)", err, deadErr) | 				// err = fmt.Errorf("%w (%w)", err, deadErr) | ||||||
| 				err = wrapBaseError(err, deadErr) | 				err = gocompat.WrapBaseError(err, deadErr) | ||||||
| 			} | 			} | ||||||
| 			return nil, err | 			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 | 		// Get a handle to the next component. O_DIRECTORY means we don't need | ||||||
| 		// to use O_PATH. | 		// to use O_PATH. | ||||||
| 		var nextDir *os.File | 		var nextDir *os.File | ||||||
| 		if hasOpenat2() { | 		if linux.HasOpenat2() { | ||||||
| 			nextDir, err = openat2File(currentDir, part, &unix.OpenHow{ | 			nextDir, err = openat2(currentDir, part, &unix.OpenHow{ | ||||||
| 				Flags:   unix.O_NOFOLLOW | unix.O_DIRECTORY | unix.O_CLOEXEC, | 				Flags:   unix.O_NOFOLLOW | unix.O_DIRECTORY | unix.O_CLOEXEC, | ||||||
| 				Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_XDEV, | 				Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_XDEV, | ||||||
| 			}) | 			}) | ||||||
| 		} else { | 		} 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 { | 		if err != nil { | ||||||
| 			return nil, err | 			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 | // 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. | // an open directory handle as the root, you should use [MkdirAllHandle] instead. | ||||||
| // This function is a wrapper around [MkdirAllHandle]. | // 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 { | func MkdirAll(root, unsafePath string, mode os.FileMode) error { | ||||||
| 	rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) | 	rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	defer rootDir.Close() | 	defer rootDir.Close() //nolint:errcheck // close failures aren't critical here | ||||||
| 
 | 
 | ||||||
| 	f, err := MkdirAllHandle(rootDir, unsafePath, mode) | 	f, err := MkdirAllHandle(rootDir, unsafePath, mode) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @ -1,17 +1,22 @@ | |||||||
|  | // SPDX-License-Identifier: MPL-2.0 | ||||||
|  | 
 | ||||||
| //go:build linux | //go:build linux | ||||||
| 
 | 
 | ||||||
| // Copyright (C) 2024 SUSE LLC. All rights reserved. | // Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com> | ||||||
| // Use of this source code is governed by a BSD-style | // Copyright (C) 2024-2025 SUSE LLC | ||||||
| // license that can be found in the LICENSE file. | // | ||||||
|  | // 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 ( | import ( | ||||||
| 	"fmt" |  | ||||||
| 	"os" | 	"os" | ||||||
| 	"strconv" |  | ||||||
| 
 | 
 | ||||||
| 	"golang.org/x/sys/unix" | 	"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 | // 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 | // 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 | // use the returned handle, you can "upgrade" it to a proper handle using | ||||||
| // [Reopen]. | // [Reopen]. | ||||||
|  | // | ||||||
|  | // [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin | ||||||
| func OpenInRoot(root, unsafePath string) (*os.File, error) { | func OpenInRoot(root, unsafePath string) (*os.File, error) { | ||||||
| 	rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) | 	rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	defer rootDir.Close() | 	defer rootDir.Close() //nolint:errcheck // close failures aren't critical here | ||||||
| 	return OpenatInRoot(rootDir, unsafePath) | 	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 | // [CVE-2019-19921]: https://github.com/advisories/GHSA-fh74-hm69-rqjw | ||||||
| func Reopen(handle *os.File, flags int) (*os.File, error) { | func Reopen(handle *os.File, flags int) (*os.File, error) { | ||||||
| 	procRoot, err := getProcRoot() | 	return procfs.ReopenFd(handle, flags) | ||||||
| 	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 |  | ||||||
| } | } | ||||||
							
								
								
									
										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. | // Copyright (C) 2017-2024 SUSE LLC. All rights reserved. | ||||||
| // Use of this source code is governed by a BSD-style | // Use of this source code is governed by a BSD-style | ||||||
| // license that can be found in the LICENSE file. | // 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. | // 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)) { | 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() { | 	for _, cmd := range root.Commands() { | ||||||
| 		VisitAll(cmd, fn) | 		visitAll(cmd, fn) | ||||||
| 	} | 	} | ||||||
| 	fn(root) | 	fn(root) | ||||||
| } | } | ||||||
|  |  | ||||||
| // DisableFlagsInUseLine sets the DisableFlagsInUseLine flag on all | // DisableFlagsInUseLine sets the DisableFlagsInUseLine flag on all | ||||||
| // commands within the tree rooted at cmd. | // 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) { | 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. | 		// do not add a `[flags]` to the end of the usage line. | ||||||
| 		ccmd.DisableFlagsInUseLine = true | 		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{ | var helpCommand = &cobra.Command{ | ||||||
| 	Use:               "help [command]", | 	Use:               "help [command]", | ||||||
| 	Short:             "Help about the 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() | 	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 | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -475,6 +486,57 @@ func (cli *DockerCli) getDockerEndPoint() (ep docker.Endpoint, err error) { | |||||||
| 	return resolveDockerEndpoint(cli.contextStore, cn) | 	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 { | func (cli *DockerCli) initialize() error { | ||||||
| 	cli.init.Do(func() { | 	cli.init.Do(func() { | ||||||
| 		cli.dockerEndpoint, cli.initErr = cli.getDockerEndPoint() | 		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 | // authConfigKey is the key used to store credentials for Docker Hub. It is | ||||||
| // a copy of [registry.IndexServer]. | // 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/" | const authConfigKey = "https://index.docker.io/v1/" | ||||||
|  |  | ||||||
| // RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info | // RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info | ||||||
| // for the given command to prompt the user for username and password. | // 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 { | func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) registrytypes.RequestAuthConfig { | ||||||
| 	configKey := getAuthConfigKey(index.Name) | 	configKey := getAuthConfigKey(index.Name) | ||||||
| 	isDefaultRegistry := configKey == authConfigKey || index.Official | 	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- | // It is similar to [registry.ResolveAuthConfig], but uses the credentials- | ||||||
| // store, instead of looking up credentials from a map. | // 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 { | func ResolveAuthConfig(cfg *configfile.ConfigFile, index *registrytypes.IndexInfo) registrytypes.AuthConfig { | ||||||
| 	configKey := index.Name | 	configKey := index.Name | ||||||
| 	if index.Official { | 	if index.Official { | ||||||
| @ -97,23 +101,6 @@ func GetDefaultAuthConfig(cfg *configfile.ConfigFile, checkCredStore bool, serve | |||||||
| 	return registrytypes.AuthConfig(authconfig), nil | 	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 | // PromptUserForCredentials handles the CLI prompt for the user to input | ||||||
| // credentials. | // credentials. | ||||||
| // If argUser is not empty, then the user is only prompted for their password. | // 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 | 	}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete | // RetrieveAuthTokenFromImage retrieves an encoded auth token given a | ||||||
| // image. The auth configuration is serialized as a base64url encoded RFC4648, | // complete image reference. The auth configuration is serialized as a | ||||||
| // section 5) JSON string for sending through the X-Registry-Auth header. | // base64url encoded ([RFC 4648, Section 5]) JSON string for sending through | ||||||
|  | // the "X-Registry-Auth" header. | ||||||
| // | // | ||||||
| // For details on base64url encoding, see: | // [RFC 4648, Section 5]: https://tools.ietf.org/html/rfc4648#section-5 | ||||||
| // - RFC4648, section 5:   https://tools.ietf.org/html/rfc4648#section-5 |  | ||||||
| func RetrieveAuthTokenFromImage(cfg *configfile.ConfigFile, image string) (string, error) { | func RetrieveAuthTokenFromImage(cfg *configfile.ConfigFile, image string) (string, error) { | ||||||
| 	// Retrieve encoded auth token from the image reference | 	registryRef, err := reference.ParseNormalizedNamed(image) | ||||||
| 	authConfig, err := resolveAuthConfigFromImage(cfg, image) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		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 { | 	if err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
| 	return encodedAuth, nil | 	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 | // 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. | // 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 | // It is similar to [registry.GetAuthConfigKey], but does not require on | ||||||
| // [registrytypes.IndexInfo] as intermediate. | // [registrytypes.IndexInfo] as intermediate. | ||||||
| // | // | ||||||
| // [registry.GetAuthConfigKey]: https://pkg.go.dev/github.com/docker/docker/registry#GetAuthConfigKey | // [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/api/types/registry#IndexInfo | // [registrytypes.IndexInfo]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/api/types/registry#IndexInfo | ||||||
| func getAuthConfigKey(domainName string) string { | func getAuthConfigKey(domainName string) string { | ||||||
| 	if domainName == "docker.io" || domainName == "index.docker.io" { | 	if domainName == "docker.io" || domainName == "index.docker.io" { | ||||||
| 		return authConfigKey | 		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 ( | const ( | ||||||
| 	// SwarmStackTableFormat is the default Swarm stack format | 	// 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}}" | 	SwarmStackTableFormat formatter.Format = "table {{.Name}}\t{{.Services}}" | ||||||
|  |  | ||||||
| 	stackServicesHeader = "SERVICES" | 	stackServicesHeader = "SERVICES" | ||||||
|  |  | ||||||
| 	// TableFormatKey is an alias for formatter.TableFormatKey | 	// 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 | 	TableFormatKey = formatter.TableFormatKey | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Context is an alias for formatter.Context | // 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 | type Context = formatter.Context | ||||||
|  |  | ||||||
| // Format is an alias for formatter.Format | // 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 | type Format = formatter.Format | ||||||
|  |  | ||||||
| // Stack contains deployed stack information. | // Stack contains deployed stack information. | ||||||
|  | // | ||||||
|  | // Deprecated: this type was for internal use and will be removed in the next release. | ||||||
| type Stack struct { | type Stack struct { | ||||||
| 	// Name is the name of the stack | 	// Name is the name of the stack | ||||||
| 	Name string | 	Name string | ||||||
| @ -31,6 +41,8 @@ type Stack struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| // StackWrite writes formatted stacks using the Context | // 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 { | func StackWrite(ctx formatter.Context, stacks []*Stack) error { | ||||||
| 	render := func(format func(subContext formatter.SubContext) error) error { | 	render := func(format func(subContext formatter.SubContext) error) error { | ||||||
| 		for _, stack := range stacks { | 		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/cli/streams" | ||||||
| 	"github.com/docker/cli/internal/prompt" | 	"github.com/docker/cli/internal/prompt" | ||||||
| 	"github.com/docker/docker/api/types/filters" | 	"github.com/docker/docker/api/types/filters" | ||||||
| 	"github.com/moby/sys/atomicwriter" |  | ||||||
| 	"github.com/pkg/errors" | 	"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]. | // Deprecated: this error is for internal use and will be removed in the next release. | ||||||
| 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 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const ErrPromptTerminated = prompt.ErrTerminated | const ErrPromptTerminated = prompt.ErrTerminated | ||||||
|  |  | ||||||
| // DisableInputEcho disables input echo on the provided streams.In. | // DisableInputEcho disables input echo on the provided streams.In. | ||||||
| // This is useful when the user provides sensitive information like passwords. | // This is useful when the user provides sensitive information like passwords. | ||||||
| // The function returns a restore function that should be called to restore the | // The function returns a restore function that should be called to restore the | ||||||
| // terminal state. | // 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) { | func DisableInputEcho(ins *streams.In) (restore func() error, err error) { | ||||||
| 	return prompt.DisableInputEcho(ins) | 	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 | // 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 | // the stack and close the io.Reader used for the prompt which will prevent the | ||||||
| // background goroutine from blocking indefinitely. | // 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) { | func PromptForInput(ctx context.Context, in io.Reader, out io.Writer, message string) (string, error) { | ||||||
| 	return prompt.ReadInput(ctx, in, out, message) | 	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 | // 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 | // the stack and close the io.Reader used for the prompt which will prevent the | ||||||
| // background goroutine from blocking indefinitely. | // 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) { | func PromptForConfirmation(ctx context.Context, ins io.Reader, outs io.Writer, message string) (bool, error) { | ||||||
| 	return prompt.Confirm(ctx, ins, outs, message) | 	return prompt.Confirm(ctx, ins, outs, message) | ||||||
| } | } | ||||||
| @ -108,12 +102,6 @@ func PruneFilters(dockerCLI config.Provider, pruneFilters filters.Args) filters. | |||||||
| 	return pruneFilters | 	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. | // ValidateOutputPath validates the output paths of the "docker cp" command. | ||||||
| func ValidateOutputPath(path string) error { | func ValidateOutputPath(path string) error { | ||||||
| 	dir := filepath.Dir(filepath.Clean(path)) | 	dir := filepath.Dir(filepath.Clean(path)) | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| package opts | package loader | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"os" | 	"os" | ||||||
| @ -6,19 +6,21 @@ import ( | |||||||
| 	"github.com/docker/cli/pkg/kvfile" | 	"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 | // “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 | // letters, digits, and the '_' (underscore) from the characters defined in | ||||||
| // Portable Character Set and do not begin with a digit. *But*, other | // Portable Character Set and do not begin with a digit. *But*, other | ||||||
| // characters may be permitted by an implementation; applications shall | // characters may be permitted by an implementation; applications shall | ||||||
| // tolerate the presence of such names.” | // 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 | // environment variables, that's why we just strip leading whitespace and | ||||||
| // nothing more. | // 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) | 	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/schema" | ||||||
| 	"github.com/docker/cli/cli/compose/template" | 	"github.com/docker/cli/cli/compose/template" | ||||||
| 	"github.com/docker/cli/cli/compose/types" | 	"github.com/docker/cli/cli/compose/types" | ||||||
|  | 	"github.com/docker/cli/internal/volumespec" | ||||||
| 	"github.com/docker/cli/opts" | 	"github.com/docker/cli/opts" | ||||||
| 	"github.com/docker/cli/opts/swarmopts" | 	"github.com/docker/cli/opts/swarmopts" | ||||||
| 	"github.com/docker/docker/api/types/versions" | 	"github.com/docker/docker/api/types/versions" | ||||||
| @ -41,6 +42,13 @@ type Options struct { | |||||||
| 	discardEnvFiles bool | 	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 | // WithDiscardEnvFiles sets the Options to discard the `env_file` section after resolving to | ||||||
| // the `environment` section | // the `environment` section | ||||||
| func WithDiscardEnvFiles(options *Options) { | func WithDiscardEnvFiles(options *Options) { | ||||||
| @ -460,7 +468,7 @@ func resolveEnvironment(serviceConfig *types.ServiceConfig, workingDir string, l | |||||||
|  |  | ||||||
| 		for _, file := range serviceConfig.EnvFile { | 		for _, file := range serviceConfig.EnvFile { | ||||||
| 			filePath := absPath(workingDir, file) | 			filePath := absPath(workingDir, file) | ||||||
| 			fileVars, err := opts.ParseEnvFile(filePath) | 			fileVars, err := parseEnvFile(filePath) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| @ -756,7 +764,7 @@ var transformBuildConfig TransformerFunc = func(data any) (any, error) { | |||||||
| var transformServiceVolumeConfig TransformerFunc = func(data any) (any, error) { | var transformServiceVolumeConfig TransformerFunc = func(data any) (any, error) { | ||||||
| 	switch value := data.(type) { | 	switch value := data.(type) { | ||||||
| 	case string: | 	case string: | ||||||
| 		return ParseVolume(value) | 		return volumespec.Parse(value) | ||||||
| 	case map[string]any: | 	case map[string]any: | ||||||
| 		return data, nil | 		return data, nil | ||||||
| 	default: | 	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" | 	"fmt" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/docker/cli/internal/volumespec" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // UnsupportedProperties not yet supported by this implementation of the compose file | // 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 | // ServiceVolumeConfig are references to a volume used by a service | ||||||
| type ServiceVolumeConfig struct { | type ServiceVolumeConfig = volumespec.VolumeConfig | ||||||
| 	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"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ServiceVolumeBind are options for a service volume of type bind | // ServiceVolumeBind are options for a service volume of type bind | ||||||
| type ServiceVolumeBind struct { | type ServiceVolumeBind = volumespec.BindOpts | ||||||
| 	Propagation string `yaml:",omitempty" json:"propagation,omitempty"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ServiceVolumeVolume are options for a service volume of type volume | // ServiceVolumeVolume are options for a service volume of type volume | ||||||
| type ServiceVolumeVolume struct { | type ServiceVolumeVolume = volumespec.VolumeOpts | ||||||
| 	NoCopy  bool   `mapstructure:"nocopy" yaml:"nocopy,omitempty" json:"nocopy,omitempty"` |  | ||||||
| 	Subpath string `mapstructure:"subpath" yaml:"subpath,omitempty" json:"subpath,omitempty"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ServiceVolumeImage are options for a service volume of type image | // ServiceVolumeImage are options for a service volume of type image | ||||||
| type ServiceVolumeImage struct { | type ServiceVolumeImage = volumespec.ImageOpts | ||||||
| 	Subpath string `mapstructure:"subpath" yaml:"subpath,omitempty" json:"subpath,omitempty"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ServiceVolumeTmpfs are options for a service volume of type tmpfs | // ServiceVolumeTmpfs are options for a service volume of type tmpfs | ||||||
| type ServiceVolumeTmpfs struct { | type ServiceVolumeTmpfs = volumespec.TmpFsOpts | ||||||
| 	Size int64 `yaml:",omitempty" json:"size,omitempty"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ServiceVolumeCluster are options for a service volume of type cluster. | // ServiceVolumeCluster are options for a service volume of type cluster. | ||||||
| // Deliberately left blank for future options, but unused now. | // 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 | // FileReferenceConfig for a reference to a swarm file object | ||||||
| type FileReferenceConfig struct { | 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"` | 	Auth     string `json:"auth,omitempty"` | ||||||
|  |  | ||||||
| 	// Email is an optional value associated with the username. | 	// 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"` | 	Email string `json:"email,omitempty"` | ||||||
|  |  | ||||||
| 	ServerAddress string `json:"serveraddress,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 | package store | ||||||
|  |  | ||||||
| import cerrdefs "github.com/containerd/errdefs" | import "github.com/containerd/errdefs" | ||||||
|  |  | ||||||
| func invalidParameter(err error) error { | func invalidParameter(err error) error { | ||||||
| 	if err == nil || cerrdefs.IsInvalidArgument(err) { | 	if err == nil || errdefs.IsInvalidArgument(err) { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	return invalidParameterErr{err} | 	return invalidParameterErr{err} | ||||||
| @ -14,7 +14,7 @@ type invalidParameterErr struct{ error } | |||||||
| func (invalidParameterErr) InvalidParameter() {} | func (invalidParameterErr) InvalidParameter() {} | ||||||
|  |  | ||||||
| func notFound(err error) error { | func notFound(err error) error { | ||||||
| 	if err == nil || cerrdefs.IsNotFound(err) { | 	if err == nil || errdefs.IsNotFound(err) { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	return notFoundErr{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 | package flags | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
|  |  | ||||||
| 	"github.com/docker/cli/cli/config" | 	"github.com/docker/cli/cli/config" | ||||||
| 	"github.com/docker/cli/opts" |  | ||||||
| 	"github.com/docker/docker/client" | 	"github.com/docker/docker/client" | ||||||
| 	"github.com/docker/go-connections/tlsconfig" | 	"github.com/docker/go-connections/tlsconfig" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| @ -54,6 +54,39 @@ var ( | |||||||
| 	dockerTLS       = os.Getenv(EnvEnableTLS) != "" | 	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. | // ClientOptions are the options used to configure the client cli. | ||||||
| type ClientOptions struct { | type ClientOptions struct { | ||||||
| 	Debug      bool | 	Debug      bool | ||||||
| @ -90,13 +123,13 @@ func (o *ClientOptions) InstallFlags(flags *pflag.FlagSet) { | |||||||
| 		KeyFile:  filepath.Join(dockerCertPath, DefaultKeyFile), | 		KeyFile:  filepath.Join(dockerCertPath, DefaultKeyFile), | ||||||
| 	} | 	} | ||||||
| 	tlsOptions := o.TLSOptions | 	tlsOptions := o.TLSOptions | ||||||
| 	flags.Var(opts.NewQuotedString(&tlsOptions.CAFile), "tlscacert", "Trust certs signed only by this CA") | 	flags.Var("edString{&tlsOptions.CAFile}, "tlscacert", "Trust certs signed only by this CA") | ||||||
| 	flags.Var(opts.NewQuotedString(&tlsOptions.CertFile), "tlscert", "Path to TLS certificate file") | 	flags.Var("edString{&tlsOptions.CertFile}, "tlscert", "Path to TLS certificate file") | ||||||
| 	flags.Var(opts.NewQuotedString(&tlsOptions.KeyFile), "tlskey", "Path to TLS key file") | 	flags.Var("edString{&tlsOptions.KeyFile}, "tlskey", "Path to TLS key file") | ||||||
|  |  | ||||||
| 	// opts.ValidateHost is not used here, so as to allow connection helpers | 	// TODO(thaJeztah): show the default host. | ||||||
| 	hostOpt := opts.NewNamedListOptsRef("hosts", &o.Hosts, nil) | 	// TODO(thaJeztah): this should be a string, not an "array" as we only allow a single host. | ||||||
| 	flags.VarP(hostOpt, "host", "H", "Daemon socket to connect to") | 	flags.VarP(&hostVar{dst: &o.Hosts}, "host", "H", "Daemon socket to connect to") | ||||||
| 	flags.StringVarP(&o.Context, "context", "c", "", | 	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")`) | 		`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) | 		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 ( | import ( | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"unicode" | 	"unicode" | ||||||
| 	"unicode/utf8" | 	"unicode/utf8" | ||||||
| 
 | 
 | ||||||
| 	"github.com/docker/cli/cli/compose/types" |  | ||||||
| 	"github.com/docker/docker/api/types/mount" | 	"github.com/docker/docker/api/types/mount" | ||||||
| 	"github.com/pkg/errors" | 	"github.com/pkg/errors" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const endOfSpec = rune(0) | const endOfSpec = rune(0) | ||||||
| 
 | 
 | ||||||
| // ParseVolume parses a volume spec without any knowledge of the target platform | // Parse parses a volume spec without any knowledge of the target platform | ||||||
| func ParseVolume(spec string) (types.ServiceVolumeConfig, error) { | func Parse(spec string) (VolumeConfig, error) { | ||||||
| 	volume := types.ServiceVolumeConfig{} | 	volume := VolumeConfig{} | ||||||
| 
 | 
 | ||||||
| 	switch len(spec) { | 	switch len(spec) { | ||||||
| 	case 0: | 	case 0: | ||||||
| @ -49,7 +48,7 @@ func isWindowsDrive(buffer []rune, char rune) bool { | |||||||
| 	return char == ':' && len(buffer) == 1 && unicode.IsLetter(buffer[0]) | 	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) | 	strBuffer := string(buffer) | ||||||
| 	switch { | 	switch { | ||||||
| 	case len(buffer) == 0: | 	case len(buffer) == 0: | ||||||
| @ -74,10 +73,10 @@ func populateFieldFromBuffer(char rune, buffer []rune, volume *types.ServiceVolu | |||||||
| 		case "rw": | 		case "rw": | ||||||
| 			volume.ReadOnly = false | 			volume.ReadOnly = false | ||||||
| 		case "nocopy": | 		case "nocopy": | ||||||
| 			volume.Volume = &types.ServiceVolumeVolume{NoCopy: true} | 			volume.Volume = &VolumeOpts{NoCopy: true} | ||||||
| 		default: | 		default: | ||||||
| 			if isBindOption(option) { | 			if isBindOption(option) { | ||||||
| 				volume.Bind = &types.ServiceVolumeBind{Propagation: option} | 				volume.Bind = &BindOpts{Propagation: option} | ||||||
| 			} | 			} | ||||||
| 			// ignore unknown options | 			// ignore unknown options | ||||||
| 		} | 		} | ||||||
| @ -94,7 +93,7 @@ func isBindOption(option string) bool { | |||||||
| 	return false | 	return false | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func populateType(volume *types.ServiceVolumeConfig) { | func populateType(volume *VolumeConfig) { | ||||||
| 	switch { | 	switch { | ||||||
| 	// Anonymous volume | 	// Anonymous volume | ||||||
| 	case volume.Source == "": | 	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. | // 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 | // Environment variable names are not validated, and it's up to the application | ||||||
| // are not validated, and it's up to the application inside the container | // inside the container to validate them (see [moby-16585]). The only validation | ||||||
| // to validate them or not. | // 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) { | func ValidateEnv(val string) (string, error) { | ||||||
| 	k, _, hasValue := strings.Cut(val, "=") | 	k, _, hasValue := strings.Cut(val, "=") | ||||||
| 	if k == "" { | 	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. | // 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) { | func ValidateHost(val string) (string, error) { | ||||||
| 	host := strings.TrimSpace(val) | 	host := strings.TrimSpace(val) | ||||||
| 	// The empty string means default and is not handled by parseDockerDaemonHost | 	// 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 | // NamedOption is an interface that list and map options | ||||||
| // with names implement. | // with names implement. | ||||||
|  | // | ||||||
|  | // Deprecated: NamedOption is no longer used and will be removed in the next release. | ||||||
| type NamedOption interface { | type NamedOption interface { | ||||||
| 	Name() string | 	Name() string | ||||||
| } | } | ||||||
| @ -141,6 +143,8 @@ type NamedOption interface { | |||||||
| // NamedListOpts is a ListOpts with a configuration name. | // NamedListOpts is a ListOpts with a configuration name. | ||||||
| // This struct is useful to keep reference to the assigned | // This struct is useful to keep reference to the assigned | ||||||
| // field name in the internal configuration struct. | // field name in the internal configuration struct. | ||||||
|  | // | ||||||
|  | // Deprecated: NamedListOpts is no longer used and will be removed in the next release. | ||||||
| type NamedListOpts struct { | type NamedListOpts struct { | ||||||
| 	name string | 	name string | ||||||
| 	ListOpts | 	ListOpts | ||||||
| @ -149,6 +153,8 @@ type NamedListOpts struct { | |||||||
| var _ NamedOption = &NamedListOpts{} | var _ NamedOption = &NamedListOpts{} | ||||||
|  |  | ||||||
| // NewNamedListOptsRef creates a reference to a new NamedListOpts struct. | // 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 { | func NewNamedListOptsRef(name string, values *[]string, validator ValidatorFctType) *NamedListOpts { | ||||||
| 	return &NamedListOpts{ | 	return &NamedListOpts{ | ||||||
| 		name:     name, | 		name:     name, | ||||||
| @ -157,6 +163,8 @@ func NewNamedListOptsRef(name string, values *[]string, validator ValidatorFctTy | |||||||
| } | } | ||||||
|  |  | ||||||
| // Name returns the name of the NamedListOpts in the configuration. | // 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 { | func (o *NamedListOpts) Name() string { | ||||||
| 	return o.name | 	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. | // NamedMapOpts is a MapOpts struct with a configuration name. | ||||||
| // This struct is useful to keep reference to the assigned | // This struct is useful to keep reference to the assigned | ||||||
| // field name in the internal configuration struct. | // field name in the internal configuration struct. | ||||||
|  | // | ||||||
|  | // Deprecated: NamedMapOpts is no longer used and will be removed in the next release. | ||||||
| type NamedMapOpts struct { | type NamedMapOpts struct { | ||||||
| 	name string | 	name string | ||||||
| 	MapOpts | 	MapOpts | ||||||
| @ -218,6 +228,8 @@ type NamedMapOpts struct { | |||||||
| var _ NamedOption = &NamedMapOpts{} | var _ NamedOption = &NamedMapOpts{} | ||||||
|  |  | ||||||
| // NewNamedMapOpts creates a reference to a new NamedMapOpts struct. | // 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 { | func NewNamedMapOpts(name string, values map[string]string, validator ValidatorFctType) *NamedMapOpts { | ||||||
| 	return &NamedMapOpts{ | 	return &NamedMapOpts{ | ||||||
| 		name:    name, | 		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. | // 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 { | func (o *NamedMapOpts) Name() string { | ||||||
| 	return o.name | 	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 | // QuotedString is a string that may have extra quotes around the value. The | ||||||
| // quotes are stripped from the value. | // 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 { | type QuotedString struct { | ||||||
| 	value *string | 	value *string | ||||||
| } | } | ||||||
| @ -35,6 +37,8 @@ func trimQuotes(value string) string { | |||||||
| } | } | ||||||
|  |  | ||||||
| // NewQuotedString returns a new quoted string option | // 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 { | func NewQuotedString(value *string) *QuotedString { | ||||||
| 	return &QuotedString{value: value} | 	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