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 | ||||
| 	git.coopcloud.tech/toolshed/godotenv v1.5.2-0.20250103171850-4d0ca41daa5c | ||||
| 	github.com/AlecAivazis/survey/v2 v2.3.7 | ||||
| 	github.com/charmbracelet/bubbletea v1.3.6 | ||||
| 	github.com/charmbracelet/bubbles v0.21.0 | ||||
| 	github.com/charmbracelet/bubbletea v1.3.10 | ||||
| 	github.com/charmbracelet/lipgloss v1.1.0 | ||||
| 	github.com/charmbracelet/log v0.4.2 | ||||
| 	github.com/distribution/reference v0.6.0 | ||||
| 	github.com/docker/cli v28.3.3+incompatible | ||||
| 	github.com/docker/docker v28.3.3+incompatible | ||||
| 	github.com/docker/cli v28.4.0+incompatible | ||||
| 	github.com/docker/docker v28.4.0+incompatible | ||||
| 	github.com/docker/go-units v0.5.0 | ||||
| 	github.com/evertras/bubble-table v0.19.2 | ||||
| 	github.com/go-git/go-git/v5 v5.16.2 | ||||
| 	github.com/google/go-cmp v0.7.0 | ||||
| 	github.com/leonelquinteros/gotext v1.7.2 | ||||
| @ -22,7 +24,7 @@ require ( | ||||
| 	github.com/moby/term v0.5.2 | ||||
| 	github.com/pkg/errors v0.9.1 | ||||
| 	github.com/schollz/progressbar/v3 v3.18.0 | ||||
| 	golang.org/x/term v0.34.0 | ||||
| 	golang.org/x/term v0.35.0 | ||||
| 	gopkg.in/yaml.v3 v3.0.1 | ||||
| 	gotest.tools/v3 v3.5.2 | ||||
| ) | ||||
| @ -33,22 +35,24 @@ require ( | ||||
| 	github.com/BurntSushi/toml v1.5.0 // indirect | ||||
| 	github.com/Microsoft/go-winio v0.6.2 // indirect | ||||
| 	github.com/ProtonMail/go-crypto v1.3.0 // indirect | ||||
| 	github.com/atotto/clipboard v0.1.4 // indirect | ||||
| 	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect | ||||
| 	github.com/beorn7/perks v1.0.1 // indirect | ||||
| 	github.com/cenkalti/backoff/v4 v4.3.0 // indirect | ||||
| 	github.com/cenkalti/backoff/v5 v5.0.3 // indirect | ||||
| 	github.com/cespare/xxhash/v2 v2.3.0 // indirect | ||||
| 	github.com/charmbracelet/colorprofile v0.3.2 // indirect | ||||
| 	github.com/charmbracelet/x/ansi v0.10.1 // indirect | ||||
| 	github.com/charmbracelet/x/ansi v0.10.2 // indirect | ||||
| 	github.com/charmbracelet/x/cellbuf v0.0.13 // indirect | ||||
| 	github.com/charmbracelet/x/term v0.2.1 // indirect | ||||
| 	github.com/clipperhouse/uax29/v2 v2.2.0 // indirect | ||||
| 	github.com/cloudflare/circl v1.6.1 // indirect | ||||
| 	github.com/containerd/errdefs v1.0.0 // indirect | ||||
| 	github.com/containerd/errdefs/pkg v0.3.0 // indirect | ||||
| 	github.com/containerd/log v0.1.0 // indirect | ||||
| 	github.com/containerd/platforms v0.2.1 // indirect | ||||
| 	github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect | ||||
| 	github.com/cyphar/filepath-securejoin v0.4.1 // indirect | ||||
| 	github.com/cyphar/filepath-securejoin v0.5.0 // indirect | ||||
| 	github.com/davecgh/go-spew v1.1.1 // indirect | ||||
| 	github.com/docker/distribution v2.8.3+incompatible // indirect | ||||
| 	github.com/docker/go-connections v0.6.0 // indirect | ||||
| @ -64,21 +68,21 @@ require ( | ||||
| 	github.com/go-logr/logr v1.4.3 // indirect | ||||
| 	github.com/go-logr/stdr v1.2.2 // indirect | ||||
| 	github.com/go-viper/mapstructure/v2 v2.4.0 // indirect | ||||
| 	github.com/gogo/protobuf v1.3.2 // indirect | ||||
| 	github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect | ||||
| 	github.com/google/uuid v1.6.0 // indirect | ||||
| 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect | ||||
| 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect | ||||
| 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect | ||||
| 	github.com/inconshreveable/mousetrap v1.1.0 // indirect | ||||
| 	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect | ||||
| 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect | ||||
| 	github.com/kevinburke/ssh_config v1.2.0 // indirect | ||||
| 	github.com/kevinburke/ssh_config v1.4.0 // indirect | ||||
| 	github.com/klauspost/compress v1.18.0 // indirect | ||||
| 	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect | ||||
| 	github.com/klauspost/cpuid/v2 v2.3.0 // indirect | ||||
| 	github.com/lucasb-eyer/go-colorful v1.3.0 // indirect | ||||
| 	github.com/mattn/go-colorable v0.1.14 // indirect | ||||
| 	github.com/mattn/go-isatty v0.0.20 // indirect | ||||
| 	github.com/mattn/go-localereader v0.0.1 // indirect | ||||
| 	github.com/mattn/go-runewidth v0.0.16 // indirect | ||||
| 	github.com/mattn/go-runewidth v0.0.19 // indirect | ||||
| 	github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect | ||||
| 	github.com/miekg/pkcs11 v1.1.1 // indirect | ||||
| 	github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect | ||||
| @ -90,47 +94,48 @@ require ( | ||||
| 	github.com/morikuni/aec v1.0.0 // indirect | ||||
| 	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect | ||||
| 	github.com/muesli/cancelreader v0.2.2 // indirect | ||||
| 	github.com/muesli/reflow v0.3.0 // indirect | ||||
| 	github.com/muesli/termenv v0.16.0 // indirect | ||||
| 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect | ||||
| 	github.com/opencontainers/go-digest v1.0.0 // indirect | ||||
| 	github.com/opencontainers/runc v1.1.13 // indirect | ||||
| 	github.com/opencontainers/runtime-spec v1.1.0 // indirect | ||||
| 	github.com/pjbgf/sha1cd v0.4.0 // indirect | ||||
| 	github.com/pjbgf/sha1cd v0.5.0 // indirect | ||||
| 	github.com/pmezard/go-difflib v1.0.0 // indirect | ||||
| 	github.com/prometheus/client_model v0.6.2 // indirect | ||||
| 	github.com/prometheus/common v0.65.0 // indirect | ||||
| 	github.com/prometheus/common v0.66.1 // indirect | ||||
| 	github.com/prometheus/procfs v0.17.0 // indirect | ||||
| 	github.com/rivo/uniseg v0.4.7 // indirect | ||||
| 	github.com/russross/blackfriday/v2 v2.1.0 // indirect | ||||
| 	github.com/sirupsen/logrus v1.9.3 // indirect | ||||
| 	github.com/skeema/knownhosts v1.3.1 // indirect | ||||
| 	github.com/spf13/pflag v1.0.7 // indirect | ||||
| 	github.com/spf13/pflag v1.0.10 // indirect | ||||
| 	github.com/xanzy/ssh-agent v0.3.3 // indirect | ||||
| 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect | ||||
| 	github.com/xeipuuv/gojsonschema v1.2.0 // indirect | ||||
| 	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect | ||||
| 	go.opentelemetry.io/auto/sdk v1.1.0 // indirect | ||||
| 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect | ||||
| 	go.opentelemetry.io/otel v1.37.0 // indirect | ||||
| 	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 // indirect | ||||
| 	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect | ||||
| 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect | ||||
| 	go.opentelemetry.io/auto/sdk v1.2.1 // indirect | ||||
| 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect | ||||
| 	go.opentelemetry.io/otel v1.38.0 // indirect | ||||
| 	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect | ||||
| 	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect | ||||
| 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect | ||||
| 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect | ||||
| 	go.opentelemetry.io/otel/metric v1.37.0 // indirect | ||||
| 	go.opentelemetry.io/otel/sdk v1.37.0 // indirect | ||||
| 	go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect | ||||
| 	go.opentelemetry.io/otel/trace v1.37.0 // indirect | ||||
| 	go.opentelemetry.io/proto/otlp v1.7.1 // indirect | ||||
| 	golang.org/x/crypto v0.41.0 // indirect | ||||
| 	golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect | ||||
| 	golang.org/x/net v0.43.0 // indirect | ||||
| 	golang.org/x/sync v0.16.0 // indirect | ||||
| 	golang.org/x/text v0.28.0 // indirect | ||||
| 	golang.org/x/time v0.12.0 // indirect | ||||
| 	google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a // indirect | ||||
| 	google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // indirect | ||||
| 	google.golang.org/grpc v1.74.2 // indirect | ||||
| 	google.golang.org/protobuf v1.36.7 // indirect | ||||
| 	go.opentelemetry.io/otel/metric v1.38.0 // indirect | ||||
| 	go.opentelemetry.io/otel/sdk v1.38.0 // indirect | ||||
| 	go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect | ||||
| 	go.opentelemetry.io/otel/trace v1.38.0 // indirect | ||||
| 	go.opentelemetry.io/proto/otlp v1.8.0 // indirect | ||||
| 	go.yaml.in/yaml/v2 v2.4.3 // indirect | ||||
| 	golang.org/x/crypto v0.42.0 // indirect | ||||
| 	golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect | ||||
| 	golang.org/x/net v0.44.0 // indirect | ||||
| 	golang.org/x/text v0.29.0 // indirect | ||||
| 	golang.org/x/time v0.13.0 // indirect | ||||
| 	google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 // indirect | ||||
| 	google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // indirect | ||||
| 	google.golang.org/grpc v1.75.1 // indirect | ||||
| 	google.golang.org/protobuf v1.36.9 // indirect | ||||
| 	gopkg.in/warnings.v0 v0.1.2 // indirect | ||||
| 	gopkg.in/yaml.v2 v2.4.0 // indirect | ||||
| ) | ||||
| @ -147,11 +152,11 @@ require ( | ||||
| 	github.com/moby/patternmatcher v0.6.0 // indirect | ||||
| 	github.com/moby/sys/sequential v0.6.0 // indirect | ||||
| 	github.com/opencontainers/image-spec v1.1.1 // indirect | ||||
| 	github.com/prometheus/client_golang v1.23.0 // indirect | ||||
| 	github.com/prometheus/client_golang v1.23.2 // indirect | ||||
| 	github.com/sergi/go-diff v1.4.0 // indirect | ||||
| 	github.com/spf13/cobra v1.9.1 | ||||
| 	github.com/stretchr/testify v1.10.0 | ||||
| 	github.com/spf13/cobra v1.10.1 | ||||
| 	github.com/stretchr/testify v1.11.1 | ||||
| 	github.com/theupdateframework/notary v0.7.0 // indirect | ||||
| 	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect | ||||
| 	golang.org/x/sys v0.35.0 | ||||
| 	golang.org/x/sys v0.36.0 | ||||
| ) | ||||
|  | ||||
							
								
								
									
										175
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										175
									
								
								go.sum
									
									
									
									
									
								
							| @ -99,6 +99,8 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 | ||||
| github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= | ||||
| github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= | ||||
| github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= | ||||
| github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= | ||||
| github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= | ||||
| github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= | ||||
| github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= | ||||
| github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= | ||||
| @ -133,20 +135,22 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf | ||||
| github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | ||||
| github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= | ||||
| github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | ||||
| github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU= | ||||
| github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc= | ||||
| github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= | ||||
| github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= | ||||
| github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= | ||||
| github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= | ||||
| github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI= | ||||
| github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI= | ||||
| github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= | ||||
| github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= | ||||
| github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig= | ||||
| github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= | ||||
| github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= | ||||
| github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= | ||||
| github.com/charmbracelet/x/ansi v0.10.2 h1:ith2ArZS0CJG30cIUfID1LXN7ZFXRCww6RUvAPA+Pzw= | ||||
| github.com/charmbracelet/x/ansi v0.10.2/go.mod h1:HbLdJjQH4UH4AqA2HpRWuWNluRE6zxJH/yteYEYCFa8= | ||||
| github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= | ||||
| github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= | ||||
| github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30= | ||||
| github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= | ||||
| github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= | ||||
| github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= | ||||
| github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= | ||||
| github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= | ||||
| github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= | ||||
| @ -164,6 +168,8 @@ github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJ | ||||
| github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= | ||||
| github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= | ||||
| github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= | ||||
| github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY= | ||||
| github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= | ||||
| github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= | ||||
| github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= | ||||
| github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= | ||||
| @ -296,8 +302,8 @@ github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= | ||||
| github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= | ||||
| github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= | ||||
| github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= | ||||
| github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= | ||||
| github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= | ||||
| github.com/cyphar/filepath-securejoin v0.5.0 h1:hIAhkRBMQ8nIeuVwcAoymp7MY4oherZdAxD+m0u9zaw= | ||||
| github.com/cyphar/filepath-securejoin v0.5.0/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= | ||||
| github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= | ||||
| github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= | ||||
| github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= | ||||
| @ -316,16 +322,16 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr | ||||
| github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= | ||||
| github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= | ||||
| github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= | ||||
| github.com/docker/cli v28.3.3+incompatible h1:fp9ZHAr1WWPGdIWBM1b3zLtgCF+83gRdVMTJsUeiyAo= | ||||
| github.com/docker/cli v28.3.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= | ||||
| github.com/docker/cli v28.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY= | ||||
| github.com/docker/cli v28.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= | ||||
| github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= | ||||
| github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= | ||||
| github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= | ||||
| github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= | ||||
| github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= | ||||
| github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= | ||||
| github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= | ||||
| github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= | ||||
| github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk= | ||||
| github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= | ||||
| github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= | ||||
| github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= | ||||
| github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= | ||||
| @ -367,6 +373,8 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6 | ||||
| github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= | ||||
| github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= | ||||
| github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= | ||||
| github.com/evertras/bubble-table v0.19.2 h1:u77oiM6JlRR+CvS5FZc3Hz+J6iEsvEDcR5kO8OFb1Yw= | ||||
| github.com/evertras/bubble-table v0.19.2/go.mod h1:ifHujS1YxwnYSOgcR2+m3GnJ84f7CVU/4kUOxUCjEbQ= | ||||
| github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= | ||||
| github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= | ||||
| github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= | ||||
| @ -439,7 +447,6 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV | ||||
| github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= | ||||
| github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= | ||||
| github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= | ||||
| github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= | ||||
| github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= | ||||
| github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= | ||||
| github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= | ||||
| @ -529,8 +536,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf | ||||
| github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= | ||||
| github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= | ||||
| github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= | ||||
| github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= | ||||
| github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= | ||||
| github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= | ||||
| github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= | ||||
| github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= | ||||
| github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= | ||||
| github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= | ||||
| @ -579,8 +586,8 @@ github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVE | ||||
| github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= | ||||
| github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= | ||||
| github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= | ||||
| github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= | ||||
| github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= | ||||
| github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= | ||||
| github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= | ||||
| github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= | ||||
| github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= | ||||
| github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= | ||||
| @ -590,6 +597,8 @@ github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdY | ||||
| github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= | ||||
| github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= | ||||
| github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= | ||||
| github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= | ||||
| github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= | ||||
| github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= | ||||
| github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | ||||
| github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | ||||
| @ -611,8 +620,8 @@ github.com/leonelquinteros/gotext v1.7.2 h1:bDPndU8nt+/kRo1m4l/1OXiiy2v7Z7dfPQ9+ | ||||
| github.com/leonelquinteros/gotext v1.7.2/go.mod h1:9/haCkm5P7Jay1sxKDGJ5WIg4zkz8oZKw4ekNpALob8= | ||||
| github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= | ||||
| github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= | ||||
| github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= | ||||
| github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= | ||||
| github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= | ||||
| github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= | ||||
| github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= | ||||
| github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= | ||||
| github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= | ||||
| @ -631,8 +640,9 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D | ||||
| github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= | ||||
| github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= | ||||
| github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= | ||||
| github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= | ||||
| github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | ||||
| github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= | ||||
| github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= | ||||
| github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= | ||||
| github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= | ||||
| github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= | ||||
| github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= | ||||
| @ -692,6 +702,8 @@ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D | ||||
| github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= | ||||
| github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= | ||||
| github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= | ||||
| github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= | ||||
| github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= | ||||
| github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= | ||||
| github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= | ||||
| github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= | ||||
| @ -758,8 +770,8 @@ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt | ||||
| github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= | ||||
| github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= | ||||
| github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= | ||||
| github.com/pjbgf/sha1cd v0.4.0 h1:NXzbL1RvjTUi6kgYZCX3fPwwl27Q1LJndxtUDVfJGRY= | ||||
| github.com/pjbgf/sha1cd v0.4.0/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= | ||||
| github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= | ||||
| github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= | ||||
| github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| @ -775,8 +787,8 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf | ||||
| github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= | ||||
| github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= | ||||
| github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= | ||||
| github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= | ||||
| github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= | ||||
| github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= | ||||
| github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= | ||||
| github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= | ||||
| github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= | ||||
| github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||||
| @ -790,8 +802,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 | ||||
| github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= | ||||
| github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= | ||||
| github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= | ||||
| github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= | ||||
| github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= | ||||
| github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= | ||||
| github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= | ||||
| github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= | ||||
| github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= | ||||
| github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= | ||||
| @ -806,6 +818,7 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 | ||||
| github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= | ||||
| github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= | ||||
| github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= | ||||
| github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||||
| github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||||
| github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= | ||||
| github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= | ||||
| @ -851,8 +864,8 @@ github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3 | ||||
| github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= | ||||
| github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= | ||||
| github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= | ||||
| github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= | ||||
| github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= | ||||
| github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= | ||||
| github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= | ||||
| github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= | ||||
| github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= | ||||
| github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= | ||||
| @ -861,9 +874,9 @@ github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bd | ||||
| github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= | ||||
| github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= | ||||
| github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||
| github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||
| github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= | ||||
| github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||
| github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||
| github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= | ||||
| github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||
| github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= | ||||
| github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= | ||||
| github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= | ||||
| @ -878,8 +891,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P | ||||
| github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | ||||
| github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | ||||
| github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||
| github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= | ||||
| github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= | ||||
| github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= | ||||
| github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= | ||||
| github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI= | ||||
| @ -937,37 +950,39 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= | ||||
| go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= | ||||
| go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= | ||||
| go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= | ||||
| go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= | ||||
| go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= | ||||
| go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= | ||||
| go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= | ||||
| go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= | ||||
| go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 h1:zG8GlgXCJQd5BU98C0hZnBbElszTmUgCNCfYneaDL0A= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0/go.mod h1:hOfBCz8kv/wuq73Mx2H2QnWokh/kHZxkh6SNF2bdKtw= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= | ||||
| go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= | ||||
| go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= | ||||
| go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= | ||||
| go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= | ||||
| go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= | ||||
| go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= | ||||
| go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= | ||||
| go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= | ||||
| go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= | ||||
| go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= | ||||
| go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= | ||||
| go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= | ||||
| go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= | ||||
| go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= | ||||
| go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= | ||||
| go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= | ||||
| go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= | ||||
| go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= | ||||
| go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= | ||||
| go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= | ||||
| go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= | ||||
| go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= | ||||
| go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= | ||||
| go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= | ||||
| go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= | ||||
| go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE= | ||||
| go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0= | ||||
| go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= | ||||
| go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= | ||||
| go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= | ||||
| go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= | ||||
| go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= | ||||
| go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= | ||||
| go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= | ||||
| go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= | ||||
| golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||||
| golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||||
| golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||||
| @ -986,8 +1001,8 @@ golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWP | ||||
| golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= | ||||
| golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||
| golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||||
| golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= | ||||
| golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= | ||||
| golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= | ||||
| golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= | ||||
| golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||
| golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||
| golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= | ||||
| @ -998,8 +1013,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 | ||||
| golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= | ||||
| golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= | ||||
| golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= | ||||
| golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE= | ||||
| golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= | ||||
| golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= | ||||
| golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= | ||||
| golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= | ||||
| golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | ||||
| golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | ||||
| @ -1063,8 +1078,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b | ||||
| golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||||
| golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||||
| golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | ||||
| golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= | ||||
| golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= | ||||
| golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= | ||||
| golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= | ||||
| golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||||
| golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||
| golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||
| @ -1082,8 +1097,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ | ||||
| golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= | ||||
| golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= | ||||
| golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| @ -1162,13 +1175,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc | ||||
| golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= | ||||
| golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= | ||||
| golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= | ||||
| golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= | ||||
| golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= | ||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||
| golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||||
| golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= | ||||
| golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= | ||||
| golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= | ||||
| golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= | ||||
| golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| @ -1178,16 +1191,16 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||||
| golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||||
| golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= | ||||
| golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= | ||||
| golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= | ||||
| golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= | ||||
| golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= | ||||
| golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= | ||||
| golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= | ||||
| golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= | ||||
| golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| @ -1237,6 +1250,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T | ||||
| golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= | ||||
| gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= | ||||
| google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= | ||||
| google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= | ||||
| google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= | ||||
| @ -1281,10 +1296,10 @@ google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfG | ||||
| google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= | ||||
| google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= | ||||
| google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||||
| google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a h1:DMCgtIAIQGZqJXMVzJF4MV8BlWoJh2ZuFiRdAleyr58= | ||||
| google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a/go.mod h1:y2yVLIE/CSMCPXaHnSKXxu1spLPnglFLegmgdY23uuE= | ||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc= | ||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= | ||||
| google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU= | ||||
| google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk= | ||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 h1:i8QOKZfYg6AbGVZzUAY3LrNWCKF8O6zFisU9Wl9RER4= | ||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= | ||||
| google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= | ||||
| google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= | ||||
| google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= | ||||
| @ -1304,8 +1319,8 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp | ||||
| google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= | ||||
| google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= | ||||
| google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= | ||||
| google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= | ||||
| google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= | ||||
| google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= | ||||
| google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= | ||||
| google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= | ||||
| google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= | ||||
| google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= | ||||
| @ -1319,8 +1334,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba | ||||
| google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= | ||||
| google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | ||||
| google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | ||||
| google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= | ||||
| google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= | ||||
| google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= | ||||
| google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= | ||||
| gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= | ||||
| gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= | ||||
| gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU= | ||||
|  | ||||
							
								
								
									
										22
									
								
								vendor/github.com/atotto/clipboard/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/atotto/clipboard/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| language: go | ||||
|  | ||||
| os: | ||||
|  - linux | ||||
|  - osx | ||||
|  - windows | ||||
|  | ||||
| go: | ||||
|  - go1.13.x | ||||
|  - go1.x | ||||
|  | ||||
| services: | ||||
|  - xvfb | ||||
|  | ||||
| before_install: | ||||
|  - export DISPLAY=:99.0 | ||||
|  | ||||
| script: | ||||
|  - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get install xsel; fi | ||||
|  - go test -v . | ||||
|  - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get install xclip; fi | ||||
|  - go test -v . | ||||
							
								
								
									
										4
									
								
								vendor/golang.org/x/sync/LICENSE → vendor/github.com/atotto/clipboard/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/golang.org/x/sync/LICENSE → vendor/github.com/atotto/clipboard/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,4 +1,4 @@ | ||||
| Copyright 2009 The Go Authors. | ||||
| Copyright (c) 2013 Ato Araki. All rights reserved. | ||||
| 
 | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are | ||||
| @ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer. | ||||
| copyright notice, this list of conditions and the following disclaimer | ||||
| in the documentation and/or other materials provided with the | ||||
| distribution. | ||||
|    * Neither the name of Google LLC nor the names of its | ||||
|    * Neither the name of @atotto. nor the names of its | ||||
| contributors may be used to endorse or promote products derived from | ||||
| this software without specific prior written permission. | ||||
| 
 | ||||
							
								
								
									
										48
									
								
								vendor/github.com/atotto/clipboard/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								vendor/github.com/atotto/clipboard/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | ||||
| [](https://travis-ci.org/atotto/clipboard) | ||||
|  | ||||
| [](http://godoc.org/github.com/atotto/clipboard) | ||||
|  | ||||
| # Clipboard for Go | ||||
|  | ||||
| Provide copying and pasting to the Clipboard for Go. | ||||
|  | ||||
| Build: | ||||
|  | ||||
|     $ go get github.com/atotto/clipboard | ||||
|  | ||||
| Platforms: | ||||
|  | ||||
| * OSX | ||||
| * Windows 7 (probably work on other Windows) | ||||
| * Linux, Unix (requires 'xclip' or 'xsel' command to be installed) | ||||
|  | ||||
|  | ||||
| Document:  | ||||
|  | ||||
| * http://godoc.org/github.com/atotto/clipboard | ||||
|  | ||||
| Notes: | ||||
|  | ||||
| * Text string only | ||||
| * UTF-8 text encoding only (no conversion) | ||||
|  | ||||
| TODO: | ||||
|  | ||||
| * Clipboard watcher(?) | ||||
|  | ||||
| ## Commands: | ||||
|  | ||||
| paste shell command: | ||||
|  | ||||
|     $ go get github.com/atotto/clipboard/cmd/gopaste | ||||
|     $ # example: | ||||
|     $ gopaste > document.txt | ||||
|  | ||||
| copy shell command: | ||||
|  | ||||
|     $ go get github.com/atotto/clipboard/cmd/gocopy | ||||
|     $ # example: | ||||
|     $ cat document.txt | gocopy | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										20
									
								
								vendor/github.com/atotto/clipboard/clipboard.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/atotto/clipboard/clipboard.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| // Copyright 2013 @atotto. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // Package clipboard read/write on clipboard | ||||
| package clipboard | ||||
|  | ||||
| // ReadAll read string from clipboard | ||||
| func ReadAll() (string, error) { | ||||
| 	return readAll() | ||||
| } | ||||
|  | ||||
| // WriteAll write string to clipboard | ||||
| func WriteAll(text string) error { | ||||
| 	return writeAll(text) | ||||
| } | ||||
|  | ||||
| // Unsupported might be set true during clipboard init, to help callers decide | ||||
| // whether or not to offer clipboard options. | ||||
| var Unsupported bool | ||||
							
								
								
									
										52
									
								
								vendor/github.com/atotto/clipboard/clipboard_darwin.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								vendor/github.com/atotto/clipboard/clipboard_darwin.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | ||||
| // Copyright 2013 @atotto. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // +build darwin | ||||
|  | ||||
| package clipboard | ||||
|  | ||||
| import ( | ||||
| 	"os/exec" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	pasteCmdArgs = "pbpaste" | ||||
| 	copyCmdArgs  = "pbcopy" | ||||
| ) | ||||
|  | ||||
| func getPasteCommand() *exec.Cmd { | ||||
| 	return exec.Command(pasteCmdArgs) | ||||
| } | ||||
|  | ||||
| func getCopyCommand() *exec.Cmd { | ||||
| 	return exec.Command(copyCmdArgs) | ||||
| } | ||||
|  | ||||
| func readAll() (string, error) { | ||||
| 	pasteCmd := getPasteCommand() | ||||
| 	out, err := pasteCmd.Output() | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return string(out), nil | ||||
| } | ||||
|  | ||||
| func writeAll(text string) error { | ||||
| 	copyCmd := getCopyCommand() | ||||
| 	in, err := copyCmd.StdinPipe() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := copyCmd.Start(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if _, err := in.Write([]byte(text)); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := in.Close(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return copyCmd.Wait() | ||||
| } | ||||
							
								
								
									
										42
									
								
								vendor/github.com/atotto/clipboard/clipboard_plan9.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								vendor/github.com/atotto/clipboard/clipboard_plan9.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| // Copyright 2013 @atotto. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // +build plan9 | ||||
|  | ||||
| package clipboard | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
| 	"io/ioutil" | ||||
| ) | ||||
|  | ||||
| func readAll() (string, error) { | ||||
| 	f, err := os.Open("/dev/snarf") | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	defer f.Close() | ||||
|  | ||||
| 	str, err := ioutil.ReadAll(f) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	 | ||||
| 	return string(str), nil | ||||
| } | ||||
|  | ||||
| func writeAll(text string) error { | ||||
| 	f, err := os.OpenFile("/dev/snarf", os.O_WRONLY, 0666) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer f.Close() | ||||
| 	 | ||||
| 	_, err = f.Write([]byte(text)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	 | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										149
									
								
								vendor/github.com/atotto/clipboard/clipboard_unix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								vendor/github.com/atotto/clipboard/clipboard_unix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,149 @@ | ||||
| // Copyright 2013 @atotto. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // +build freebsd linux netbsd openbsd solaris dragonfly | ||||
|  | ||||
| package clipboard | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	xsel               = "xsel" | ||||
| 	xclip              = "xclip" | ||||
| 	powershellExe      = "powershell.exe" | ||||
| 	clipExe            = "clip.exe" | ||||
| 	wlcopy             = "wl-copy" | ||||
| 	wlpaste            = "wl-paste" | ||||
| 	termuxClipboardGet = "termux-clipboard-get" | ||||
| 	termuxClipboardSet = "termux-clipboard-set" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	Primary bool | ||||
| 	trimDos bool | ||||
|  | ||||
| 	pasteCmdArgs []string | ||||
| 	copyCmdArgs  []string | ||||
|  | ||||
| 	xselPasteArgs = []string{xsel, "--output", "--clipboard"} | ||||
| 	xselCopyArgs  = []string{xsel, "--input", "--clipboard"} | ||||
|  | ||||
| 	xclipPasteArgs = []string{xclip, "-out", "-selection", "clipboard"} | ||||
| 	xclipCopyArgs  = []string{xclip, "-in", "-selection", "clipboard"} | ||||
|  | ||||
| 	powershellExePasteArgs = []string{powershellExe, "Get-Clipboard"} | ||||
| 	clipExeCopyArgs        = []string{clipExe} | ||||
|  | ||||
| 	wlpasteArgs = []string{wlpaste, "--no-newline"} | ||||
| 	wlcopyArgs  = []string{wlcopy} | ||||
|  | ||||
| 	termuxPasteArgs = []string{termuxClipboardGet} | ||||
| 	termuxCopyArgs  = []string{termuxClipboardSet} | ||||
|  | ||||
| 	missingCommands = errors.New("No clipboard utilities available. Please install xsel, xclip, wl-clipboard or Termux:API add-on for termux-clipboard-get/set.") | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	if os.Getenv("WAYLAND_DISPLAY") != "" { | ||||
| 		pasteCmdArgs = wlpasteArgs | ||||
| 		copyCmdArgs = wlcopyArgs | ||||
|  | ||||
| 		if _, err := exec.LookPath(wlcopy); err == nil { | ||||
| 			if _, err := exec.LookPath(wlpaste); err == nil { | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	pasteCmdArgs = xclipPasteArgs | ||||
| 	copyCmdArgs = xclipCopyArgs | ||||
|  | ||||
| 	if _, err := exec.LookPath(xclip); err == nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	pasteCmdArgs = xselPasteArgs | ||||
| 	copyCmdArgs = xselCopyArgs | ||||
|  | ||||
| 	if _, err := exec.LookPath(xsel); err == nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	pasteCmdArgs = termuxPasteArgs | ||||
| 	copyCmdArgs = termuxCopyArgs | ||||
|  | ||||
| 	if _, err := exec.LookPath(termuxClipboardSet); err == nil { | ||||
| 		if _, err := exec.LookPath(termuxClipboardGet); err == nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	pasteCmdArgs = powershellExePasteArgs | ||||
| 	copyCmdArgs = clipExeCopyArgs | ||||
| 	trimDos = true | ||||
|  | ||||
| 	if _, err := exec.LookPath(clipExe); err == nil { | ||||
| 		if _, err := exec.LookPath(powershellExe); err == nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	Unsupported = true | ||||
| } | ||||
|  | ||||
| func getPasteCommand() *exec.Cmd { | ||||
| 	if Primary { | ||||
| 		pasteCmdArgs = pasteCmdArgs[:1] | ||||
| 	} | ||||
| 	return exec.Command(pasteCmdArgs[0], pasteCmdArgs[1:]...) | ||||
| } | ||||
|  | ||||
| func getCopyCommand() *exec.Cmd { | ||||
| 	if Primary { | ||||
| 		copyCmdArgs = copyCmdArgs[:1] | ||||
| 	} | ||||
| 	return exec.Command(copyCmdArgs[0], copyCmdArgs[1:]...) | ||||
| } | ||||
|  | ||||
| func readAll() (string, error) { | ||||
| 	if Unsupported { | ||||
| 		return "", missingCommands | ||||
| 	} | ||||
| 	pasteCmd := getPasteCommand() | ||||
| 	out, err := pasteCmd.Output() | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	result := string(out) | ||||
| 	if trimDos && len(result) > 1 { | ||||
| 		result = result[:len(result)-2] | ||||
| 	} | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| func writeAll(text string) error { | ||||
| 	if Unsupported { | ||||
| 		return missingCommands | ||||
| 	} | ||||
| 	copyCmd := getCopyCommand() | ||||
| 	in, err := copyCmd.StdinPipe() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := copyCmd.Start(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if _, err := in.Write([]byte(text)); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := in.Close(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return copyCmd.Wait() | ||||
| } | ||||
							
								
								
									
										157
									
								
								vendor/github.com/atotto/clipboard/clipboard_windows.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								vendor/github.com/atotto/clipboard/clipboard_windows.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,157 @@ | ||||
| // Copyright 2013 @atotto. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // +build windows | ||||
|  | ||||
| package clipboard | ||||
|  | ||||
| import ( | ||||
| 	"runtime" | ||||
| 	"syscall" | ||||
| 	"time" | ||||
| 	"unsafe" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	cfUnicodetext = 13 | ||||
| 	gmemMoveable  = 0x0002 | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	user32                     = syscall.MustLoadDLL("user32") | ||||
| 	isClipboardFormatAvailable = user32.MustFindProc("IsClipboardFormatAvailable") | ||||
| 	openClipboard              = user32.MustFindProc("OpenClipboard") | ||||
| 	closeClipboard             = user32.MustFindProc("CloseClipboard") | ||||
| 	emptyClipboard             = user32.MustFindProc("EmptyClipboard") | ||||
| 	getClipboardData           = user32.MustFindProc("GetClipboardData") | ||||
| 	setClipboardData           = user32.MustFindProc("SetClipboardData") | ||||
|  | ||||
| 	kernel32     = syscall.NewLazyDLL("kernel32") | ||||
| 	globalAlloc  = kernel32.NewProc("GlobalAlloc") | ||||
| 	globalFree   = kernel32.NewProc("GlobalFree") | ||||
| 	globalLock   = kernel32.NewProc("GlobalLock") | ||||
| 	globalUnlock = kernel32.NewProc("GlobalUnlock") | ||||
| 	lstrcpy      = kernel32.NewProc("lstrcpyW") | ||||
| ) | ||||
|  | ||||
| // waitOpenClipboard opens the clipboard, waiting for up to a second to do so. | ||||
| func waitOpenClipboard() error { | ||||
| 	started := time.Now() | ||||
| 	limit := started.Add(time.Second) | ||||
| 	var r uintptr | ||||
| 	var err error | ||||
| 	for time.Now().Before(limit) { | ||||
| 		r, _, err = openClipboard.Call(0) | ||||
| 		if r != 0 { | ||||
| 			return nil | ||||
| 		} | ||||
| 		time.Sleep(time.Millisecond) | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func readAll() (string, error) { | ||||
| 	// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution). | ||||
| 	// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock. | ||||
| 	runtime.LockOSThread() | ||||
| 	defer runtime.UnlockOSThread() | ||||
| 	if formatAvailable, _, err := isClipboardFormatAvailable.Call(cfUnicodetext); formatAvailable == 0 { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	err := waitOpenClipboard() | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	h, _, err := getClipboardData.Call(cfUnicodetext) | ||||
| 	if h == 0 { | ||||
| 		_, _, _ = closeClipboard.Call() | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	l, _, err := globalLock.Call(h) | ||||
| 	if l == 0 { | ||||
| 		_, _, _ = closeClipboard.Call() | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:]) | ||||
|  | ||||
| 	r, _, err := globalUnlock.Call(h) | ||||
| 	if r == 0 { | ||||
| 		_, _, _ = closeClipboard.Call() | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	closed, _, err := closeClipboard.Call() | ||||
| 	if closed == 0 { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return text, nil | ||||
| } | ||||
|  | ||||
| func writeAll(text string) error { | ||||
| 	// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution). | ||||
| 	// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock. | ||||
| 	runtime.LockOSThread() | ||||
| 	defer runtime.UnlockOSThread() | ||||
|  | ||||
| 	err := waitOpenClipboard() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	r, _, err := emptyClipboard.Call(0) | ||||
| 	if r == 0 { | ||||
| 		_, _, _ = closeClipboard.Call() | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	data := syscall.StringToUTF16(text) | ||||
|  | ||||
| 	// "If the hMem parameter identifies a memory object, the object must have | ||||
| 	// been allocated using the function with the GMEM_MOVEABLE flag." | ||||
| 	h, _, err := globalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0])))) | ||||
| 	if h == 0 { | ||||
| 		_, _, _ = closeClipboard.Call() | ||||
| 		return err | ||||
| 	} | ||||
| 	defer func() { | ||||
| 		if h != 0 { | ||||
| 			globalFree.Call(h) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	l, _, err := globalLock.Call(h) | ||||
| 	if l == 0 { | ||||
| 		_, _, _ = closeClipboard.Call() | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	r, _, err = lstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0]))) | ||||
| 	if r == 0 { | ||||
| 		_, _, _ = closeClipboard.Call() | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	r, _, err = globalUnlock.Call(h) | ||||
| 	if r == 0 { | ||||
| 		if err.(syscall.Errno) != 0 { | ||||
| 			_, _, _ = closeClipboard.Call() | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	r, _, err = setClipboardData.Call(cfUnicodetext, h) | ||||
| 	if r == 0 { | ||||
| 		_, _, _ = closeClipboard.Call() | ||||
| 		return err | ||||
| 	} | ||||
| 	h = 0 // suppress deferred cleanup | ||||
| 	closed, _, err := closeClipboard.Call() | ||||
| 	if closed == 0 { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										21
									
								
								vendor/github.com/charmbracelet/bubbles/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/charmbracelet/bubbles/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| MIT License | ||||
|  | ||||
| Copyright (c) 2020-2023 Charmbracelet, Inc | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
							
								
								
									
										219
									
								
								vendor/github.com/charmbracelet/bubbles/cursor/cursor.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								vendor/github.com/charmbracelet/bubbles/cursor/cursor.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,219 @@ | ||||
| // Package cursor provides cursor functionality for Bubble Tea applications. | ||||
| package cursor | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"time" | ||||
|  | ||||
| 	tea "github.com/charmbracelet/bubbletea" | ||||
| 	"github.com/charmbracelet/lipgloss" | ||||
| ) | ||||
|  | ||||
| const defaultBlinkSpeed = time.Millisecond * 530 | ||||
|  | ||||
| // initialBlinkMsg initializes cursor blinking. | ||||
| type initialBlinkMsg struct{} | ||||
|  | ||||
| // BlinkMsg signals that the cursor should blink. It contains metadata that | ||||
| // allows us to tell if the blink message is the one we're expecting. | ||||
| type BlinkMsg struct { | ||||
| 	id  int | ||||
| 	tag int | ||||
| } | ||||
|  | ||||
| // blinkCanceled is sent when a blink operation is canceled. | ||||
| type blinkCanceled struct{} | ||||
|  | ||||
| // blinkCtx manages cursor blinking. | ||||
| type blinkCtx struct { | ||||
| 	ctx    context.Context | ||||
| 	cancel context.CancelFunc | ||||
| } | ||||
|  | ||||
| // Mode describes the behavior of the cursor. | ||||
| type Mode int | ||||
|  | ||||
| // Available cursor modes. | ||||
| const ( | ||||
| 	CursorBlink Mode = iota | ||||
| 	CursorStatic | ||||
| 	CursorHide | ||||
| ) | ||||
|  | ||||
| // String returns the cursor mode in a human-readable format. This method is | ||||
| // provisional and for informational purposes only. | ||||
| func (c Mode) String() string { | ||||
| 	return [...]string{ | ||||
| 		"blink", | ||||
| 		"static", | ||||
| 		"hidden", | ||||
| 	}[c] | ||||
| } | ||||
|  | ||||
| // Model is the Bubble Tea model for this cursor element. | ||||
| type Model struct { | ||||
| 	BlinkSpeed time.Duration | ||||
| 	// Style for styling the cursor block. | ||||
| 	Style lipgloss.Style | ||||
| 	// TextStyle is the style used for the cursor when it is hidden (when blinking). | ||||
| 	// I.e. displaying normal text. | ||||
| 	TextStyle lipgloss.Style | ||||
|  | ||||
| 	// char is the character under the cursor | ||||
| 	char string | ||||
| 	// The ID of this Model as it relates to other cursors | ||||
| 	id int | ||||
| 	// focus indicates whether the containing input is focused | ||||
| 	focus bool | ||||
| 	// Cursor Blink state. | ||||
| 	Blink bool | ||||
| 	// Used to manage cursor blink | ||||
| 	blinkCtx *blinkCtx | ||||
| 	// The ID of the blink message we're expecting to receive. | ||||
| 	blinkTag int | ||||
| 	// mode determines the behavior of the cursor | ||||
| 	mode Mode | ||||
| } | ||||
|  | ||||
| // New creates a new model with default settings. | ||||
| func New() Model { | ||||
| 	return Model{ | ||||
| 		BlinkSpeed: defaultBlinkSpeed, | ||||
|  | ||||
| 		Blink: true, | ||||
| 		mode:  CursorBlink, | ||||
|  | ||||
| 		blinkCtx: &blinkCtx{ | ||||
| 			ctx: context.Background(), | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Update updates the cursor. | ||||
| func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { | ||||
| 	switch msg := msg.(type) { | ||||
| 	case initialBlinkMsg: | ||||
| 		// We accept all initialBlinkMsgs generated by the Blink command. | ||||
|  | ||||
| 		if m.mode != CursorBlink || !m.focus { | ||||
| 			return m, nil | ||||
| 		} | ||||
|  | ||||
| 		cmd := m.BlinkCmd() | ||||
| 		return m, cmd | ||||
|  | ||||
| 	case tea.FocusMsg: | ||||
| 		return m, m.Focus() | ||||
|  | ||||
| 	case tea.BlurMsg: | ||||
| 		m.Blur() | ||||
| 		return m, nil | ||||
|  | ||||
| 	case BlinkMsg: | ||||
| 		// We're choosy about whether to accept blinkMsgs so that our cursor | ||||
| 		// only exactly when it should. | ||||
|  | ||||
| 		// Is this model blink-able? | ||||
| 		if m.mode != CursorBlink || !m.focus { | ||||
| 			return m, nil | ||||
| 		} | ||||
|  | ||||
| 		// Were we expecting this blink message? | ||||
| 		if msg.id != m.id || msg.tag != m.blinkTag { | ||||
| 			return m, nil | ||||
| 		} | ||||
|  | ||||
| 		var cmd tea.Cmd | ||||
| 		if m.mode == CursorBlink { | ||||
| 			m.Blink = !m.Blink | ||||
| 			cmd = m.BlinkCmd() | ||||
| 		} | ||||
| 		return m, cmd | ||||
|  | ||||
| 	case blinkCanceled: // no-op | ||||
| 		return m, nil | ||||
| 	} | ||||
| 	return m, nil | ||||
| } | ||||
|  | ||||
| // Mode returns the model's cursor mode. For available cursor modes, see | ||||
| // type Mode. | ||||
| func (m Model) Mode() Mode { | ||||
| 	return m.mode | ||||
| } | ||||
|  | ||||
| // SetMode sets the model's cursor mode. This method returns a command. | ||||
| // | ||||
| // For available cursor modes, see type CursorMode. | ||||
| func (m *Model) SetMode(mode Mode) tea.Cmd { | ||||
| 	// Adjust the mode value if it's value is out of range | ||||
| 	if mode < CursorBlink || mode > CursorHide { | ||||
| 		return nil | ||||
| 	} | ||||
| 	m.mode = mode | ||||
| 	m.Blink = m.mode == CursorHide || !m.focus | ||||
| 	if mode == CursorBlink { | ||||
| 		return Blink | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // BlinkCmd is a command used to manage cursor blinking. | ||||
| func (m *Model) BlinkCmd() tea.Cmd { | ||||
| 	if m.mode != CursorBlink { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	if m.blinkCtx != nil && m.blinkCtx.cancel != nil { | ||||
| 		m.blinkCtx.cancel() | ||||
| 	} | ||||
|  | ||||
| 	ctx, cancel := context.WithTimeout(m.blinkCtx.ctx, m.BlinkSpeed) | ||||
| 	m.blinkCtx.cancel = cancel | ||||
|  | ||||
| 	m.blinkTag++ | ||||
|  | ||||
| 	return func() tea.Msg { | ||||
| 		defer cancel() | ||||
| 		<-ctx.Done() | ||||
| 		if ctx.Err() == context.DeadlineExceeded { | ||||
| 			return BlinkMsg{id: m.id, tag: m.blinkTag} | ||||
| 		} | ||||
| 		return blinkCanceled{} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Blink is a command used to initialize cursor blinking. | ||||
| func Blink() tea.Msg { | ||||
| 	return initialBlinkMsg{} | ||||
| } | ||||
|  | ||||
| // Focus focuses the cursor to allow it to blink if desired. | ||||
| func (m *Model) Focus() tea.Cmd { | ||||
| 	m.focus = true | ||||
| 	m.Blink = m.mode == CursorHide // show the cursor unless we've explicitly hidden it | ||||
|  | ||||
| 	if m.mode == CursorBlink && m.focus { | ||||
| 		return m.BlinkCmd() | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Blur blurs the cursor. | ||||
| func (m *Model) Blur() { | ||||
| 	m.focus = false | ||||
| 	m.Blink = true | ||||
| } | ||||
|  | ||||
| // SetChar sets the character under the cursor. | ||||
| func (m *Model) SetChar(char string) { | ||||
| 	m.char = char | ||||
| } | ||||
|  | ||||
| // View displays the cursor. | ||||
| func (m Model) View() string { | ||||
| 	if m.Blink { | ||||
| 		return m.TextStyle.Inline(true).Render(m.char) | ||||
| 	} | ||||
| 	return m.Style.Inline(true).Reverse(true).Render(m.char) | ||||
| } | ||||
							
								
								
									
										140
									
								
								vendor/github.com/charmbracelet/bubbles/key/key.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								vendor/github.com/charmbracelet/bubbles/key/key.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,140 @@ | ||||
| // Package key provides some types and functions for generating user-definable | ||||
| // keymappings useful in Bubble Tea components. There are a few different ways | ||||
| // you can define a keymapping with this package. Here's one example: | ||||
| // | ||||
| //	type KeyMap struct { | ||||
| //	    Up key.Binding | ||||
| //	    Down key.Binding | ||||
| //	} | ||||
| // | ||||
| //	var DefaultKeyMap = KeyMap{ | ||||
| //	    Up: key.NewBinding( | ||||
| //	        key.WithKeys("k", "up"),        // actual keybindings | ||||
| //	        key.WithHelp("↑/k", "move up"), // corresponding help text | ||||
| //	    ), | ||||
| //	    Down: key.NewBinding( | ||||
| //	        key.WithKeys("j", "down"), | ||||
| //	        key.WithHelp("↓/j", "move down"), | ||||
| //	    ), | ||||
| //	} | ||||
| // | ||||
| //	func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { | ||||
| //	    switch msg := msg.(type) { | ||||
| //	    case tea.KeyMsg: | ||||
| //	        switch { | ||||
| //	        case key.Matches(msg, DefaultKeyMap.Up): | ||||
| //	            // The user pressed up | ||||
| //	        case key.Matches(msg, DefaultKeyMap.Down): | ||||
| //	            // The user pressed down | ||||
| //	        } | ||||
| //	    } | ||||
| // | ||||
| //	    // ... | ||||
| //	} | ||||
| // | ||||
| // The help information, which is not used in the example above, can be used | ||||
| // to render help text for keystrokes in your views. | ||||
| package key | ||||
|  | ||||
| import "fmt" | ||||
|  | ||||
| // Binding describes a set of keybindings and, optionally, their associated | ||||
| // help text. | ||||
| type Binding struct { | ||||
| 	keys     []string | ||||
| 	help     Help | ||||
| 	disabled bool | ||||
| } | ||||
|  | ||||
| // BindingOpt is an initialization option for a keybinding. It's used as an | ||||
| // argument to NewBinding. | ||||
| type BindingOpt func(*Binding) | ||||
|  | ||||
| // NewBinding returns a new keybinding from a set of BindingOpt options. | ||||
| func NewBinding(opts ...BindingOpt) Binding { | ||||
| 	b := &Binding{} | ||||
| 	for _, opt := range opts { | ||||
| 		opt(b) | ||||
| 	} | ||||
| 	return *b | ||||
| } | ||||
|  | ||||
| // WithKeys initializes a keybinding with the given keystrokes. | ||||
| func WithKeys(keys ...string) BindingOpt { | ||||
| 	return func(b *Binding) { | ||||
| 		b.keys = keys | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // WithHelp initializes a keybinding with the given help text. | ||||
| func WithHelp(key, desc string) BindingOpt { | ||||
| 	return func(b *Binding) { | ||||
| 		b.help = Help{Key: key, Desc: desc} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // WithDisabled initializes a disabled keybinding. | ||||
| func WithDisabled() BindingOpt { | ||||
| 	return func(b *Binding) { | ||||
| 		b.disabled = true | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // SetKeys sets the keys for the keybinding. | ||||
| func (b *Binding) SetKeys(keys ...string) { | ||||
| 	b.keys = keys | ||||
| } | ||||
|  | ||||
| // Keys returns the keys for the keybinding. | ||||
| func (b Binding) Keys() []string { | ||||
| 	return b.keys | ||||
| } | ||||
|  | ||||
| // SetHelp sets the help text for the keybinding. | ||||
| func (b *Binding) SetHelp(key, desc string) { | ||||
| 	b.help = Help{Key: key, Desc: desc} | ||||
| } | ||||
|  | ||||
| // Help returns the Help information for the keybinding. | ||||
| func (b Binding) Help() Help { | ||||
| 	return b.help | ||||
| } | ||||
|  | ||||
| // Enabled returns whether or not the keybinding is enabled. Disabled | ||||
| // keybindings won't be activated and won't show up in help. Keybindings are | ||||
| // enabled by default. | ||||
| func (b Binding) Enabled() bool { | ||||
| 	return !b.disabled && b.keys != nil | ||||
| } | ||||
|  | ||||
| // SetEnabled enables or disables the keybinding. | ||||
| func (b *Binding) SetEnabled(v bool) { | ||||
| 	b.disabled = !v | ||||
| } | ||||
|  | ||||
| // Unbind removes the keys and help from this binding, effectively nullifying | ||||
| // it. This is a step beyond disabling it, since applications can enable | ||||
| // or disable key bindings based on application state. | ||||
| func (b *Binding) Unbind() { | ||||
| 	b.keys = nil | ||||
| 	b.help = Help{} | ||||
| } | ||||
|  | ||||
| // Help is help information for a given keybinding. | ||||
| type Help struct { | ||||
| 	Key  string | ||||
| 	Desc string | ||||
| } | ||||
|  | ||||
| // Matches checks if the given key matches the given bindings. | ||||
| func Matches[Key fmt.Stringer](k Key, b ...Binding) bool { | ||||
| 	keys := k.String() | ||||
| 	for _, binding := range b { | ||||
| 		for _, v := range binding.keys { | ||||
| 			if keys == v && binding.Enabled() { | ||||
| 				return true | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
							
								
								
									
										102
									
								
								vendor/github.com/charmbracelet/bubbles/runeutil/runeutil.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								vendor/github.com/charmbracelet/bubbles/runeutil/runeutil.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,102 @@ | ||||
| // Package runeutil provides a utility function for use in Bubbles | ||||
| // that can process Key messages containing runes. | ||||
| package runeutil | ||||
|  | ||||
| import ( | ||||
| 	"unicode" | ||||
| 	"unicode/utf8" | ||||
| ) | ||||
|  | ||||
| // Sanitizer is a helper for bubble widgets that want to process | ||||
| // Runes from input key messages. | ||||
| type Sanitizer interface { | ||||
| 	// Sanitize removes control characters from runes in a KeyRunes | ||||
| 	// message, and optionally replaces newline/carriage return/tabs by a | ||||
| 	// specified character. | ||||
| 	// | ||||
| 	// The rune array is modified in-place if possible. In that case, the | ||||
| 	// returned slice is the original slice shortened after the control | ||||
| 	// characters have been removed/translated. | ||||
| 	Sanitize(runes []rune) []rune | ||||
| } | ||||
|  | ||||
| // NewSanitizer constructs a rune sanitizer. | ||||
| func NewSanitizer(opts ...Option) Sanitizer { | ||||
| 	s := sanitizer{ | ||||
| 		replaceNewLine: []rune("\n"), | ||||
| 		replaceTab:     []rune("    "), | ||||
| 	} | ||||
| 	for _, o := range opts { | ||||
| 		s = o(s) | ||||
| 	} | ||||
| 	return &s | ||||
| } | ||||
|  | ||||
| // Option is the type of option that can be passed to Sanitize(). | ||||
| type Option func(sanitizer) sanitizer | ||||
|  | ||||
| // ReplaceTabs replaces tabs by the specified string. | ||||
| func ReplaceTabs(tabRepl string) Option { | ||||
| 	return func(s sanitizer) sanitizer { | ||||
| 		s.replaceTab = []rune(tabRepl) | ||||
| 		return s | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ReplaceNewlines replaces newline characters by the specified string. | ||||
| func ReplaceNewlines(nlRepl string) Option { | ||||
| 	return func(s sanitizer) sanitizer { | ||||
| 		s.replaceNewLine = []rune(nlRepl) | ||||
| 		return s | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (s *sanitizer) Sanitize(runes []rune) []rune { | ||||
| 	// dstrunes are where we are storing the result. | ||||
| 	dstrunes := runes[:0:len(runes)] | ||||
| 	// copied indicates whether dstrunes is an alias of runes | ||||
| 	// or a copy. We need a copy when dst moves past src. | ||||
| 	// We use this as an optimization to avoid allocating | ||||
| 	// a new rune slice in the common case where the output | ||||
| 	// is smaller or equal to the input. | ||||
| 	copied := false | ||||
|  | ||||
| 	for src := 0; src < len(runes); src++ { | ||||
| 		r := runes[src] | ||||
| 		switch { | ||||
| 		case r == utf8.RuneError: | ||||
| 			// skip | ||||
|  | ||||
| 		case r == '\r' || r == '\n': | ||||
| 			if len(dstrunes)+len(s.replaceNewLine) > src && !copied { | ||||
| 				dst := len(dstrunes) | ||||
| 				dstrunes = make([]rune, dst, len(runes)+len(s.replaceNewLine)) | ||||
| 				copy(dstrunes, runes[:dst]) | ||||
| 				copied = true | ||||
| 			} | ||||
| 			dstrunes = append(dstrunes, s.replaceNewLine...) | ||||
|  | ||||
| 		case r == '\t': | ||||
| 			if len(dstrunes)+len(s.replaceTab) > src && !copied { | ||||
| 				dst := len(dstrunes) | ||||
| 				dstrunes = make([]rune, dst, len(runes)+len(s.replaceTab)) | ||||
| 				copy(dstrunes, runes[:dst]) | ||||
| 				copied = true | ||||
| 			} | ||||
| 			dstrunes = append(dstrunes, s.replaceTab...) | ||||
|  | ||||
| 		case unicode.IsControl(r): | ||||
| 			// Other control characters: skip. | ||||
|  | ||||
| 		default: | ||||
| 			// Keep the character. | ||||
| 			dstrunes = append(dstrunes, runes[src]) | ||||
| 		} | ||||
| 	} | ||||
| 	return dstrunes | ||||
| } | ||||
|  | ||||
| type sanitizer struct { | ||||
| 	replaceNewLine []rune | ||||
| 	replaceTab     []rune | ||||
| } | ||||
							
								
								
									
										224
									
								
								vendor/github.com/charmbracelet/bubbles/spinner/spinner.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								vendor/github.com/charmbracelet/bubbles/spinner/spinner.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,224 @@ | ||||
| // Package spinner provides a spinner component for Bubble Tea applications. | ||||
| package spinner | ||||
|  | ||||
| import ( | ||||
| 	"sync/atomic" | ||||
| 	"time" | ||||
|  | ||||
| 	tea "github.com/charmbracelet/bubbletea" | ||||
| 	"github.com/charmbracelet/lipgloss" | ||||
| ) | ||||
|  | ||||
| // Internal ID management. Used during animating to ensure that frame messages | ||||
| // are received only by spinner components that sent them. | ||||
| var lastID int64 | ||||
|  | ||||
| func nextID() int { | ||||
| 	return int(atomic.AddInt64(&lastID, 1)) | ||||
| } | ||||
|  | ||||
| // Spinner is a set of frames used in animating the spinner. | ||||
| type Spinner struct { | ||||
| 	Frames []string | ||||
| 	FPS    time.Duration | ||||
| } | ||||
|  | ||||
| // Some spinners to choose from. You could also make your own. | ||||
| var ( | ||||
| 	Line = Spinner{ | ||||
| 		Frames: []string{"|", "/", "-", "\\"}, | ||||
| 		FPS:    time.Second / 10, //nolint:mnd | ||||
| 	} | ||||
| 	Dot = Spinner{ | ||||
| 		Frames: []string{"⣾ ", "⣽ ", "⣻ ", "⢿ ", "⡿ ", "⣟ ", "⣯ ", "⣷ "}, | ||||
| 		FPS:    time.Second / 10, //nolint:mnd | ||||
| 	} | ||||
| 	MiniDot = Spinner{ | ||||
| 		Frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}, | ||||
| 		FPS:    time.Second / 12, //nolint:mnd | ||||
| 	} | ||||
| 	Jump = Spinner{ | ||||
| 		Frames: []string{"⢄", "⢂", "⢁", "⡁", "⡈", "⡐", "⡠"}, | ||||
| 		FPS:    time.Second / 10, //nolint:mnd | ||||
| 	} | ||||
| 	Pulse = Spinner{ | ||||
| 		Frames: []string{"█", "▓", "▒", "░"}, | ||||
| 		FPS:    time.Second / 8, //nolint:mnd | ||||
| 	} | ||||
| 	Points = Spinner{ | ||||
| 		Frames: []string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●"}, | ||||
| 		FPS:    time.Second / 7, //nolint:mnd | ||||
| 	} | ||||
| 	Globe = Spinner{ | ||||
| 		Frames: []string{"🌍", "🌎", "🌏"}, | ||||
| 		FPS:    time.Second / 4, //nolint:mnd | ||||
| 	} | ||||
| 	Moon = Spinner{ | ||||
| 		Frames: []string{"🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"}, | ||||
| 		FPS:    time.Second / 8, //nolint:mnd | ||||
| 	} | ||||
| 	Monkey = Spinner{ | ||||
| 		Frames: []string{"🙈", "🙉", "🙊"}, | ||||
| 		FPS:    time.Second / 3, //nolint:mnd | ||||
| 	} | ||||
| 	Meter = Spinner{ | ||||
| 		Frames: []string{ | ||||
| 			"▱▱▱", | ||||
| 			"▰▱▱", | ||||
| 			"▰▰▱", | ||||
| 			"▰▰▰", | ||||
| 			"▰▰▱", | ||||
| 			"▰▱▱", | ||||
| 			"▱▱▱", | ||||
| 		}, | ||||
| 		FPS: time.Second / 7, //nolint:mnd | ||||
| 	} | ||||
| 	Hamburger = Spinner{ | ||||
| 		Frames: []string{"☱", "☲", "☴", "☲"}, | ||||
| 		FPS:    time.Second / 3, //nolint:mnd | ||||
| 	} | ||||
| 	Ellipsis = Spinner{ | ||||
| 		Frames: []string{"", ".", "..", "..."}, | ||||
| 		FPS:    time.Second / 3, //nolint:mnd | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| // Model contains the state for the spinner. Use New to create new models | ||||
| // rather than using Model as a struct literal. | ||||
| type Model struct { | ||||
| 	// Spinner settings to use. See type Spinner. | ||||
| 	Spinner Spinner | ||||
|  | ||||
| 	// Style sets the styling for the spinner. Most of the time you'll just | ||||
| 	// want foreground and background coloring, and potentially some padding. | ||||
| 	// | ||||
| 	// For an introduction to styling with Lip Gloss see: | ||||
| 	// https://github.com/charmbracelet/lipgloss | ||||
| 	Style lipgloss.Style | ||||
|  | ||||
| 	frame int | ||||
| 	id    int | ||||
| 	tag   int | ||||
| } | ||||
|  | ||||
| // ID returns the spinner's unique ID. | ||||
| func (m Model) ID() int { | ||||
| 	return m.id | ||||
| } | ||||
|  | ||||
| // New returns a model with default values. | ||||
| func New(opts ...Option) Model { | ||||
| 	m := Model{ | ||||
| 		Spinner: Line, | ||||
| 		id:      nextID(), | ||||
| 	} | ||||
|  | ||||
| 	for _, opt := range opts { | ||||
| 		opt(&m) | ||||
| 	} | ||||
|  | ||||
| 	return m | ||||
| } | ||||
|  | ||||
| // NewModel returns a model with default values. | ||||
| // | ||||
| // Deprecated: use [New] instead. | ||||
| var NewModel = New | ||||
|  | ||||
| // TickMsg indicates that the timer has ticked and we should render a frame. | ||||
| type TickMsg struct { | ||||
| 	Time time.Time | ||||
| 	tag  int | ||||
| 	ID   int | ||||
| } | ||||
|  | ||||
| // Update is the Tea update function. | ||||
| func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { | ||||
| 	switch msg := msg.(type) { | ||||
| 	case TickMsg: | ||||
| 		// If an ID is set, and the ID doesn't belong to this spinner, reject | ||||
| 		// the message. | ||||
| 		if msg.ID > 0 && msg.ID != m.id { | ||||
| 			return m, nil | ||||
| 		} | ||||
|  | ||||
| 		// If a tag is set, and it's not the one we expect, reject the message. | ||||
| 		// This prevents the spinner from receiving too many messages and | ||||
| 		// thus spinning too fast. | ||||
| 		if msg.tag > 0 && msg.tag != m.tag { | ||||
| 			return m, nil | ||||
| 		} | ||||
|  | ||||
| 		m.frame++ | ||||
| 		if m.frame >= len(m.Spinner.Frames) { | ||||
| 			m.frame = 0 | ||||
| 		} | ||||
|  | ||||
| 		m.tag++ | ||||
| 		return m, m.tick(m.id, m.tag) | ||||
| 	default: | ||||
| 		return m, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // View renders the model's view. | ||||
| func (m Model) View() string { | ||||
| 	if m.frame >= len(m.Spinner.Frames) { | ||||
| 		return "(error)" | ||||
| 	} | ||||
|  | ||||
| 	return m.Style.Render(m.Spinner.Frames[m.frame]) | ||||
| } | ||||
|  | ||||
| // Tick is the command used to advance the spinner one frame. Use this command | ||||
| // to effectively start the spinner. | ||||
| func (m Model) Tick() tea.Msg { | ||||
| 	return TickMsg{ | ||||
| 		// The time at which the tick occurred. | ||||
| 		Time: time.Now(), | ||||
|  | ||||
| 		// The ID of the spinner that this message belongs to. This can be | ||||
| 		// helpful when routing messages, however bear in mind that spinners | ||||
| 		// will ignore messages that don't contain ID by default. | ||||
| 		ID: m.id, | ||||
|  | ||||
| 		tag: m.tag, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (m Model) tick(id, tag int) tea.Cmd { | ||||
| 	return tea.Tick(m.Spinner.FPS, func(t time.Time) tea.Msg { | ||||
| 		return TickMsg{ | ||||
| 			Time: t, | ||||
| 			ID:   id, | ||||
| 			tag:  tag, | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // Tick is the command used to advance the spinner one frame. Use this command | ||||
| // to effectively start the spinner. | ||||
| // | ||||
| // Deprecated: Use [Model.Tick] instead. | ||||
| func Tick() tea.Msg { | ||||
| 	return TickMsg{Time: time.Now()} | ||||
| } | ||||
|  | ||||
| // Option is used to set options in New. For example: | ||||
| // | ||||
| //	spinner := New(WithSpinner(Dot)) | ||||
| type Option func(*Model) | ||||
|  | ||||
| // WithSpinner is an option to set the spinner. | ||||
| func WithSpinner(spinner Spinner) Option { | ||||
| 	return func(m *Model) { | ||||
| 		m.Spinner = spinner | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // WithStyle is an option to set the spinner style. | ||||
| func WithStyle(style lipgloss.Style) Option { | ||||
| 	return func(m *Model) { | ||||
| 		m.Style = style | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										898
									
								
								vendor/github.com/charmbracelet/bubbles/textinput/textinput.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										898
									
								
								vendor/github.com/charmbracelet/bubbles/textinput/textinput.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,898 @@ | ||||
| // Package textinput provides a text input component for Bubble Tea | ||||
| // applications. | ||||
| package textinput | ||||
|  | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 	"unicode" | ||||
|  | ||||
| 	"github.com/atotto/clipboard" | ||||
| 	"github.com/charmbracelet/bubbles/cursor" | ||||
| 	"github.com/charmbracelet/bubbles/key" | ||||
| 	"github.com/charmbracelet/bubbles/runeutil" | ||||
| 	tea "github.com/charmbracelet/bubbletea" | ||||
| 	"github.com/charmbracelet/lipgloss" | ||||
| 	rw "github.com/mattn/go-runewidth" | ||||
| 	"github.com/rivo/uniseg" | ||||
| ) | ||||
|  | ||||
| // Internal messages for clipboard operations. | ||||
| type ( | ||||
| 	pasteMsg    string | ||||
| 	pasteErrMsg struct{ error } | ||||
| ) | ||||
|  | ||||
| // EchoMode sets the input behavior of the text input field. | ||||
| type EchoMode int | ||||
|  | ||||
| const ( | ||||
| 	// EchoNormal displays text as is. This is the default behavior. | ||||
| 	EchoNormal EchoMode = iota | ||||
|  | ||||
| 	// EchoPassword displays the EchoCharacter mask instead of actual | ||||
| 	// characters. This is commonly used for password fields. | ||||
| 	EchoPassword | ||||
|  | ||||
| 	// EchoNone displays nothing as characters are entered. This is commonly | ||||
| 	// seen for password fields on the command line. | ||||
| 	EchoNone | ||||
| ) | ||||
|  | ||||
| // ValidateFunc is a function that returns an error if the input is invalid. | ||||
| type ValidateFunc func(string) error | ||||
|  | ||||
| // KeyMap is the key bindings for different actions within the textinput. | ||||
| type KeyMap struct { | ||||
| 	CharacterForward        key.Binding | ||||
| 	CharacterBackward       key.Binding | ||||
| 	WordForward             key.Binding | ||||
| 	WordBackward            key.Binding | ||||
| 	DeleteWordBackward      key.Binding | ||||
| 	DeleteWordForward       key.Binding | ||||
| 	DeleteAfterCursor       key.Binding | ||||
| 	DeleteBeforeCursor      key.Binding | ||||
| 	DeleteCharacterBackward key.Binding | ||||
| 	DeleteCharacterForward  key.Binding | ||||
| 	LineStart               key.Binding | ||||
| 	LineEnd                 key.Binding | ||||
| 	Paste                   key.Binding | ||||
| 	AcceptSuggestion        key.Binding | ||||
| 	NextSuggestion          key.Binding | ||||
| 	PrevSuggestion          key.Binding | ||||
| } | ||||
|  | ||||
| // DefaultKeyMap is the default set of key bindings for navigating and acting | ||||
| // upon the textinput. | ||||
| var DefaultKeyMap = KeyMap{ | ||||
| 	CharacterForward:        key.NewBinding(key.WithKeys("right", "ctrl+f")), | ||||
| 	CharacterBackward:       key.NewBinding(key.WithKeys("left", "ctrl+b")), | ||||
| 	WordForward:             key.NewBinding(key.WithKeys("alt+right", "ctrl+right", "alt+f")), | ||||
| 	WordBackward:            key.NewBinding(key.WithKeys("alt+left", "ctrl+left", "alt+b")), | ||||
| 	DeleteWordBackward:      key.NewBinding(key.WithKeys("alt+backspace", "ctrl+w")), | ||||
| 	DeleteWordForward:       key.NewBinding(key.WithKeys("alt+delete", "alt+d")), | ||||
| 	DeleteAfterCursor:       key.NewBinding(key.WithKeys("ctrl+k")), | ||||
| 	DeleteBeforeCursor:      key.NewBinding(key.WithKeys("ctrl+u")), | ||||
| 	DeleteCharacterBackward: key.NewBinding(key.WithKeys("backspace", "ctrl+h")), | ||||
| 	DeleteCharacterForward:  key.NewBinding(key.WithKeys("delete", "ctrl+d")), | ||||
| 	LineStart:               key.NewBinding(key.WithKeys("home", "ctrl+a")), | ||||
| 	LineEnd:                 key.NewBinding(key.WithKeys("end", "ctrl+e")), | ||||
| 	Paste:                   key.NewBinding(key.WithKeys("ctrl+v")), | ||||
| 	AcceptSuggestion:        key.NewBinding(key.WithKeys("tab")), | ||||
| 	NextSuggestion:          key.NewBinding(key.WithKeys("down", "ctrl+n")), | ||||
| 	PrevSuggestion:          key.NewBinding(key.WithKeys("up", "ctrl+p")), | ||||
| } | ||||
|  | ||||
| // Model is the Bubble Tea model for this text input element. | ||||
| type Model struct { | ||||
| 	Err error | ||||
|  | ||||
| 	// General settings. | ||||
| 	Prompt        string | ||||
| 	Placeholder   string | ||||
| 	EchoMode      EchoMode | ||||
| 	EchoCharacter rune | ||||
| 	Cursor        cursor.Model | ||||
|  | ||||
| 	// Deprecated: use [cursor.BlinkSpeed] instead. | ||||
| 	BlinkSpeed time.Duration | ||||
|  | ||||
| 	// Styles. These will be applied as inline styles. | ||||
| 	// | ||||
| 	// For an introduction to styling with Lip Gloss see: | ||||
| 	// https://github.com/charmbracelet/lipgloss | ||||
| 	PromptStyle      lipgloss.Style | ||||
| 	TextStyle        lipgloss.Style | ||||
| 	PlaceholderStyle lipgloss.Style | ||||
| 	CompletionStyle  lipgloss.Style | ||||
|  | ||||
| 	// Deprecated: use Cursor.Style instead. | ||||
| 	CursorStyle lipgloss.Style | ||||
|  | ||||
| 	// CharLimit is the maximum amount of characters this input element will | ||||
| 	// accept. If 0 or less, there's no limit. | ||||
| 	CharLimit int | ||||
|  | ||||
| 	// Width is the maximum number of characters that can be displayed at once. | ||||
| 	// It essentially treats the text field like a horizontally scrolling | ||||
| 	// viewport. If 0 or less this setting is ignored. | ||||
| 	Width int | ||||
|  | ||||
| 	// KeyMap encodes the keybindings recognized by the widget. | ||||
| 	KeyMap KeyMap | ||||
|  | ||||
| 	// Underlying text value. | ||||
| 	value []rune | ||||
|  | ||||
| 	// focus indicates whether user input focus should be on this input | ||||
| 	// component. When false, ignore keyboard input and hide the cursor. | ||||
| 	focus bool | ||||
|  | ||||
| 	// Cursor position. | ||||
| 	pos int | ||||
|  | ||||
| 	// Used to emulate a viewport when width is set and the content is | ||||
| 	// overflowing. | ||||
| 	offset      int | ||||
| 	offsetRight int | ||||
|  | ||||
| 	// Validate is a function that checks whether or not the text within the | ||||
| 	// input is valid. If it is not valid, the `Err` field will be set to the | ||||
| 	// error returned by the function. If the function is not defined, all | ||||
| 	// input is considered valid. | ||||
| 	Validate ValidateFunc | ||||
|  | ||||
| 	// rune sanitizer for input. | ||||
| 	rsan runeutil.Sanitizer | ||||
|  | ||||
| 	// Should the input suggest to complete | ||||
| 	ShowSuggestions bool | ||||
|  | ||||
| 	// suggestions is a list of suggestions that may be used to complete the | ||||
| 	// input. | ||||
| 	suggestions            [][]rune | ||||
| 	matchedSuggestions     [][]rune | ||||
| 	currentSuggestionIndex int | ||||
| } | ||||
|  | ||||
| // New creates a new model with default settings. | ||||
| func New() Model { | ||||
| 	return Model{ | ||||
| 		Prompt:           "> ", | ||||
| 		EchoCharacter:    '*', | ||||
| 		CharLimit:        0, | ||||
| 		PlaceholderStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("240")), | ||||
| 		ShowSuggestions:  false, | ||||
| 		CompletionStyle:  lipgloss.NewStyle().Foreground(lipgloss.Color("240")), | ||||
| 		Cursor:           cursor.New(), | ||||
| 		KeyMap:           DefaultKeyMap, | ||||
|  | ||||
| 		suggestions: [][]rune{}, | ||||
| 		value:       nil, | ||||
| 		focus:       false, | ||||
| 		pos:         0, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewModel creates a new model with default settings. | ||||
| // | ||||
| // Deprecated: Use [New] instead. | ||||
| var NewModel = New | ||||
|  | ||||
| // SetValue sets the value of the text input. | ||||
| func (m *Model) SetValue(s string) { | ||||
| 	// Clean up any special characters in the input provided by the | ||||
| 	// caller. This avoids bugs due to e.g. tab characters and whatnot. | ||||
| 	runes := m.san().Sanitize([]rune(s)) | ||||
| 	err := m.validate(runes) | ||||
| 	m.setValueInternal(runes, err) | ||||
| } | ||||
|  | ||||
| func (m *Model) setValueInternal(runes []rune, err error) { | ||||
| 	m.Err = err | ||||
|  | ||||
| 	empty := len(m.value) == 0 | ||||
|  | ||||
| 	if m.CharLimit > 0 && len(runes) > m.CharLimit { | ||||
| 		m.value = runes[:m.CharLimit] | ||||
| 	} else { | ||||
| 		m.value = runes | ||||
| 	} | ||||
| 	if (m.pos == 0 && empty) || m.pos > len(m.value) { | ||||
| 		m.SetCursor(len(m.value)) | ||||
| 	} | ||||
| 	m.handleOverflow() | ||||
| } | ||||
|  | ||||
| // Value returns the value of the text input. | ||||
| func (m Model) Value() string { | ||||
| 	return string(m.value) | ||||
| } | ||||
|  | ||||
| // Position returns the cursor position. | ||||
| func (m Model) Position() int { | ||||
| 	return m.pos | ||||
| } | ||||
|  | ||||
| // SetCursor moves the cursor to the given position. If the position is | ||||
| // out of bounds the cursor will be moved to the start or end accordingly. | ||||
| func (m *Model) SetCursor(pos int) { | ||||
| 	m.pos = clamp(pos, 0, len(m.value)) | ||||
| 	m.handleOverflow() | ||||
| } | ||||
|  | ||||
| // CursorStart moves the cursor to the start of the input field. | ||||
| func (m *Model) CursorStart() { | ||||
| 	m.SetCursor(0) | ||||
| } | ||||
|  | ||||
| // CursorEnd moves the cursor to the end of the input field. | ||||
| func (m *Model) CursorEnd() { | ||||
| 	m.SetCursor(len(m.value)) | ||||
| } | ||||
|  | ||||
| // Focused returns the focus state on the model. | ||||
| func (m Model) Focused() bool { | ||||
| 	return m.focus | ||||
| } | ||||
|  | ||||
| // Focus sets the focus state on the model. When the model is in focus it can | ||||
| // receive keyboard input and the cursor will be shown. | ||||
| func (m *Model) Focus() tea.Cmd { | ||||
| 	m.focus = true | ||||
| 	return m.Cursor.Focus() | ||||
| } | ||||
|  | ||||
| // Blur removes the focus state on the model.  When the model is blurred it can | ||||
| // not receive keyboard input and the cursor will be hidden. | ||||
| func (m *Model) Blur() { | ||||
| 	m.focus = false | ||||
| 	m.Cursor.Blur() | ||||
| } | ||||
|  | ||||
| // Reset sets the input to its default state with no input. | ||||
| func (m *Model) Reset() { | ||||
| 	m.value = nil | ||||
| 	m.SetCursor(0) | ||||
| } | ||||
|  | ||||
| // SetSuggestions sets the suggestions for the input. | ||||
| func (m *Model) SetSuggestions(suggestions []string) { | ||||
| 	m.suggestions = make([][]rune, len(suggestions)) | ||||
| 	for i, s := range suggestions { | ||||
| 		m.suggestions[i] = []rune(s) | ||||
| 	} | ||||
|  | ||||
| 	m.updateSuggestions() | ||||
| } | ||||
|  | ||||
| // rsan initializes or retrieves the rune sanitizer. | ||||
| func (m *Model) san() runeutil.Sanitizer { | ||||
| 	if m.rsan == nil { | ||||
| 		// Textinput has all its input on a single line so collapse | ||||
| 		// newlines/tabs to single spaces. | ||||
| 		m.rsan = runeutil.NewSanitizer( | ||||
| 			runeutil.ReplaceTabs(" "), runeutil.ReplaceNewlines(" ")) | ||||
| 	} | ||||
| 	return m.rsan | ||||
| } | ||||
|  | ||||
| func (m *Model) insertRunesFromUserInput(v []rune) { | ||||
| 	// Clean up any special characters in the input provided by the | ||||
| 	// clipboard. This avoids bugs due to e.g. tab characters and | ||||
| 	// whatnot. | ||||
| 	paste := m.san().Sanitize(v) | ||||
|  | ||||
| 	var availSpace int | ||||
| 	if m.CharLimit > 0 { | ||||
| 		availSpace = m.CharLimit - len(m.value) | ||||
|  | ||||
| 		// If the char limit's been reached, cancel. | ||||
| 		if availSpace <= 0 { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// If there's not enough space to paste the whole thing cut the pasted | ||||
| 		// runes down so they'll fit. | ||||
| 		if availSpace < len(paste) { | ||||
| 			paste = paste[:availSpace] | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Stuff before and after the cursor | ||||
| 	head := m.value[:m.pos] | ||||
| 	tailSrc := m.value[m.pos:] | ||||
| 	tail := make([]rune, len(tailSrc)) | ||||
| 	copy(tail, tailSrc) | ||||
|  | ||||
| 	// Insert pasted runes | ||||
| 	for _, r := range paste { | ||||
| 		head = append(head, r) | ||||
| 		m.pos++ | ||||
| 		if m.CharLimit > 0 { | ||||
| 			availSpace-- | ||||
| 			if availSpace <= 0 { | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Put it all back together | ||||
| 	value := append(head, tail...) | ||||
| 	inputErr := m.validate(value) | ||||
| 	m.setValueInternal(value, inputErr) | ||||
| } | ||||
|  | ||||
| // If a max width is defined, perform some logic to treat the visible area | ||||
| // as a horizontally scrolling viewport. | ||||
| func (m *Model) handleOverflow() { | ||||
| 	if m.Width <= 0 || uniseg.StringWidth(string(m.value)) <= m.Width { | ||||
| 		m.offset = 0 | ||||
| 		m.offsetRight = len(m.value) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Correct right offset if we've deleted characters | ||||
| 	m.offsetRight = min(m.offsetRight, len(m.value)) | ||||
|  | ||||
| 	if m.pos < m.offset { | ||||
| 		m.offset = m.pos | ||||
|  | ||||
| 		w := 0 | ||||
| 		i := 0 | ||||
| 		runes := m.value[m.offset:] | ||||
|  | ||||
| 		for i < len(runes) && w <= m.Width { | ||||
| 			w += rw.RuneWidth(runes[i]) | ||||
| 			if w <= m.Width+1 { | ||||
| 				i++ | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		m.offsetRight = m.offset + i | ||||
| 	} else if m.pos >= m.offsetRight { | ||||
| 		m.offsetRight = m.pos | ||||
|  | ||||
| 		w := 0 | ||||
| 		runes := m.value[:m.offsetRight] | ||||
| 		i := len(runes) - 1 | ||||
|  | ||||
| 		for i > 0 && w < m.Width { | ||||
| 			w += rw.RuneWidth(runes[i]) | ||||
| 			if w <= m.Width { | ||||
| 				i-- | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		m.offset = m.offsetRight - (len(runes) - 1 - i) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // deleteBeforeCursor deletes all text before the cursor. | ||||
| func (m *Model) deleteBeforeCursor() { | ||||
| 	m.value = m.value[m.pos:] | ||||
| 	m.Err = m.validate(m.value) | ||||
| 	m.offset = 0 | ||||
| 	m.SetCursor(0) | ||||
| } | ||||
|  | ||||
| // deleteAfterCursor deletes all text after the cursor. If input is masked | ||||
| // delete everything after the cursor so as not to reveal word breaks in the | ||||
| // masked input. | ||||
| func (m *Model) deleteAfterCursor() { | ||||
| 	m.value = m.value[:m.pos] | ||||
| 	m.Err = m.validate(m.value) | ||||
| 	m.SetCursor(len(m.value)) | ||||
| } | ||||
|  | ||||
| // deleteWordBackward deletes the word left to the cursor. | ||||
| func (m *Model) deleteWordBackward() { | ||||
| 	if m.pos == 0 || len(m.value) == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if m.EchoMode != EchoNormal { | ||||
| 		m.deleteBeforeCursor() | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Linter note: it's critical that we acquire the initial cursor position | ||||
| 	// here prior to altering it via SetCursor() below. As such, moving this | ||||
| 	// call into the corresponding if clause does not apply here. | ||||
| 	oldPos := m.pos //nolint:ifshort | ||||
|  | ||||
| 	m.SetCursor(m.pos - 1) | ||||
| 	for unicode.IsSpace(m.value[m.pos]) { | ||||
| 		if m.pos <= 0 { | ||||
| 			break | ||||
| 		} | ||||
| 		// ignore series of whitespace before cursor | ||||
| 		m.SetCursor(m.pos - 1) | ||||
| 	} | ||||
|  | ||||
| 	for m.pos > 0 { | ||||
| 		if !unicode.IsSpace(m.value[m.pos]) { | ||||
| 			m.SetCursor(m.pos - 1) | ||||
| 		} else { | ||||
| 			if m.pos > 0 { | ||||
| 				// keep the previous space | ||||
| 				m.SetCursor(m.pos + 1) | ||||
| 			} | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if oldPos > len(m.value) { | ||||
| 		m.value = m.value[:m.pos] | ||||
| 	} else { | ||||
| 		m.value = append(m.value[:m.pos], m.value[oldPos:]...) | ||||
| 	} | ||||
| 	m.Err = m.validate(m.value) | ||||
| } | ||||
|  | ||||
| // deleteWordForward deletes the word right to the cursor. If input is masked | ||||
| // delete everything after the cursor so as not to reveal word breaks in the | ||||
| // masked input. | ||||
| func (m *Model) deleteWordForward() { | ||||
| 	if m.pos >= len(m.value) || len(m.value) == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if m.EchoMode != EchoNormal { | ||||
| 		m.deleteAfterCursor() | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	oldPos := m.pos | ||||
| 	m.SetCursor(m.pos + 1) | ||||
| 	for unicode.IsSpace(m.value[m.pos]) { | ||||
| 		// ignore series of whitespace after cursor | ||||
| 		m.SetCursor(m.pos + 1) | ||||
|  | ||||
| 		if m.pos >= len(m.value) { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for m.pos < len(m.value) { | ||||
| 		if !unicode.IsSpace(m.value[m.pos]) { | ||||
| 			m.SetCursor(m.pos + 1) | ||||
| 		} else { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if m.pos > len(m.value) { | ||||
| 		m.value = m.value[:oldPos] | ||||
| 	} else { | ||||
| 		m.value = append(m.value[:oldPos], m.value[m.pos:]...) | ||||
| 	} | ||||
| 	m.Err = m.validate(m.value) | ||||
|  | ||||
| 	m.SetCursor(oldPos) | ||||
| } | ||||
|  | ||||
| // wordBackward moves the cursor one word to the left. If input is masked, move | ||||
| // input to the start so as not to reveal word breaks in the masked input. | ||||
| func (m *Model) wordBackward() { | ||||
| 	if m.pos == 0 || len(m.value) == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if m.EchoMode != EchoNormal { | ||||
| 		m.CursorStart() | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	i := m.pos - 1 | ||||
| 	for i >= 0 { | ||||
| 		if unicode.IsSpace(m.value[i]) { | ||||
| 			m.SetCursor(m.pos - 1) | ||||
| 			i-- | ||||
| 		} else { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for i >= 0 { | ||||
| 		if !unicode.IsSpace(m.value[i]) { | ||||
| 			m.SetCursor(m.pos - 1) | ||||
| 			i-- | ||||
| 		} else { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // wordForward moves the cursor one word to the right. If the input is masked, | ||||
| // move input to the end so as not to reveal word breaks in the masked input. | ||||
| func (m *Model) wordForward() { | ||||
| 	if m.pos >= len(m.value) || len(m.value) == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if m.EchoMode != EchoNormal { | ||||
| 		m.CursorEnd() | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	i := m.pos | ||||
| 	for i < len(m.value) { | ||||
| 		if unicode.IsSpace(m.value[i]) { | ||||
| 			m.SetCursor(m.pos + 1) | ||||
| 			i++ | ||||
| 		} else { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for i < len(m.value) { | ||||
| 		if !unicode.IsSpace(m.value[i]) { | ||||
| 			m.SetCursor(m.pos + 1) | ||||
| 			i++ | ||||
| 		} else { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (m Model) echoTransform(v string) string { | ||||
| 	switch m.EchoMode { | ||||
| 	case EchoPassword: | ||||
| 		return strings.Repeat(string(m.EchoCharacter), uniseg.StringWidth(v)) | ||||
| 	case EchoNone: | ||||
| 		return "" | ||||
| 	case EchoNormal: | ||||
| 		return v | ||||
| 	default: | ||||
| 		return v | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Update is the Bubble Tea update loop. | ||||
| func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { | ||||
| 	if !m.focus { | ||||
| 		return m, nil | ||||
| 	} | ||||
|  | ||||
| 	// Need to check for completion before, because key is configurable and might be double assigned | ||||
| 	keyMsg, ok := msg.(tea.KeyMsg) | ||||
| 	if ok && key.Matches(keyMsg, m.KeyMap.AcceptSuggestion) { | ||||
| 		if m.canAcceptSuggestion() { | ||||
| 			m.value = append(m.value, m.matchedSuggestions[m.currentSuggestionIndex][len(m.value):]...) | ||||
| 			m.CursorEnd() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Let's remember where the position of the cursor currently is so that if | ||||
| 	// the cursor position changes, we can reset the blink. | ||||
| 	oldPos := m.pos | ||||
|  | ||||
| 	switch msg := msg.(type) { | ||||
| 	case tea.KeyMsg: | ||||
| 		switch { | ||||
| 		case key.Matches(msg, m.KeyMap.DeleteWordBackward): | ||||
| 			m.deleteWordBackward() | ||||
| 		case key.Matches(msg, m.KeyMap.DeleteCharacterBackward): | ||||
| 			m.Err = nil | ||||
| 			if len(m.value) > 0 { | ||||
| 				m.value = append(m.value[:max(0, m.pos-1)], m.value[m.pos:]...) | ||||
| 				m.Err = m.validate(m.value) | ||||
| 				if m.pos > 0 { | ||||
| 					m.SetCursor(m.pos - 1) | ||||
| 				} | ||||
| 			} | ||||
| 		case key.Matches(msg, m.KeyMap.WordBackward): | ||||
| 			m.wordBackward() | ||||
| 		case key.Matches(msg, m.KeyMap.CharacterBackward): | ||||
| 			if m.pos > 0 { | ||||
| 				m.SetCursor(m.pos - 1) | ||||
| 			} | ||||
| 		case key.Matches(msg, m.KeyMap.WordForward): | ||||
| 			m.wordForward() | ||||
| 		case key.Matches(msg, m.KeyMap.CharacterForward): | ||||
| 			if m.pos < len(m.value) { | ||||
| 				m.SetCursor(m.pos + 1) | ||||
| 			} | ||||
| 		case key.Matches(msg, m.KeyMap.LineStart): | ||||
| 			m.CursorStart() | ||||
| 		case key.Matches(msg, m.KeyMap.DeleteCharacterForward): | ||||
| 			if len(m.value) > 0 && m.pos < len(m.value) { | ||||
| 				m.value = append(m.value[:m.pos], m.value[m.pos+1:]...) | ||||
| 				m.Err = m.validate(m.value) | ||||
| 			} | ||||
| 		case key.Matches(msg, m.KeyMap.LineEnd): | ||||
| 			m.CursorEnd() | ||||
| 		case key.Matches(msg, m.KeyMap.DeleteAfterCursor): | ||||
| 			m.deleteAfterCursor() | ||||
| 		case key.Matches(msg, m.KeyMap.DeleteBeforeCursor): | ||||
| 			m.deleteBeforeCursor() | ||||
| 		case key.Matches(msg, m.KeyMap.Paste): | ||||
| 			return m, Paste | ||||
| 		case key.Matches(msg, m.KeyMap.DeleteWordForward): | ||||
| 			m.deleteWordForward() | ||||
| 		case key.Matches(msg, m.KeyMap.NextSuggestion): | ||||
| 			m.nextSuggestion() | ||||
| 		case key.Matches(msg, m.KeyMap.PrevSuggestion): | ||||
| 			m.previousSuggestion() | ||||
| 		default: | ||||
| 			// Input one or more regular characters. | ||||
| 			m.insertRunesFromUserInput(msg.Runes) | ||||
| 		} | ||||
|  | ||||
| 		// Check again if can be completed | ||||
| 		// because value might be something that does not match the completion prefix | ||||
| 		m.updateSuggestions() | ||||
|  | ||||
| 	case pasteMsg: | ||||
| 		m.insertRunesFromUserInput([]rune(msg)) | ||||
|  | ||||
| 	case pasteErrMsg: | ||||
| 		m.Err = msg | ||||
| 	} | ||||
|  | ||||
| 	var cmds []tea.Cmd | ||||
| 	var cmd tea.Cmd | ||||
|  | ||||
| 	m.Cursor, cmd = m.Cursor.Update(msg) | ||||
| 	cmds = append(cmds, cmd) | ||||
|  | ||||
| 	if oldPos != m.pos && m.Cursor.Mode() == cursor.CursorBlink { | ||||
| 		m.Cursor.Blink = false | ||||
| 		cmds = append(cmds, m.Cursor.BlinkCmd()) | ||||
| 	} | ||||
|  | ||||
| 	m.handleOverflow() | ||||
| 	return m, tea.Batch(cmds...) | ||||
| } | ||||
|  | ||||
| // View renders the textinput in its current state. | ||||
| func (m Model) View() string { | ||||
| 	// Placeholder text | ||||
| 	if len(m.value) == 0 && m.Placeholder != "" { | ||||
| 		return m.placeholderView() | ||||
| 	} | ||||
|  | ||||
| 	styleText := m.TextStyle.Inline(true).Render | ||||
|  | ||||
| 	value := m.value[m.offset:m.offsetRight] | ||||
| 	pos := max(0, m.pos-m.offset) | ||||
| 	v := styleText(m.echoTransform(string(value[:pos]))) | ||||
|  | ||||
| 	if pos < len(value) { //nolint:nestif | ||||
| 		char := m.echoTransform(string(value[pos])) | ||||
| 		m.Cursor.SetChar(char) | ||||
| 		v += m.Cursor.View()                                   // cursor and text under it | ||||
| 		v += styleText(m.echoTransform(string(value[pos+1:]))) // text after cursor | ||||
| 		v += m.completionView(0)                               // suggested completion | ||||
| 	} else { | ||||
| 		if m.focus && m.canAcceptSuggestion() { | ||||
| 			suggestion := m.matchedSuggestions[m.currentSuggestionIndex] | ||||
| 			if len(value) < len(suggestion) { | ||||
| 				m.Cursor.TextStyle = m.CompletionStyle | ||||
| 				m.Cursor.SetChar(m.echoTransform(string(suggestion[pos]))) | ||||
| 				v += m.Cursor.View() | ||||
| 				v += m.completionView(1) | ||||
| 			} else { | ||||
| 				m.Cursor.SetChar(" ") | ||||
| 				v += m.Cursor.View() | ||||
| 			} | ||||
| 		} else { | ||||
| 			m.Cursor.SetChar(" ") | ||||
| 			v += m.Cursor.View() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// If a max width and background color were set fill the empty spaces with | ||||
| 	// the background color. | ||||
| 	valWidth := uniseg.StringWidth(string(value)) | ||||
| 	if m.Width > 0 && valWidth <= m.Width { | ||||
| 		padding := max(0, m.Width-valWidth) | ||||
| 		if valWidth+padding <= m.Width && pos < len(value) { | ||||
| 			padding++ | ||||
| 		} | ||||
| 		v += styleText(strings.Repeat(" ", padding)) | ||||
| 	} | ||||
|  | ||||
| 	return m.PromptStyle.Render(m.Prompt) + v | ||||
| } | ||||
|  | ||||
| // placeholderView returns the prompt and placeholder view, if any. | ||||
| func (m Model) placeholderView() string { | ||||
| 	var ( | ||||
| 		v     string | ||||
| 		style = m.PlaceholderStyle.Inline(true).Render | ||||
| 	) | ||||
|  | ||||
| 	p := make([]rune, m.Width+1) | ||||
| 	copy(p, []rune(m.Placeholder)) | ||||
|  | ||||
| 	m.Cursor.TextStyle = m.PlaceholderStyle | ||||
| 	m.Cursor.SetChar(string(p[:1])) | ||||
| 	v += m.Cursor.View() | ||||
|  | ||||
| 	// If the entire placeholder is already set and no padding is needed, finish | ||||
| 	if m.Width < 1 && len(p) <= 1 { | ||||
| 		return m.PromptStyle.Render(m.Prompt) + v | ||||
| 	} | ||||
|  | ||||
| 	// If Width is set then size placeholder accordingly | ||||
| 	if m.Width > 0 { | ||||
| 		// available width is width - len + cursor offset of 1 | ||||
| 		minWidth := lipgloss.Width(m.Placeholder) | ||||
| 		availWidth := m.Width - minWidth + 1 | ||||
|  | ||||
| 		// if width < len, 'subtract'(add) number to len and dont add padding | ||||
| 		if availWidth < 0 { | ||||
| 			minWidth += availWidth | ||||
| 			availWidth = 0 | ||||
| 		} | ||||
| 		// append placeholder[len] - cursor, append padding | ||||
| 		v += style(string(p[1:minWidth])) | ||||
| 		v += style(strings.Repeat(" ", availWidth)) | ||||
| 	} else { | ||||
| 		// if there is no width, the placeholder can be any length | ||||
| 		v += style(string(p[1:])) | ||||
| 	} | ||||
|  | ||||
| 	return m.PromptStyle.Render(m.Prompt) + v | ||||
| } | ||||
|  | ||||
| // Blink is a command used to initialize cursor blinking. | ||||
| func Blink() tea.Msg { | ||||
| 	return cursor.Blink() | ||||
| } | ||||
|  | ||||
| // Paste is a command for pasting from the clipboard into the text input. | ||||
| func Paste() tea.Msg { | ||||
| 	str, err := clipboard.ReadAll() | ||||
| 	if err != nil { | ||||
| 		return pasteErrMsg{err} | ||||
| 	} | ||||
| 	return pasteMsg(str) | ||||
| } | ||||
|  | ||||
| func clamp(v, low, high int) int { | ||||
| 	if high < low { | ||||
| 		low, high = high, low | ||||
| 	} | ||||
| 	return min(high, max(low, v)) | ||||
| } | ||||
|  | ||||
| // Deprecated. | ||||
|  | ||||
| // Deprecated: use [cursor.Mode]. | ||||
| // | ||||
| //nolint:revive | ||||
| type CursorMode int | ||||
|  | ||||
| //nolint:revive | ||||
| const ( | ||||
| 	// Deprecated: use [cursor.CursorBlink]. | ||||
| 	CursorBlink = CursorMode(cursor.CursorBlink) | ||||
| 	// Deprecated: use [cursor.CursorStatic]. | ||||
| 	CursorStatic = CursorMode(cursor.CursorStatic) | ||||
| 	// Deprecated: use [cursor.CursorHide]. | ||||
| 	CursorHide = CursorMode(cursor.CursorHide) | ||||
| ) | ||||
|  | ||||
| func (c CursorMode) String() string { | ||||
| 	return cursor.Mode(c).String() | ||||
| } | ||||
|  | ||||
| // Deprecated: use [cursor.Mode]. | ||||
| // | ||||
| //nolint:revive | ||||
| func (m Model) CursorMode() CursorMode { | ||||
| 	return CursorMode(m.Cursor.Mode()) | ||||
| } | ||||
|  | ||||
| // Deprecated: use cursor.SetMode(). | ||||
| // | ||||
| //nolint:revive | ||||
| func (m *Model) SetCursorMode(mode CursorMode) tea.Cmd { | ||||
| 	return m.Cursor.SetMode(cursor.Mode(mode)) | ||||
| } | ||||
|  | ||||
| func (m Model) completionView(offset int) string { | ||||
| 	var ( | ||||
| 		value = m.value | ||||
| 		style = m.PlaceholderStyle.Inline(true).Render | ||||
| 	) | ||||
|  | ||||
| 	if m.canAcceptSuggestion() { | ||||
| 		suggestion := m.matchedSuggestions[m.currentSuggestionIndex] | ||||
| 		if len(value) < len(suggestion) { | ||||
| 			return style(string(suggestion[len(value)+offset:])) | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (m *Model) getSuggestions(sugs [][]rune) []string { | ||||
| 	suggestions := make([]string, len(sugs)) | ||||
| 	for i, s := range sugs { | ||||
| 		suggestions[i] = string(s) | ||||
| 	} | ||||
| 	return suggestions | ||||
| } | ||||
|  | ||||
| // AvailableSuggestions returns the list of available suggestions. | ||||
| func (m *Model) AvailableSuggestions() []string { | ||||
| 	return m.getSuggestions(m.suggestions) | ||||
| } | ||||
|  | ||||
| // MatchedSuggestions returns the list of matched suggestions. | ||||
| func (m *Model) MatchedSuggestions() []string { | ||||
| 	return m.getSuggestions(m.matchedSuggestions) | ||||
| } | ||||
|  | ||||
| // CurrentSuggestionIndex returns the currently selected suggestion index. | ||||
| func (m *Model) CurrentSuggestionIndex() int { | ||||
| 	return m.currentSuggestionIndex | ||||
| } | ||||
|  | ||||
| // CurrentSuggestion returns the currently selected suggestion. | ||||
| func (m *Model) CurrentSuggestion() string { | ||||
| 	if m.currentSuggestionIndex >= len(m.matchedSuggestions) { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return string(m.matchedSuggestions[m.currentSuggestionIndex]) | ||||
| } | ||||
|  | ||||
| // canAcceptSuggestion returns whether there is an acceptable suggestion to | ||||
| // autocomplete the current value. | ||||
| func (m *Model) canAcceptSuggestion() bool { | ||||
| 	return len(m.matchedSuggestions) > 0 | ||||
| } | ||||
|  | ||||
| // updateSuggestions refreshes the list of matching suggestions. | ||||
| func (m *Model) updateSuggestions() { | ||||
| 	if !m.ShowSuggestions { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if len(m.value) <= 0 || len(m.suggestions) <= 0 { | ||||
| 		m.matchedSuggestions = [][]rune{} | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	matches := [][]rune{} | ||||
| 	for _, s := range m.suggestions { | ||||
| 		suggestion := string(s) | ||||
|  | ||||
| 		if strings.HasPrefix(strings.ToLower(suggestion), strings.ToLower(string(m.value))) { | ||||
| 			matches = append(matches, []rune(suggestion)) | ||||
| 		} | ||||
| 	} | ||||
| 	if !reflect.DeepEqual(matches, m.matchedSuggestions) { | ||||
| 		m.currentSuggestionIndex = 0 | ||||
| 	} | ||||
|  | ||||
| 	m.matchedSuggestions = matches | ||||
| } | ||||
|  | ||||
| // nextSuggestion selects the next suggestion. | ||||
| func (m *Model) nextSuggestion() { | ||||
| 	m.currentSuggestionIndex = (m.currentSuggestionIndex + 1) | ||||
| 	if m.currentSuggestionIndex >= len(m.matchedSuggestions) { | ||||
| 		m.currentSuggestionIndex = 0 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // previousSuggestion selects the previous suggestion. | ||||
| func (m *Model) previousSuggestion() { | ||||
| 	m.currentSuggestionIndex = (m.currentSuggestionIndex - 1) | ||||
| 	if m.currentSuggestionIndex < 0 { | ||||
| 		m.currentSuggestionIndex = len(m.matchedSuggestions) - 1 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (m Model) validate(v []rune) error { | ||||
| 	if m.Validate != nil { | ||||
| 		return m.Validate(string(v)) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										4
									
								
								vendor/github.com/charmbracelet/bubbletea/.golangci.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/charmbracelet/bubbletea/.golangci.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -26,6 +26,10 @@ linters: | ||||
|     - whitespace | ||||
|     - wrapcheck | ||||
|   exclusions: | ||||
|     rules: | ||||
|       - text: '(slog|log)\.\w+' | ||||
|         linters: | ||||
|           - noctx | ||||
|     generated: lax | ||||
|     presets: | ||||
|       - common-false-positives | ||||
|  | ||||
							
								
								
									
										2
									
								
								vendor/github.com/charmbracelet/bubbletea/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/charmbracelet/bubbletea/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,6 +1,6 @@ | ||||
| MIT License | ||||
|  | ||||
| Copyright (c) 2020-2023 Charmbracelet, Inc | ||||
| Copyright (c) 2020-2025 Charmbracelet, Inc | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
|  | ||||
							
								
								
									
										4
									
								
								vendor/github.com/charmbracelet/bubbletea/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/charmbracelet/bubbletea/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -9,7 +9,7 @@ | ||||
|     <br> | ||||
|     <a href="https://github.com/charmbracelet/bubbletea/releases"><img src="https://img.shields.io/github/release/charmbracelet/bubbletea.svg" alt="Latest Release"></a> | ||||
|     <a href="https://pkg.go.dev/github.com/charmbracelet/bubbletea?tab=doc"><img src="https://godoc.org/github.com/charmbracelet/bubbletea?status.svg" alt="GoDoc"></a> | ||||
|     <a href="https://github.com/charmbracelet/bubbletea/actions"><img src="https://github.com/charmbracelet/bubbletea/actions/workflows/build.yml/badge.svg" alt="Build Status"></a> | ||||
|     <a href="https://github.com/charmbracelet/bubbletea/actions"><img src="https://github.com/charmbracelet/bubbletea/actions/workflows/build.yml/badge.svg?branch=main" alt="Build Status"></a> | ||||
| </p> | ||||
|  | ||||
| The fun, functional and stateful way to build terminal apps. A Go framework | ||||
| @ -395,6 +395,6 @@ of days past. | ||||
|  | ||||
| Part of [Charm](https://charm.sh). | ||||
|  | ||||
| <a href="https://charm.sh/"><img alt="The Charm logo" src="https://stuff.charm.sh/charm-badge.jpg" width="400"></a> | ||||
| <a href="https://charm.sh/"><img alt="The Charm logo" src="https://stuff.charm.sh/charm-banner-next.jpg" width="400"></a> | ||||
|  | ||||
| Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة | ||||
|  | ||||
							
								
								
									
										38
									
								
								vendor/github.com/charmbracelet/bubbletea/commands.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										38
									
								
								vendor/github.com/charmbracelet/bubbletea/commands.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -13,6 +13,27 @@ import ( | ||||
| //		       return tea.Batch(someCommand, someOtherCommand) | ||||
| //	    } | ||||
| func Batch(cmds ...Cmd) Cmd { | ||||
| 	return compactCmds[BatchMsg](cmds) | ||||
| } | ||||
|  | ||||
| // BatchMsg is a message used to perform a bunch of commands concurrently with | ||||
| // no ordering guarantees. You can send a BatchMsg with Batch. | ||||
| type BatchMsg []Cmd | ||||
|  | ||||
| // Sequence runs the given commands one at a time, in order. Contrast this with | ||||
| // Batch, which runs commands concurrently. | ||||
| func Sequence(cmds ...Cmd) Cmd { | ||||
| 	return compactCmds[sequenceMsg](cmds) | ||||
| } | ||||
|  | ||||
| // sequenceMsg is used internally to run the given commands in order. | ||||
| type sequenceMsg []Cmd | ||||
|  | ||||
| // compactCmds ignores any nil commands in cmds, and returns the most direct | ||||
| // command possible. That is, considering the non-nil commands, if there are | ||||
| // none it returns nil, if there is exactly one it returns that command | ||||
| // directly, else it returns the non-nil commands as type T. | ||||
| func compactCmds[T ~[]Cmd](cmds []Cmd) Cmd { | ||||
| 	var validCmds []Cmd //nolint:prealloc | ||||
| 	for _, c := range cmds { | ||||
| 		if c == nil { | ||||
| @ -27,26 +48,11 @@ func Batch(cmds ...Cmd) Cmd { | ||||
| 		return validCmds[0] | ||||
| 	default: | ||||
| 		return func() Msg { | ||||
| 			return BatchMsg(validCmds) | ||||
| 			return T(validCmds) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // BatchMsg is a message used to perform a bunch of commands concurrently with | ||||
| // no ordering guarantees. You can send a BatchMsg with Batch. | ||||
| type BatchMsg []Cmd | ||||
|  | ||||
| // Sequence runs the given commands one at a time, in order. Contrast this with | ||||
| // Batch, which runs commands concurrently. | ||||
| func Sequence(cmds ...Cmd) Cmd { | ||||
| 	return func() Msg { | ||||
| 		return sequenceMsg(cmds) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // sequenceMsg is used internally to run the given commands in order. | ||||
| type sequenceMsg []Cmd | ||||
|  | ||||
| // Every is a command that ticks in sync with the system clock. So, if you | ||||
| // wanted to tick with the system clock every second, minute or hour you | ||||
| // could use this. It's also handy for having different things tick in sync. | ||||
|  | ||||
							
								
								
									
										2
									
								
								vendor/github.com/charmbracelet/bubbletea/inputreader_windows.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/charmbracelet/bubbletea/inputreader_windows.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -108,7 +108,7 @@ func prepareConsole(input windows.Handle, modes ...uint32) (originalMode uint32, | ||||
| 	return originalMode, nil | ||||
| } | ||||
|  | ||||
| // cancelMixin represents a goroutine-safe cancelation status. | ||||
| // cancelMixin represents a goroutine-safe cancellation status. | ||||
| type cancelMixin struct { | ||||
| 	unsafeCanceled bool | ||||
| 	lock           sync.Mutex | ||||
|  | ||||
							
								
								
									
										16
									
								
								vendor/github.com/charmbracelet/bubbletea/key_windows.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								vendor/github.com/charmbracelet/bubbletea/key_windows.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -109,12 +109,12 @@ func peekAndReadConsInput(con *conInputReader) ([]coninput.InputRecord, error) { | ||||
| 	return events, nil | ||||
| } | ||||
|  | ||||
| // Convert i to unit32 or panic if it cannot be converted. Check satisifes lint G115. | ||||
| // Convert i to unit32 or panic if it cannot be converted. Check satisfies lint G115. | ||||
| func intToUint32OrDie(i int) uint32 { | ||||
| 	if i < 0 { | ||||
| 		panic("cannot convert numEvents " + fmt.Sprint(i) + " to uint32") | ||||
| 	} | ||||
| 	return uint32(i) | ||||
| 	return uint32(i) //nolint:gosec | ||||
| } | ||||
|  | ||||
| // Keeps peeking until there is data or the input is cancelled. | ||||
| @ -158,16 +158,16 @@ func mouseEventButton(p, s coninput.ButtonState) (button MouseButton, action Mou | ||||
| 		return button, action | ||||
| 	} | ||||
|  | ||||
| 	switch { | ||||
| 	case btn == coninput.FROM_LEFT_1ST_BUTTON_PRESSED: // left button | ||||
| 	switch btn { | ||||
| 	case coninput.FROM_LEFT_1ST_BUTTON_PRESSED: // left button | ||||
| 		button = MouseButtonLeft | ||||
| 	case btn == coninput.RIGHTMOST_BUTTON_PRESSED: // right button | ||||
| 	case coninput.RIGHTMOST_BUTTON_PRESSED: // right button | ||||
| 		button = MouseButtonRight | ||||
| 	case btn == coninput.FROM_LEFT_2ND_BUTTON_PRESSED: // middle button | ||||
| 	case coninput.FROM_LEFT_2ND_BUTTON_PRESSED: // middle button | ||||
| 		button = MouseButtonMiddle | ||||
| 	case btn == coninput.FROM_LEFT_3RD_BUTTON_PRESSED: // unknown (possibly mouse backward) | ||||
| 	case coninput.FROM_LEFT_3RD_BUTTON_PRESSED: // unknown (possibly mouse backward) | ||||
| 		button = MouseButtonBackward | ||||
| 	case btn == coninput.FROM_LEFT_4TH_BUTTON_PRESSED: // unknown (possibly mouse forward) | ||||
| 	case coninput.FROM_LEFT_4TH_BUTTON_PRESSED: // unknown (possibly mouse forward) | ||||
| 		button = MouseButtonForward | ||||
| 	} | ||||
|  | ||||
|  | ||||
							
								
								
									
										2
									
								
								vendor/github.com/charmbracelet/bubbletea/screen.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/charmbracelet/bubbletea/screen.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -131,7 +131,7 @@ func EnableBracketedPaste() Msg { | ||||
| type enableBracketedPasteMsg struct{} | ||||
|  | ||||
| // DisableBracketedPaste is a special command that tells the Bubble Tea program | ||||
| // to accept bracketed paste input. | ||||
| // to stop processing bracketed paste input. | ||||
| // | ||||
| // Note that bracketed paste will be automatically disabled when the | ||||
| // program quits. | ||||
|  | ||||
							
								
								
									
										2
									
								
								vendor/github.com/charmbracelet/bubbletea/standard_renderer.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/charmbracelet/bubbletea/standard_renderer.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -277,7 +277,7 @@ func (r *standardRenderer) flush() { | ||||
| 		// using the full terminal window. | ||||
| 		buf.WriteString(ansi.CursorPosition(0, len(newLines))) | ||||
| 	} else { | ||||
| 		buf.WriteString(ansi.CursorBackward(r.width)) | ||||
| 		buf.WriteByte('\r') | ||||
| 	} | ||||
|  | ||||
| 	_, _ = r.out.Write(buf.Bytes()) | ||||
|  | ||||
							
								
								
									
										107
									
								
								vendor/github.com/charmbracelet/bubbletea/tea.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										107
									
								
								vendor/github.com/charmbracelet/bubbletea/tea.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -24,7 +24,6 @@ import ( | ||||
|  | ||||
| 	"github.com/charmbracelet/x/term" | ||||
| 	"github.com/muesli/cancelreader" | ||||
| 	"golang.org/x/sync/errgroup" | ||||
| ) | ||||
|  | ||||
| // ErrProgramPanic is returned by [Program.Run] when the program recovers from a panic. | ||||
| @ -73,7 +72,7 @@ const ( | ||||
| 	customInput | ||||
| ) | ||||
|  | ||||
| // String implements the stringer interface for [inputType]. It is inteded to | ||||
| // String implements the stringer interface for [inputType]. It is intended to | ||||
| // be used in testing. | ||||
| func (i inputType) String() string { | ||||
| 	return [...]string{ | ||||
| @ -220,7 +219,7 @@ func Suspend() Msg { | ||||
| // You can send this message with [Suspend()]. | ||||
| type SuspendMsg struct{} | ||||
|  | ||||
| // ResumeMsg can be listen to to do something once a program is resumed back | ||||
| // ResumeMsg can be listen to do something once a program is resumed back | ||||
| // from a suspend state. | ||||
| type ResumeMsg struct{} | ||||
|  | ||||
| @ -472,42 +471,12 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) { | ||||
| 				p.exec(msg.cmd, msg.fn) | ||||
|  | ||||
| 			case BatchMsg: | ||||
| 				for _, cmd := range msg { | ||||
| 					select { | ||||
| 					case <-p.ctx.Done(): | ||||
| 						return model, nil | ||||
| 					case cmds <- cmd: | ||||
| 					} | ||||
| 				} | ||||
| 				go p.execBatchMsg(msg) | ||||
| 				continue | ||||
|  | ||||
| 			case sequenceMsg: | ||||
| 				go func() { | ||||
| 					// Execute commands one at a time, in order. | ||||
| 					for _, cmd := range msg { | ||||
| 						if cmd == nil { | ||||
| 				go p.execSequenceMsg(msg) | ||||
| 				continue | ||||
| 						} | ||||
|  | ||||
| 						msg := cmd() | ||||
| 						if batchMsg, ok := msg.(BatchMsg); ok { | ||||
| 							g, _ := errgroup.WithContext(p.ctx) | ||||
| 							for _, cmd := range batchMsg { | ||||
| 								cmd := cmd | ||||
| 								g.Go(func() error { | ||||
| 									p.Send(cmd()) | ||||
| 									return nil | ||||
| 								}) | ||||
| 							} | ||||
|  | ||||
| 							//nolint:errcheck,gosec | ||||
| 							g.Wait() // wait for all commands from batch msg to finish | ||||
| 							continue | ||||
| 						} | ||||
|  | ||||
| 						p.Send(msg) | ||||
| 					} | ||||
| 				}() | ||||
|  | ||||
| 			case setWindowTitleMsg: | ||||
| 				p.SetWindowTitle(string(msg)) | ||||
| @ -535,6 +504,74 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *Program) execSequenceMsg(msg sequenceMsg) { | ||||
| 	if !p.startupOptions.has(withoutCatchPanics) { | ||||
| 		defer func() { | ||||
| 			if r := recover(); r != nil { | ||||
| 				p.recoverFromGoPanic(r) | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
|  | ||||
| 	// Execute commands one at a time, in order. | ||||
| 	for _, cmd := range msg { | ||||
| 		if cmd == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		msg := cmd() | ||||
| 		switch msg := msg.(type) { | ||||
| 		case BatchMsg: | ||||
| 			p.execBatchMsg(msg) | ||||
| 		case sequenceMsg: | ||||
| 			p.execSequenceMsg(msg) | ||||
| 		default: | ||||
| 			p.Send(msg) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *Program) execBatchMsg(msg BatchMsg) { | ||||
| 	if !p.startupOptions.has(withoutCatchPanics) { | ||||
| 		defer func() { | ||||
| 			if r := recover(); r != nil { | ||||
| 				p.recoverFromGoPanic(r) | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
|  | ||||
| 	// Execute commands one at a time. | ||||
| 	var wg sync.WaitGroup | ||||
| 	for _, cmd := range msg { | ||||
| 		if cmd == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		wg.Add(1) | ||||
| 		go func() { | ||||
| 			defer wg.Done() | ||||
|  | ||||
| 			if !p.startupOptions.has(withoutCatchPanics) { | ||||
| 				defer func() { | ||||
| 					if r := recover(); r != nil { | ||||
| 						p.recoverFromGoPanic(r) | ||||
| 					} | ||||
| 				}() | ||||
| 			} | ||||
|  | ||||
| 			msg := cmd() | ||||
| 			switch msg := msg.(type) { | ||||
| 			case BatchMsg: | ||||
| 				p.execBatchMsg(msg) | ||||
| 			case sequenceMsg: | ||||
| 				p.execSequenceMsg(msg) | ||||
| 			default: | ||||
| 				p.Send(msg) | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
|  | ||||
| 	wg.Wait() // wait for all commands from batch msg to finish | ||||
| } | ||||
|  | ||||
| // Run initializes the program and runs its event loops, blocking until it gets | ||||
| // terminated by either [Program.Quit], [Program.Kill], or its signal handler. | ||||
| // Returns the final model. | ||||
|  | ||||
							
								
								
									
										2
									
								
								vendor/github.com/charmbracelet/bubbletea/tty_windows.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/charmbracelet/bubbletea/tty_windows.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -56,7 +56,7 @@ func (p *Program) initInput() (err error) { | ||||
|  | ||||
| // Open the Windows equivalent of a TTY. | ||||
| func openInputTTY() (*os.File, error) { | ||||
| 	f, err := os.OpenFile("CONIN$", os.O_RDWR, 0o644) | ||||
| 	f, err := os.OpenFile("CONIN$", os.O_RDWR, 0o644) //nolint:gosec | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error opening file: %w", err) | ||||
| 	} | ||||
|  | ||||
							
								
								
									
										24
									
								
								vendor/github.com/charmbracelet/x/ansi/inband.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								vendor/github.com/charmbracelet/x/ansi/inband.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| package ansi | ||||
|  | ||||
| import "fmt" | ||||
|  | ||||
| // InBandResize encodes an in-band terminal resize event sequence. | ||||
| // | ||||
| //	CSI 48 ; height_cells ; widht_cells ; height_pixels ; width_pixels t | ||||
| // | ||||
| // See https://gist.github.com/rockorager/e695fb2924d36b2bcf1fff4a3704bd83 | ||||
| func InBandResize(heightCells, widthCells, heightPixels, widthPixels int) string { | ||||
| 	if heightCells < 0 { | ||||
| 		heightCells = 0 | ||||
| 	} | ||||
| 	if widthCells < 0 { | ||||
| 		widthCells = 0 | ||||
| 	} | ||||
| 	if heightPixels < 0 { | ||||
| 		heightPixels = 0 | ||||
| 	} | ||||
| 	if widthPixels < 0 { | ||||
| 		widthPixels = 0 | ||||
| 	} | ||||
| 	return fmt.Sprintf("\x1b[48;%d;%d;%d;%dt", heightCells, widthCells, heightPixels, widthPixels) | ||||
| } | ||||
							
								
								
									
										34
									
								
								vendor/github.com/charmbracelet/x/ansi/palette.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								vendor/github.com/charmbracelet/x/ansi/palette.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| package ansi | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"image/color" | ||||
| ) | ||||
|  | ||||
| // SetPalette sets the palette color for the given index. The index is a 16 | ||||
| // color index between 0 and 15. The color is a 24-bit RGB color. | ||||
| // | ||||
| //	OSC P n rrggbb BEL | ||||
| // | ||||
| // Where n is the color index in hex (0-f), and rrggbb is the color in | ||||
| // hexadecimal format (e.g., ff0000 for red). | ||||
| // | ||||
| // This sequence is specific to the Linux Console and may not work in other | ||||
| // terminal emulators. | ||||
| // | ||||
| // See https://man7.org/linux/man-pages/man4/console_codes.4.html | ||||
| func SetPalette(i int, c color.Color) string { | ||||
| 	if c == nil || i < 0 || i > 15 { | ||||
| 		return "" | ||||
| 	} | ||||
| 	r, g, b, _ := c.RGBA() | ||||
| 	return fmt.Sprintf("\x1b]P%x%02x%02x%02x\x07", i, r>>8, g>>8, b>>8) | ||||
| } | ||||
|  | ||||
| // ResetPalette resets the color palette to the default values. | ||||
| // | ||||
| // This sequence is specific to the Linux Console and may not work in other | ||||
| // terminal emulators. | ||||
| // | ||||
| // See https://man7.org/linux/man-pages/man4/console_codes.4.html | ||||
| const ResetPalette = "\x1b]R\x07" | ||||
							
								
								
									
										49
									
								
								vendor/github.com/charmbracelet/x/ansi/progress.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								vendor/github.com/charmbracelet/x/ansi/progress.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | ||||
| package ansi | ||||
|  | ||||
| import "strconv" | ||||
|  | ||||
| // ResetProgressBar is a sequence that resets the progress bar to its default | ||||
| // state (hidden). | ||||
| // | ||||
| // OSC 9 ; 4 ; 0 BEL | ||||
| // | ||||
| // See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences | ||||
| const ResetProgressBar = "\x1b]9;4;0\x07" | ||||
|  | ||||
| // SetProgressBar returns a sequence for setting the progress bar to a specific | ||||
| // percentage (0-100) in the "default" state. | ||||
| // | ||||
| // OSC 9 ; 4 ; 1 Percentage BEL | ||||
| // | ||||
| // See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences | ||||
| func SetProgressBar(percentage int) string { | ||||
| 	return "\x1b]9;4;1;" + strconv.Itoa(min(max(0, percentage), 100)) + "\x07" | ||||
| } | ||||
|  | ||||
| // SetErrorProgressBar returns a sequence for setting the progress bar to a | ||||
| // specific percentage (0-100) in the "Error" state.. | ||||
| // | ||||
| // OSC 9 ; 4 ; 2 Percentage BEL | ||||
| // | ||||
| // See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences | ||||
| func SetErrorProgressBar(percentage int) string { | ||||
| 	return "\x1b]9;4;2;" + strconv.Itoa(min(max(0, percentage), 100)) + "\x07" | ||||
| } | ||||
|  | ||||
| // SetIndeterminateProgressBar is a sequence that sets the progress bar to the | ||||
| // indeterminate state. | ||||
| // | ||||
| // OSC 9 ; 4 ; 3 BEL | ||||
| // | ||||
| // See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences | ||||
| const SetIndeterminateProgressBar = "\x1b]9;4;3\x07" | ||||
|  | ||||
| // SetWarningProgressBar is a sequence that sets the progress bar to the | ||||
| // "Warning" state. | ||||
| // | ||||
| // OSC 9 ; 4 ; 4 Percentage BEL | ||||
| // | ||||
| // See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences | ||||
| func SetWarningProgressBar(percentage int) string { | ||||
| 	return "\x1b]9;4;4;" + strconv.Itoa(min(max(0, percentage), 100)) + "\x07" | ||||
| } | ||||
							
								
								
									
										6
									
								
								vendor/github.com/charmbracelet/x/ansi/winop.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								vendor/github.com/charmbracelet/x/ansi/winop.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -8,16 +8,22 @@ import ( | ||||
| const ( | ||||
| 	// ResizeWindowWinOp is a window operation that resizes the terminal | ||||
| 	// window. | ||||
| 	// | ||||
| 	// Deprecated: Use constant number directly with [WindowOp]. | ||||
| 	ResizeWindowWinOp = 4 | ||||
|  | ||||
| 	// RequestWindowSizeWinOp is a window operation that requests a report of | ||||
| 	// the size of the terminal window in pixels. The response is in the form: | ||||
| 	//  CSI 4 ; height ; width t | ||||
| 	// | ||||
| 	// Deprecated: Use constant number directly with [WindowOp]. | ||||
| 	RequestWindowSizeWinOp = 14 | ||||
|  | ||||
| 	// RequestCellSizeWinOp is a window operation that requests a report of | ||||
| 	// the size of the terminal cell size in pixels. The response is in the form: | ||||
| 	//  CSI 6 ; height ; width t | ||||
| 	// | ||||
| 	// Deprecated: Use constant number directly with [WindowOp]. | ||||
| 	RequestCellSizeWinOp = 16 | ||||
| ) | ||||
|  | ||||
|  | ||||
							
								
								
									
										21
									
								
								vendor/github.com/clipperhouse/uax29/v2/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/clipperhouse/uax29/v2/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| MIT License | ||||
|  | ||||
| Copyright (c) 2020 Matt Sherman | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
							
								
								
									
										82
									
								
								vendor/github.com/clipperhouse/uax29/v2/graphemes/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								vendor/github.com/clipperhouse/uax29/v2/graphemes/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,82 @@ | ||||
| An implementation of grapheme cluster boundaries from [Unicode text segmentation](https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries) (UAX 29), for Unicode version 15.0.0. | ||||
|  | ||||
| ## Quick start | ||||
|  | ||||
| ``` | ||||
| go get "github.com/clipperhouse/uax29/v2/graphemes" | ||||
| ``` | ||||
|  | ||||
| ```go | ||||
| import "github.com/clipperhouse/uax29/v2/graphemes" | ||||
|  | ||||
| text := "Hello, 世界. Nice dog! 👍🐶" | ||||
|  | ||||
| tokens := graphemes.FromString(text) | ||||
|  | ||||
| for tokens.Next() {                     // Next() returns true until end of data | ||||
| 	fmt.Println(tokens.Value())         // Do something with the current grapheme | ||||
| } | ||||
| ``` | ||||
|  | ||||
| [](https://pkg.go.dev/github.com/clipperhouse/uax29/v2/graphemes) | ||||
|  | ||||
| _A grapheme is a “single visible character”, which might be a simple as a single letter, or a complex emoji that consists of several Unicode code points._ | ||||
|  | ||||
| ## Conformance | ||||
|  | ||||
| We use the Unicode [test suite](https://unicode.org/reports/tr41/tr41-26.html#Tests29). Status: | ||||
|  | ||||
|  | ||||
|  | ||||
| ## APIs | ||||
|  | ||||
| ### If you have a `string` | ||||
|  | ||||
| ```go | ||||
| text := "Hello, 世界. Nice dog! 👍🐶" | ||||
|  | ||||
| tokens := graphemes.FromString(text) | ||||
|  | ||||
| for tokens.Next() {                     // Next() returns true until end of data | ||||
| 	fmt.Println(tokens.Value())         // Do something with the current grapheme | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### If you have an `io.Reader` | ||||
|  | ||||
| `FromReader` embeds a [`bufio.Scanner`](https://pkg.go.dev/bufio#Scanner), so just use those methods. | ||||
|  | ||||
| ```go | ||||
| r := getYourReader()                        // from a file or network maybe | ||||
| tokens := graphemes.FromReader(r) | ||||
|  | ||||
| for tokens.Scan() {                         // Scan() returns true until error or EOF | ||||
| 	fmt.Println(tokens.Text())              // Do something with the current grapheme | ||||
| } | ||||
|  | ||||
| if tokens.Err() != nil {                    // Check the error | ||||
| 	log.Fatal(tokens.Err()) | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### If you have a `[]byte` | ||||
|  | ||||
| ```go | ||||
| b := []byte("Hello, 世界. Nice dog! 👍🐶") | ||||
|  | ||||
| tokens := graphemes.FromBytes(b) | ||||
|  | ||||
| for tokens.Next() {                     // Next() returns true until end of data | ||||
| 	fmt.Println(tokens.Value())         // Do something with the current grapheme | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Performance | ||||
|  | ||||
| On a Mac M2 laptop, we see around 200MB/s, or around 100 million graphemes per second. You should see ~constant memory, and no allocations. | ||||
|  | ||||
| ### Invalid inputs | ||||
|  | ||||
| Invalid UTF-8 input is considered undefined behavior. We test to ensure that bad inputs will not cause pathological outcomes, such as a panic or infinite loop. Callers should expect “garbage-in, garbage-out”. | ||||
|  | ||||
| Your pipeline should probably include a call to [`utf8.Valid()`](https://pkg.go.dev/unicode/utf8#Valid). | ||||
							
								
								
									
										28
									
								
								vendor/github.com/clipperhouse/uax29/v2/graphemes/iterator.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								vendor/github.com/clipperhouse/uax29/v2/graphemes/iterator.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| package graphemes | ||||
|  | ||||
| import "github.com/clipperhouse/uax29/v2/internal/iterators" | ||||
|  | ||||
| type Iterator[T iterators.Stringish] struct { | ||||
| 	*iterators.Iterator[T] | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	splitFuncString = splitFunc[string] | ||||
| 	splitFuncBytes  = splitFunc[[]byte] | ||||
| ) | ||||
|  | ||||
| // FromString returns an iterator for the grapheme clusters in the input string. | ||||
| // Iterate while Next() is true, and access the grapheme via Value(). | ||||
| func FromString(s string) Iterator[string] { | ||||
| 	return Iterator[string]{ | ||||
| 		iterators.New(splitFuncString, s), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // FromBytes returns an iterator for the grapheme clusters in the input bytes. | ||||
| // Iterate while Next() is true, and access the grapheme via Value(). | ||||
| func FromBytes(b []byte) Iterator[[]byte] { | ||||
| 	return Iterator[[]byte]{ | ||||
| 		iterators.New(splitFuncBytes, b), | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										25
									
								
								vendor/github.com/clipperhouse/uax29/v2/graphemes/reader.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								vendor/github.com/clipperhouse/uax29/v2/graphemes/reader.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| // Package graphemes implements Unicode grapheme cluster boundaries: https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries | ||||
| package graphemes | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| type Scanner struct { | ||||
| 	*bufio.Scanner | ||||
| } | ||||
|  | ||||
| // FromReader returns a Scanner, to split graphemes per | ||||
| // https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries. | ||||
| // | ||||
| // It embeds a [bufio.Scanner], so you can use its methods. | ||||
| // | ||||
| // Iterate through graphemes by calling Scan() until false, then check Err(). | ||||
| func FromReader(r io.Reader) *Scanner { | ||||
| 	sc := bufio.NewScanner(r) | ||||
| 	sc.Split(SplitFunc) | ||||
| 	return &Scanner{ | ||||
| 		Scanner: sc, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										174
									
								
								vendor/github.com/clipperhouse/uax29/v2/graphemes/splitfunc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								vendor/github.com/clipperhouse/uax29/v2/graphemes/splitfunc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,174 @@ | ||||
| package graphemes | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
|  | ||||
| 	"github.com/clipperhouse/uax29/v2/internal/iterators" | ||||
| ) | ||||
|  | ||||
| // is determines if lookup intersects propert(ies) | ||||
| func (lookup property) is(properties property) bool { | ||||
| 	return (lookup & properties) != 0 | ||||
| } | ||||
|  | ||||
| const _Ignore = _Extend | ||||
|  | ||||
| // SplitFunc is a bufio.SplitFunc implementation of Unicode grapheme cluster segmentation, for use with bufio.Scanner. | ||||
| // | ||||
| // See https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries. | ||||
| var SplitFunc bufio.SplitFunc = splitFunc[[]byte] | ||||
|  | ||||
| func splitFunc[T iterators.Stringish](data T, atEOF bool) (advance int, token T, err error) { | ||||
| 	var empty T | ||||
| 	if len(data) == 0 { | ||||
| 		return 0, empty, nil | ||||
| 	} | ||||
|  | ||||
| 	// These vars are stateful across loop iterations | ||||
| 	var pos int | ||||
| 	var lastExIgnore property = 0     // "last excluding ignored categories" | ||||
| 	var lastLastExIgnore property = 0 // "last one before that" | ||||
| 	var regionalIndicatorCount int | ||||
|  | ||||
| 	// Rules are usually of the form Cat1 × Cat2; "current" refers to the first property | ||||
| 	// to the right of the ×, from which we look back or forward | ||||
|  | ||||
| 	current, w := lookup(data[pos:]) | ||||
| 	if w == 0 { | ||||
| 		if !atEOF { | ||||
| 			// Rune extends past current data, request more | ||||
| 			return 0, empty, nil | ||||
| 		} | ||||
| 		pos = len(data) | ||||
| 		return pos, data[:pos], nil | ||||
| 	} | ||||
|  | ||||
| 	// https://unicode.org/reports/tr29/#GB1 | ||||
| 	// Start of text always advances | ||||
| 	pos += w | ||||
|  | ||||
| 	for { | ||||
| 		eot := pos == len(data) // "end of text" | ||||
|  | ||||
| 		if eot { | ||||
| 			if !atEOF { | ||||
| 				// Token extends past current data, request more | ||||
| 				return 0, empty, nil | ||||
| 			} | ||||
|  | ||||
| 			// https://unicode.org/reports/tr29/#GB2 | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		/* | ||||
| 			We've switched the evaluation order of GB1↓ and GB2↑. It's ok: | ||||
| 			because we've checked for len(data) at the top of this function, | ||||
| 			sot and eot are mutually exclusive, order doesn't matter. | ||||
| 		*/ | ||||
|  | ||||
| 		// Rules are usually of the form Cat1 × Cat2; "current" refers to the first property | ||||
| 		// to the right of the ×, from which we look back or forward | ||||
|  | ||||
| 		// Remember previous properties to avoid lookups/lookbacks | ||||
| 		last := current | ||||
| 		if !last.is(_Ignore) { | ||||
| 			lastLastExIgnore = lastExIgnore | ||||
| 			lastExIgnore = last | ||||
| 		} | ||||
|  | ||||
| 		current, w = lookup(data[pos:]) | ||||
| 		if w == 0 { | ||||
| 			if atEOF { | ||||
| 				// Just return the bytes, we can't do anything with them | ||||
| 				pos = len(data) | ||||
| 				break | ||||
| 			} | ||||
| 			// Rune extends past current data, request more | ||||
| 			return 0, empty, nil | ||||
| 		} | ||||
|  | ||||
| 		// Optimization: no rule can possibly apply | ||||
| 		if current|last == 0 { // i.e. both are zero | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB3 | ||||
| 		if current.is(_LF) && last.is(_CR) { | ||||
| 			pos += w | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB4 | ||||
| 		// https://unicode.org/reports/tr29/#GB5 | ||||
| 		if (current | last).is(_Control | _CR | _LF) { | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB6 | ||||
| 		if current.is(_L|_V|_LV|_LVT) && last.is(_L) { | ||||
| 			pos += w | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB7 | ||||
| 		if current.is(_V|_T) && last.is(_LV|_V) { | ||||
| 			pos += w | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB8 | ||||
| 		if current.is(_T) && last.is(_LVT|_T) { | ||||
| 			pos += w | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB9 | ||||
| 		if current.is(_Extend | _ZWJ) { | ||||
| 			pos += w | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB9a | ||||
| 		if current.is(_SpacingMark) { | ||||
| 			pos += w | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB9b | ||||
| 		if last.is(_Prepend) { | ||||
| 			pos += w | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB9c | ||||
| 		// TODO(clipperhouse): | ||||
| 		// It appears to be added in Unicode 15.1.0: | ||||
| 		// https://unicode.org/versions/Unicode15.1.0/#Migration | ||||
| 		// This package currently supports Unicode 15.0.0, so | ||||
| 		// out of scope for now | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB11 | ||||
| 		if current.is(_ExtendedPictographic) && last.is(_ZWJ) && lastLastExIgnore.is(_ExtendedPictographic) { | ||||
| 			pos += w | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB12 | ||||
| 		// https://unicode.org/reports/tr29/#GB13 | ||||
| 		if (current & last).is(_RegionalIndicator) { | ||||
| 			regionalIndicatorCount++ | ||||
|  | ||||
| 			odd := regionalIndicatorCount%2 == 1 | ||||
| 			if odd { | ||||
| 				pos += w | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// If we fall through all the above rules, it's a grapheme cluster break | ||||
| 		break | ||||
| 	} | ||||
|  | ||||
| 	// Return token | ||||
| 	return pos, data[:pos], nil | ||||
| } | ||||
							
								
								
									
										1409
									
								
								vendor/github.com/clipperhouse/uax29/v2/graphemes/trie.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1409
									
								
								vendor/github.com/clipperhouse/uax29/v2/graphemes/trie.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										85
									
								
								vendor/github.com/clipperhouse/uax29/v2/internal/iterators/iterator.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								vendor/github.com/clipperhouse/uax29/v2/internal/iterators/iterator.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | ||||
| package iterators | ||||
|  | ||||
| type Stringish interface { | ||||
| 	[]byte | string | ||||
| } | ||||
|  | ||||
| type SplitFunc[T Stringish] func(T, bool) (int, T, error) | ||||
|  | ||||
| // Iterator is a generic iterator for words that are either []byte or string. | ||||
| // Iterate while Next() is true, and access the word via Value(). | ||||
| type Iterator[T Stringish] struct { | ||||
| 	split SplitFunc[T] | ||||
| 	data  T | ||||
| 	start int | ||||
| 	pos   int | ||||
| } | ||||
|  | ||||
| // New creates a new Iterator for the given data and SplitFunc. | ||||
| func New[T Stringish](split SplitFunc[T], data T) *Iterator[T] { | ||||
| 	return &Iterator[T]{ | ||||
| 		split: split, | ||||
| 		data:  data, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // SetText sets the text for the iterator to operate on, and resets all state. | ||||
| func (iter *Iterator[T]) SetText(data T) { | ||||
| 	iter.data = data | ||||
| 	iter.start = 0 | ||||
| 	iter.pos = 0 | ||||
| } | ||||
|  | ||||
| // Split sets the SplitFunc for the Iterator. | ||||
| func (iter *Iterator[T]) Split(split SplitFunc[T]) { | ||||
| 	iter.split = split | ||||
| } | ||||
|  | ||||
| // Next advances the iterator to the next token. It returns false when there | ||||
| // are no remaining tokens or an error occurred. | ||||
| func (iter *Iterator[T]) Next() bool { | ||||
| 	if iter.pos == len(iter.data) { | ||||
| 		return false | ||||
| 	} | ||||
| 	if iter.pos > len(iter.data) { | ||||
| 		panic("SplitFunc advanced beyond the end of the data") | ||||
| 	} | ||||
|  | ||||
| 	iter.start = iter.pos | ||||
|  | ||||
| 	advance, _, err := iter.split(iter.data[iter.pos:], true) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	if advance <= 0 { | ||||
| 		panic("SplitFunc returned a zero or negative advance") | ||||
| 	} | ||||
|  | ||||
| 	iter.pos += advance | ||||
| 	if iter.pos > len(iter.data) { | ||||
| 		panic("SplitFunc advanced beyond the end of the data") | ||||
| 	} | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // Value returns the current token. | ||||
| func (iter *Iterator[T]) Value() T { | ||||
| 	return iter.data[iter.start:iter.pos] | ||||
| } | ||||
|  | ||||
| // Start returns the byte position of the current token in the original data. | ||||
| func (iter *Iterator[T]) Start() int { | ||||
| 	return iter.start | ||||
| } | ||||
|  | ||||
| // End returns the byte position after the current token in the original data. | ||||
| func (iter *Iterator[T]) End() int { | ||||
| 	return iter.pos | ||||
| } | ||||
|  | ||||
| // Reset resets the iterator to the beginning of the data. | ||||
| func (iter *Iterator[T]) Reset() { | ||||
| 	iter.start = 0 | ||||
| 	iter.pos = 0 | ||||
| } | ||||
							
								
								
									
										56
									
								
								vendor/github.com/cyphar/filepath-securejoin/.golangci.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								vendor/github.com/cyphar/filepath-securejoin/.golangci.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | ||||
| # SPDX-License-Identifier: MPL-2.0 | ||||
|  | ||||
| # Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| # Copyright (C) 2025 SUSE LLC | ||||
| # | ||||
| # This Source Code Form is subject to the terms of the Mozilla Public | ||||
| # License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| # file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| version: "2" | ||||
|  | ||||
| linters: | ||||
|   enable: | ||||
|     - asasalint | ||||
|     - asciicheck | ||||
|     - containedctx | ||||
|     - contextcheck | ||||
|     - errcheck | ||||
|     - errorlint | ||||
|     - exhaustive | ||||
|     - forcetypeassert | ||||
|     - godot | ||||
|     - goprintffuncname | ||||
|     - govet | ||||
|     - importas | ||||
|     - ineffassign | ||||
|     - makezero | ||||
|     - misspell | ||||
|     - musttag | ||||
|     - nilerr | ||||
|     - nilnesserr | ||||
|     - nilnil | ||||
|     - noctx | ||||
|     - prealloc | ||||
|     - revive | ||||
|     - staticcheck | ||||
|     - testifylint | ||||
|     - unconvert | ||||
|     - unparam | ||||
|     - unused | ||||
|     - usetesting | ||||
|   settings: | ||||
|     govet: | ||||
|       enable: | ||||
|         - nilness | ||||
|     testifylint: | ||||
|       enable-all: true | ||||
|  | ||||
| formatters: | ||||
|   enable: | ||||
|     - gofumpt | ||||
|     - goimports | ||||
|   settings: | ||||
|     goimports: | ||||
|       local-prefixes: | ||||
|         - github.com/cyphar/filepath-securejoin | ||||
							
								
								
									
										121
									
								
								vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										121
									
								
								vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -6,6 +6,122 @@ and this project adheres to [Semantic Versioning](http://semver.org/). | ||||
|  | ||||
| ## [Unreleased] ## | ||||
|  | ||||
| ## [0.5.0] - 2025-09-26 ## | ||||
|  | ||||
| > Let the past die. Kill it if you have to. | ||||
|  | ||||
| > **NOTE**: With this release, some parts of | ||||
| > `github.com/cyphar/filepath-securejoin` are now licensed under the Mozilla | ||||
| > Public License (version 2). Please see [COPYING.md][] as well as the the | ||||
| > license header in each file for more details. | ||||
|  | ||||
| [COPYING.md]: ./COPYING.md | ||||
|  | ||||
| ### Breaking ### | ||||
| - The new API introduced in the [0.3.0][] release has been moved to a new | ||||
|   subpackage called `pathrs-lite`. This was primarily done to better indicate | ||||
|   the split between the new and old APIs, as well as indicate to users the | ||||
|   purpose of this subpackage (it is a less complete version of [libpathrs][]). | ||||
|  | ||||
|   We have added some wrappers to the top-level package to ease the transition, | ||||
|   but those are deprecated and will be removed in the next minor release of | ||||
|   filepath-securejoin. Users should update their import paths. | ||||
|  | ||||
|   This new subpackage has also been relicensed under the Mozilla Public License | ||||
|   (version 2), please see [COPYING.md][] for more details. | ||||
|  | ||||
| ### Added ### | ||||
| - Most of the key bits the safe `procfs` API have now been exported and are | ||||
|   available in `github.com/cyphar/filepath-securejoin/pathrs-lite/procfs`. At | ||||
|   the moment this primarily consists of a new `procfs.Handle` API: | ||||
|  | ||||
|    * `OpenProcRoot` returns a new handle to `/proc`, endeavouring to make it | ||||
|      safe if possible (`subset=pid` to protect against mistaken write attacks | ||||
|      and leaks, as well as using `fsopen(2)` to avoid racing mount attacks). | ||||
|  | ||||
|      `OpenUnsafeProcRoot` returns a handle without attempting to create one | ||||
|      with `subset=pid`, which makes it more dangerous to leak. Most users | ||||
|      should use `OpenProcRoot` (even if you need to use `ProcRoot` as the base | ||||
|      of an operation, as filepath-securejoin will internally open a handle when | ||||
|      necessary). | ||||
|  | ||||
|    * The `(*procfs.Handle).Open*` family of methods lets you get a safe | ||||
|      `O_PATH` handle to subpaths within `/proc` for certain subpaths. | ||||
|  | ||||
|      For `OpenThreadSelf`, the returned `ProcThreadSelfCloser` needs to be | ||||
|      called after you completely finish using the handle (this is necessary | ||||
|      because Go is multi-threaded and `ProcThreadSelf` references | ||||
|      `/proc/thread-self` which may disappear if we do not | ||||
|      `runtime.LockOSThread` -- `ProcThreadSelfCloser` is currently equivalent | ||||
|      to `runtime.UnlockOSThread`). | ||||
|  | ||||
|      Note that you cannot open any `procfs` symlinks (most notably magic-links) | ||||
|      using this API. At the moment, filepath-securejoin does not support this | ||||
|      feature (but [libpathrs][] does). | ||||
|  | ||||
|    * `ProcSelfFdReadlink` lets you get the in-kernel path representation of a | ||||
|      file descriptor (think `readlink("/proc/self/fd/...")`), except that we | ||||
|      verify that there aren't any tricky overmounts that could fool the | ||||
|      process. | ||||
|  | ||||
|      Please be aware that the returned string is simply a snapshot at that | ||||
|      particular moment, and an attacker could move the file being pointed to. | ||||
|      In addition, complex namespace configurations could result in non-sensical | ||||
|      or confusing paths to be returned. The value received from this function | ||||
|      should only be used as secondary verification of some security property, | ||||
|      not as proof that a particular handle has a particular path. | ||||
|  | ||||
|   The procfs handle used internally by the API is the same as the rest of | ||||
|   `filepath-securejoin` (for privileged programs this is usually a private | ||||
|   in-process `procfs` instance created with `fsopen(2)`). | ||||
|  | ||||
|   As before, this is intended as a stop-gap before users migrate to | ||||
|   [libpathrs][], which provides a far more extensive safe `procfs` API and is | ||||
|   generally more robust. | ||||
|  | ||||
| - Previously, the hardened procfs implementation (used internally within | ||||
|   `Reopen` and `Open(at)InRoot`) only protected against overmount attacks on | ||||
|   systems with `openat2(2)` (Linux 5.6) or systems with `fsopen(2)` or | ||||
|   `open_tree(2)` (Linux 5.2) and programs with privileges to use them (with | ||||
|   some caveats about locked mounts that probably affect very few users). For | ||||
|   other users, an attacker with the ability to create malicious mounts (on most | ||||
|   systems, a sysadmin) could trick you into operating on files you didn't | ||||
|   expect. This attack only really makes sense in the context of container | ||||
|   runtime implementations. | ||||
|  | ||||
|   This was considered a reasonable trade-off, as the long-term intention was to | ||||
|   get all users to just switch to [libpathrs][] if they wanted to use the safe | ||||
|   `procfs` API (which had more extensive protections, and is what these new | ||||
|   protections in `filepath-securejoin` are based on). However, as the API | ||||
|   is now being exported it seems unwise to advertise the API as "safe" if we do | ||||
|   not protect against known attacks. | ||||
|  | ||||
|   The procfs API is now more protected against attackers on systems lacking the | ||||
|   aforementioned protections. However, the most comprehensive of these | ||||
|   protections effectively rely on [`statx(STATX_MNT_ID)`][statx.2] (Linux 5.8). | ||||
|   On older kernel versions, there is no effective protection (there is some | ||||
|   minimal protection against non-`procfs` filesystem components but a | ||||
|   sufficiently clever attacker can work around those). In addition, | ||||
|   `STATX_MNT_ID` is vulnerable to mount ID reuse attacks by sufficiently | ||||
|   motivated and privileged attackers -- this problem is mitigated with | ||||
|   `STATX_MNT_ID_UNIQUE` (Linux 6.8) but that raises the minimum kernel version | ||||
|   for more protection. | ||||
|  | ||||
|   The fact that these protections are quite limited despite needing a fair bit | ||||
|   of extra code to handle was one of the primary reasons we did not initially | ||||
|   implement this in `filepath-securejoin` ([libpathrs][] supports all of this, | ||||
|   of course). | ||||
|  | ||||
| ### Fixed ### | ||||
| - RHEL 8 kernels have backports of `fsopen(2)` but in some testing we've found | ||||
|   that it has very bad (and very difficult to debug) performance issues, and so | ||||
|   we will explicitly refuse to use `fsopen(2)` if the running kernel version is | ||||
|   pre-5.2 and will instead fallback to `open("/proc")`. | ||||
|  | ||||
| [CVE-2024-21626]: https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv | ||||
| [libpathrs]: https://github.com/cyphar/libpathrs | ||||
| [statx.2]: https://www.man7.org/linux/man-pages/man2/statx.2.html | ||||
|  | ||||
| ## [0.4.1] - 2025-01-28 ## | ||||
|  | ||||
| ### Fixed ### | ||||
| @ -173,7 +289,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). | ||||
|   safe to start migrating to as we have extensive tests ensuring they behave | ||||
|   correctly and are safe against various races and other attacks. | ||||
|  | ||||
| [libpathrs]: https://github.com/openSUSE/libpathrs | ||||
| [libpathrs]: https://github.com/cyphar/libpathrs | ||||
| [open.2]: https://www.man7.org/linux/man-pages/man2/open.2.html | ||||
|  | ||||
| ## [0.2.5] - 2024-05-03 ## | ||||
| @ -238,7 +354,8 @@ This is our first release of `github.com/cyphar/filepath-securejoin`, | ||||
| containing a full implementation with a coverage of 93.5% (the only missing | ||||
| cases are the error cases, which are hard to mocktest at the moment). | ||||
|  | ||||
| [Unreleased]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...HEAD | ||||
| [Unreleased]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.0...HEAD | ||||
| [0.5.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...v0.5.0 | ||||
| [0.4.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.0...v0.4.1 | ||||
| [0.4.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.6...v0.4.0 | ||||
| [0.3.6]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.5...v0.3.6 | ||||
|  | ||||
							
								
								
									
										447
									
								
								vendor/github.com/cyphar/filepath-securejoin/COPYING.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										447
									
								
								vendor/github.com/cyphar/filepath-securejoin/COPYING.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,447 @@ | ||||
| ## COPYING ## | ||||
|  | ||||
| `SPDX-License-Identifier: BSD-3-Clause AND MPL-2.0` | ||||
|  | ||||
| This project is made up of code licensed under different licenses. Which code | ||||
| you use will have an impact on whether only one or both licenses apply to your | ||||
| usage of this library. | ||||
|  | ||||
| Note that **each file** in this project individually has a code comment at the | ||||
| start describing the license of that particular file -- this is the most | ||||
| accurate license information of this project; in case there is any conflict | ||||
| between this document and the comment at the start of a file, the comment shall | ||||
| take precedence. The only purpose of this document is to work around [a known | ||||
| technical limitation of pkg.go.dev's license checking tool when dealing with | ||||
| non-trivial project licenses][go75067]. | ||||
|  | ||||
| [go75067]: https://go.dev/issue/75067 | ||||
|  | ||||
| ### `BSD-3-Clause` ### | ||||
|  | ||||
| At time of writing, the following files and directories are licensed under the | ||||
| BSD-3-Clause license: | ||||
|  | ||||
|  * `doc.go` | ||||
|  * `join*.go` | ||||
|  * `vfs.go` | ||||
|  * `internal/consts/*.go` | ||||
|  * `pathrs-lite/internal/gocompat/*.go` | ||||
|  * `pathrs-lite/internal/kernelversion/*.go` | ||||
|  | ||||
| The text of the BSD-3-Clause license used by this project is the following (the | ||||
| text is also available from the [`LICENSE.BSD`](./LICENSE.BSD) file): | ||||
|  | ||||
| ``` | ||||
| Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. | ||||
| Copyright (C) 2017-2024 SUSE LLC. All rights reserved. | ||||
|  | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are | ||||
| met: | ||||
|  | ||||
|    * Redistributions of source code must retain the above copyright | ||||
| notice, this list of conditions and the following disclaimer. | ||||
|    * Redistributions in binary form must reproduce the above | ||||
| copyright notice, this list of conditions and the following disclaimer | ||||
| in the documentation and/or other materials provided with the | ||||
| distribution. | ||||
|    * Neither the name of Google Inc. nor the names of its | ||||
| contributors may be used to endorse or promote products derived from | ||||
| this software without specific prior written permission. | ||||
|  | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
| ``` | ||||
|  | ||||
| ### `MPL-2.0` ### | ||||
|  | ||||
| All other files (unless otherwise marked) are licensed under the Mozilla Public | ||||
| License (version 2.0). | ||||
|  | ||||
| The text of the Mozilla Public License (version 2.0) is the following (the text | ||||
| is also available from the [`LICENSE.MPL-2.0`](./LICENSE.MPL-2.0) file): | ||||
|  | ||||
| ``` | ||||
| Mozilla Public License Version 2.0 | ||||
| ================================== | ||||
|  | ||||
| 1. Definitions | ||||
| -------------- | ||||
|  | ||||
| 1.1. "Contributor" | ||||
|     means each individual or legal entity that creates, contributes to | ||||
|     the creation of, or owns Covered Software. | ||||
|  | ||||
| 1.2. "Contributor Version" | ||||
|     means the combination of the Contributions of others (if any) used | ||||
|     by a Contributor and that particular Contributor's Contribution. | ||||
|  | ||||
| 1.3. "Contribution" | ||||
|     means Covered Software of a particular Contributor. | ||||
|  | ||||
| 1.4. "Covered Software" | ||||
|     means Source Code Form to which the initial Contributor has attached | ||||
|     the notice in Exhibit A, the Executable Form of such Source Code | ||||
|     Form, and Modifications of such Source Code Form, in each case | ||||
|     including portions thereof. | ||||
|  | ||||
| 1.5. "Incompatible With Secondary Licenses" | ||||
|     means | ||||
|  | ||||
|     (a) that the initial Contributor has attached the notice described | ||||
|         in Exhibit B to the Covered Software; or | ||||
|  | ||||
|     (b) that the Covered Software was made available under the terms of | ||||
|         version 1.1 or earlier of the License, but not also under the | ||||
|         terms of a Secondary License. | ||||
|  | ||||
| 1.6. "Executable Form" | ||||
|     means any form of the work other than Source Code Form. | ||||
|  | ||||
| 1.7. "Larger Work" | ||||
|     means a work that combines Covered Software with other material, in | ||||
|     a separate file or files, that is not Covered Software. | ||||
|  | ||||
| 1.8. "License" | ||||
|     means this document. | ||||
|  | ||||
| 1.9. "Licensable" | ||||
|     means having the right to grant, to the maximum extent possible, | ||||
|     whether at the time of the initial grant or subsequently, any and | ||||
|     all of the rights conveyed by this License. | ||||
|  | ||||
| 1.10. "Modifications" | ||||
|     means any of the following: | ||||
|  | ||||
|     (a) any file in Source Code Form that results from an addition to, | ||||
|         deletion from, or modification of the contents of Covered | ||||
|         Software; or | ||||
|  | ||||
|     (b) any new file in Source Code Form that contains any Covered | ||||
|         Software. | ||||
|  | ||||
| 1.11. "Patent Claims" of a Contributor | ||||
|     means any patent claim(s), including without limitation, method, | ||||
|     process, and apparatus claims, in any patent Licensable by such | ||||
|     Contributor that would be infringed, but for the grant of the | ||||
|     License, by the making, using, selling, offering for sale, having | ||||
|     made, import, or transfer of either its Contributions or its | ||||
|     Contributor Version. | ||||
|  | ||||
| 1.12. "Secondary License" | ||||
|     means either the GNU General Public License, Version 2.0, the GNU | ||||
|     Lesser General Public License, Version 2.1, the GNU Affero General | ||||
|     Public License, Version 3.0, or any later versions of those | ||||
|     licenses. | ||||
|  | ||||
| 1.13. "Source Code Form" | ||||
|     means the form of the work preferred for making modifications. | ||||
|  | ||||
| 1.14. "You" (or "Your") | ||||
|     means an individual or a legal entity exercising rights under this | ||||
|     License. For legal entities, "You" includes any entity that | ||||
|     controls, is controlled by, or is under common control with You. For | ||||
|     purposes of this definition, "control" means (a) the power, direct | ||||
|     or indirect, to cause the direction or management of such entity, | ||||
|     whether by contract or otherwise, or (b) ownership of more than | ||||
|     fifty percent (50%) of the outstanding shares or beneficial | ||||
|     ownership of such entity. | ||||
|  | ||||
| 2. License Grants and Conditions | ||||
| -------------------------------- | ||||
|  | ||||
| 2.1. Grants | ||||
|  | ||||
| Each Contributor hereby grants You a world-wide, royalty-free, | ||||
| non-exclusive license: | ||||
|  | ||||
| (a) under intellectual property rights (other than patent or trademark) | ||||
|     Licensable by such Contributor to use, reproduce, make available, | ||||
|     modify, display, perform, distribute, and otherwise exploit its | ||||
|     Contributions, either on an unmodified basis, with Modifications, or | ||||
|     as part of a Larger Work; and | ||||
|  | ||||
| (b) under Patent Claims of such Contributor to make, use, sell, offer | ||||
|     for sale, have made, import, and otherwise transfer either its | ||||
|     Contributions or its Contributor Version. | ||||
|  | ||||
| 2.2. Effective Date | ||||
|  | ||||
| The licenses granted in Section 2.1 with respect to any Contribution | ||||
| become effective for each Contribution on the date the Contributor first | ||||
| distributes such Contribution. | ||||
|  | ||||
| 2.3. Limitations on Grant Scope | ||||
|  | ||||
| The licenses granted in this Section 2 are the only rights granted under | ||||
| this License. No additional rights or licenses will be implied from the | ||||
| distribution or licensing of Covered Software under this License. | ||||
| Notwithstanding Section 2.1(b) above, no patent license is granted by a | ||||
| Contributor: | ||||
|  | ||||
| (a) for any code that a Contributor has removed from Covered Software; | ||||
|     or | ||||
|  | ||||
| (b) for infringements caused by: (i) Your and any other third party's | ||||
|     modifications of Covered Software, or (ii) the combination of its | ||||
|     Contributions with other software (except as part of its Contributor | ||||
|     Version); or | ||||
|  | ||||
| (c) under Patent Claims infringed by Covered Software in the absence of | ||||
|     its Contributions. | ||||
|  | ||||
| This License does not grant any rights in the trademarks, service marks, | ||||
| or logos of any Contributor (except as may be necessary to comply with | ||||
| the notice requirements in Section 3.4). | ||||
|  | ||||
| 2.4. Subsequent Licenses | ||||
|  | ||||
| No Contributor makes additional grants as a result of Your choice to | ||||
| distribute the Covered Software under a subsequent version of this | ||||
| License (see Section 10.2) or under the terms of a Secondary License (if | ||||
| permitted under the terms of Section 3.3). | ||||
|  | ||||
| 2.5. Representation | ||||
|  | ||||
| Each Contributor represents that the Contributor believes its | ||||
| Contributions are its original creation(s) or it has sufficient rights | ||||
| to grant the rights to its Contributions conveyed by this License. | ||||
|  | ||||
| 2.6. Fair Use | ||||
|  | ||||
| This License is not intended to limit any rights You have under | ||||
| applicable copyright doctrines of fair use, fair dealing, or other | ||||
| equivalents. | ||||
|  | ||||
| 2.7. Conditions | ||||
|  | ||||
| Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted | ||||
| in Section 2.1. | ||||
|  | ||||
| 3. Responsibilities | ||||
| ------------------- | ||||
|  | ||||
| 3.1. Distribution of Source Form | ||||
|  | ||||
| All distribution of Covered Software in Source Code Form, including any | ||||
| Modifications that You create or to which You contribute, must be under | ||||
| the terms of this License. You must inform recipients that the Source | ||||
| Code Form of the Covered Software is governed by the terms of this | ||||
| License, and how they can obtain a copy of this License. You may not | ||||
| attempt to alter or restrict the recipients' rights in the Source Code | ||||
| Form. | ||||
|  | ||||
| 3.2. Distribution of Executable Form | ||||
|  | ||||
| If You distribute Covered Software in Executable Form then: | ||||
|  | ||||
| (a) such Covered Software must also be made available in Source Code | ||||
|     Form, as described in Section 3.1, and You must inform recipients of | ||||
|     the Executable Form how they can obtain a copy of such Source Code | ||||
|     Form by reasonable means in a timely manner, at a charge no more | ||||
|     than the cost of distribution to the recipient; and | ||||
|  | ||||
| (b) You may distribute such Executable Form under the terms of this | ||||
|     License, or sublicense it under different terms, provided that the | ||||
|     license for the Executable Form does not attempt to limit or alter | ||||
|     the recipients' rights in the Source Code Form under this License. | ||||
|  | ||||
| 3.3. Distribution of a Larger Work | ||||
|  | ||||
| You may create and distribute a Larger Work under terms of Your choice, | ||||
| provided that You also comply with the requirements of this License for | ||||
| the Covered Software. If the Larger Work is a combination of Covered | ||||
| Software with a work governed by one or more Secondary Licenses, and the | ||||
| Covered Software is not Incompatible With Secondary Licenses, this | ||||
| License permits You to additionally distribute such Covered Software | ||||
| under the terms of such Secondary License(s), so that the recipient of | ||||
| the Larger Work may, at their option, further distribute the Covered | ||||
| Software under the terms of either this License or such Secondary | ||||
| License(s). | ||||
|  | ||||
| 3.4. Notices | ||||
|  | ||||
| You may not remove or alter the substance of any license notices | ||||
| (including copyright notices, patent notices, disclaimers of warranty, | ||||
| or limitations of liability) contained within the Source Code Form of | ||||
| the Covered Software, except that You may alter any license notices to | ||||
| the extent required to remedy known factual inaccuracies. | ||||
|  | ||||
| 3.5. Application of Additional Terms | ||||
|  | ||||
| You may choose to offer, and to charge a fee for, warranty, support, | ||||
| indemnity or liability obligations to one or more recipients of Covered | ||||
| Software. However, You may do so only on Your own behalf, and not on | ||||
| behalf of any Contributor. You must make it absolutely clear that any | ||||
| such warranty, support, indemnity, or liability obligation is offered by | ||||
| You alone, and You hereby agree to indemnify every Contributor for any | ||||
| liability incurred by such Contributor as a result of warranty, support, | ||||
| indemnity or liability terms You offer. You may include additional | ||||
| disclaimers of warranty and limitations of liability specific to any | ||||
| jurisdiction. | ||||
|  | ||||
| 4. Inability to Comply Due to Statute or Regulation | ||||
| --------------------------------------------------- | ||||
|  | ||||
| If it is impossible for You to comply with any of the terms of this | ||||
| License with respect to some or all of the Covered Software due to | ||||
| statute, judicial order, or regulation then You must: (a) comply with | ||||
| the terms of this License to the maximum extent possible; and (b) | ||||
| describe the limitations and the code they affect. Such description must | ||||
| be placed in a text file included with all distributions of the Covered | ||||
| Software under this License. Except to the extent prohibited by statute | ||||
| or regulation, such description must be sufficiently detailed for a | ||||
| recipient of ordinary skill to be able to understand it. | ||||
|  | ||||
| 5. Termination | ||||
| -------------- | ||||
|  | ||||
| 5.1. The rights granted under this License will terminate automatically | ||||
| if You fail to comply with any of its terms. However, if You become | ||||
| compliant, then the rights granted under this License from a particular | ||||
| Contributor are reinstated (a) provisionally, unless and until such | ||||
| Contributor explicitly and finally terminates Your grants, and (b) on an | ||||
| ongoing basis, if such Contributor fails to notify You of the | ||||
| non-compliance by some reasonable means prior to 60 days after You have | ||||
| come back into compliance. Moreover, Your grants from a particular | ||||
| Contributor are reinstated on an ongoing basis if such Contributor | ||||
| notifies You of the non-compliance by some reasonable means, this is the | ||||
| first time You have received notice of non-compliance with this License | ||||
| from such Contributor, and You become compliant prior to 30 days after | ||||
| Your receipt of the notice. | ||||
|  | ||||
| 5.2. If You initiate litigation against any entity by asserting a patent | ||||
| infringement claim (excluding declaratory judgment actions, | ||||
| counter-claims, and cross-claims) alleging that a Contributor Version | ||||
| directly or indirectly infringes any patent, then the rights granted to | ||||
| You by any and all Contributors for the Covered Software under Section | ||||
| 2.1 of this License shall terminate. | ||||
|  | ||||
| 5.3. In the event of termination under Sections 5.1 or 5.2 above, all | ||||
| end user license agreements (excluding distributors and resellers) which | ||||
| have been validly granted by You or Your distributors under this License | ||||
| prior to termination shall survive termination. | ||||
|  | ||||
| ************************************************************************ | ||||
| *                                                                      * | ||||
| *  6. Disclaimer of Warranty                                           * | ||||
| *  -------------------------                                           * | ||||
| *                                                                      * | ||||
| *  Covered Software is provided under this License on an "as is"       * | ||||
| *  basis, without warranty of any kind, either expressed, implied, or  * | ||||
| *  statutory, including, without limitation, warranties that the       * | ||||
| *  Covered Software is free of defects, merchantable, fit for a        * | ||||
| *  particular purpose or non-infringing. The entire risk as to the     * | ||||
| *  quality and performance of the Covered Software is with You.        * | ||||
| *  Should any Covered Software prove defective in any respect, You     * | ||||
| *  (not any Contributor) assume the cost of any necessary servicing,   * | ||||
| *  repair, or correction. This disclaimer of warranty constitutes an   * | ||||
| *  essential part of this License. No use of any Covered Software is   * | ||||
| *  authorized under this License except under this disclaimer.         * | ||||
| *                                                                      * | ||||
| ************************************************************************ | ||||
|  | ||||
| ************************************************************************ | ||||
| *                                                                      * | ||||
| *  7. Limitation of Liability                                          * | ||||
| *  --------------------------                                          * | ||||
| *                                                                      * | ||||
| *  Under no circumstances and under no legal theory, whether tort      * | ||||
| *  (including negligence), contract, or otherwise, shall any           * | ||||
| *  Contributor, or anyone who distributes Covered Software as          * | ||||
| *  permitted above, be liable to You for any direct, indirect,         * | ||||
| *  special, incidental, or consequential damages of any character      * | ||||
| *  including, without limitation, damages for lost profits, loss of    * | ||||
| *  goodwill, work stoppage, computer failure or malfunction, or any    * | ||||
| *  and all other commercial damages or losses, even if such party      * | ||||
| *  shall have been informed of the possibility of such damages. This   * | ||||
| *  limitation of liability shall not apply to liability for death or   * | ||||
| *  personal injury resulting from such party's negligence to the       * | ||||
| *  extent applicable law prohibits such limitation. Some               * | ||||
| *  jurisdictions do not allow the exclusion or limitation of           * | ||||
| *  incidental or consequential damages, so this exclusion and          * | ||||
| *  limitation may not apply to You.                                    * | ||||
| *                                                                      * | ||||
| ************************************************************************ | ||||
|  | ||||
| 8. Litigation | ||||
| ------------- | ||||
|  | ||||
| Any litigation relating to this License may be brought only in the | ||||
| courts of a jurisdiction where the defendant maintains its principal | ||||
| place of business and such litigation shall be governed by laws of that | ||||
| jurisdiction, without reference to its conflict-of-law provisions. | ||||
| Nothing in this Section shall prevent a party's ability to bring | ||||
| cross-claims or counter-claims. | ||||
|  | ||||
| 9. Miscellaneous | ||||
| ---------------- | ||||
|  | ||||
| This License represents the complete agreement concerning the subject | ||||
| matter hereof. If any provision of this License is held to be | ||||
| unenforceable, such provision shall be reformed only to the extent | ||||
| necessary to make it enforceable. Any law or regulation which provides | ||||
| that the language of a contract shall be construed against the drafter | ||||
| shall not be used to construe this License against a Contributor. | ||||
|  | ||||
| 10. Versions of the License | ||||
| --------------------------- | ||||
|  | ||||
| 10.1. New Versions | ||||
|  | ||||
| Mozilla Foundation is the license steward. Except as provided in Section | ||||
| 10.3, no one other than the license steward has the right to modify or | ||||
| publish new versions of this License. Each version will be given a | ||||
| distinguishing version number. | ||||
|  | ||||
| 10.2. Effect of New Versions | ||||
|  | ||||
| You may distribute the Covered Software under the terms of the version | ||||
| of the License under which You originally received the Covered Software, | ||||
| or under the terms of any subsequent version published by the license | ||||
| steward. | ||||
|  | ||||
| 10.3. Modified Versions | ||||
|  | ||||
| If you create software not governed by this License, and you want to | ||||
| create a new license for such software, you may create and use a | ||||
| modified version of this License if you rename the license and remove | ||||
| any references to the name of the license steward (except to note that | ||||
| such modified license differs from this License). | ||||
|  | ||||
| 10.4. Distributing Source Code Form that is Incompatible With Secondary | ||||
| Licenses | ||||
|  | ||||
| If You choose to distribute Source Code Form that is Incompatible With | ||||
| Secondary Licenses under the terms of this version of the License, the | ||||
| notice described in Exhibit B of this License must be attached. | ||||
|  | ||||
| Exhibit A - Source Code Form License Notice | ||||
| ------------------------------------------- | ||||
|  | ||||
|   This Source Code Form is subject to the terms of the Mozilla Public | ||||
|   License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|   file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| If it is not possible or desirable to put the notice in a particular | ||||
| file, then You may include the notice in a location (such as a LICENSE | ||||
| file in a relevant directory) where a recipient would be likely to look | ||||
| for such a notice. | ||||
|  | ||||
| You may add additional accurate notices of copyright ownership. | ||||
|  | ||||
| Exhibit B - "Incompatible With Secondary Licenses" Notice | ||||
| --------------------------------------------------------- | ||||
|  | ||||
|   This Source Code Form is "Incompatible With Secondary Licenses", as | ||||
|   defined by the Mozilla Public License, v. 2.0. | ||||
| ``` | ||||
							
								
								
									
										373
									
								
								vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										373
									
								
								vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,373 @@ | ||||
| Mozilla Public License Version 2.0 | ||||
| ================================== | ||||
|  | ||||
| 1. Definitions | ||||
| -------------- | ||||
|  | ||||
| 1.1. "Contributor" | ||||
|     means each individual or legal entity that creates, contributes to | ||||
|     the creation of, or owns Covered Software. | ||||
|  | ||||
| 1.2. "Contributor Version" | ||||
|     means the combination of the Contributions of others (if any) used | ||||
|     by a Contributor and that particular Contributor's Contribution. | ||||
|  | ||||
| 1.3. "Contribution" | ||||
|     means Covered Software of a particular Contributor. | ||||
|  | ||||
| 1.4. "Covered Software" | ||||
|     means Source Code Form to which the initial Contributor has attached | ||||
|     the notice in Exhibit A, the Executable Form of such Source Code | ||||
|     Form, and Modifications of such Source Code Form, in each case | ||||
|     including portions thereof. | ||||
|  | ||||
| 1.5. "Incompatible With Secondary Licenses" | ||||
|     means | ||||
|  | ||||
|     (a) that the initial Contributor has attached the notice described | ||||
|         in Exhibit B to the Covered Software; or | ||||
|  | ||||
|     (b) that the Covered Software was made available under the terms of | ||||
|         version 1.1 or earlier of the License, but not also under the | ||||
|         terms of a Secondary License. | ||||
|  | ||||
| 1.6. "Executable Form" | ||||
|     means any form of the work other than Source Code Form. | ||||
|  | ||||
| 1.7. "Larger Work" | ||||
|     means a work that combines Covered Software with other material, in | ||||
|     a separate file or files, that is not Covered Software. | ||||
|  | ||||
| 1.8. "License" | ||||
|     means this document. | ||||
|  | ||||
| 1.9. "Licensable" | ||||
|     means having the right to grant, to the maximum extent possible, | ||||
|     whether at the time of the initial grant or subsequently, any and | ||||
|     all of the rights conveyed by this License. | ||||
|  | ||||
| 1.10. "Modifications" | ||||
|     means any of the following: | ||||
|  | ||||
|     (a) any file in Source Code Form that results from an addition to, | ||||
|         deletion from, or modification of the contents of Covered | ||||
|         Software; or | ||||
|  | ||||
|     (b) any new file in Source Code Form that contains any Covered | ||||
|         Software. | ||||
|  | ||||
| 1.11. "Patent Claims" of a Contributor | ||||
|     means any patent claim(s), including without limitation, method, | ||||
|     process, and apparatus claims, in any patent Licensable by such | ||||
|     Contributor that would be infringed, but for the grant of the | ||||
|     License, by the making, using, selling, offering for sale, having | ||||
|     made, import, or transfer of either its Contributions or its | ||||
|     Contributor Version. | ||||
|  | ||||
| 1.12. "Secondary License" | ||||
|     means either the GNU General Public License, Version 2.0, the GNU | ||||
|     Lesser General Public License, Version 2.1, the GNU Affero General | ||||
|     Public License, Version 3.0, or any later versions of those | ||||
|     licenses. | ||||
|  | ||||
| 1.13. "Source Code Form" | ||||
|     means the form of the work preferred for making modifications. | ||||
|  | ||||
| 1.14. "You" (or "Your") | ||||
|     means an individual or a legal entity exercising rights under this | ||||
|     License. For legal entities, "You" includes any entity that | ||||
|     controls, is controlled by, or is under common control with You. For | ||||
|     purposes of this definition, "control" means (a) the power, direct | ||||
|     or indirect, to cause the direction or management of such entity, | ||||
|     whether by contract or otherwise, or (b) ownership of more than | ||||
|     fifty percent (50%) of the outstanding shares or beneficial | ||||
|     ownership of such entity. | ||||
|  | ||||
| 2. License Grants and Conditions | ||||
| -------------------------------- | ||||
|  | ||||
| 2.1. Grants | ||||
|  | ||||
| Each Contributor hereby grants You a world-wide, royalty-free, | ||||
| non-exclusive license: | ||||
|  | ||||
| (a) under intellectual property rights (other than patent or trademark) | ||||
|     Licensable by such Contributor to use, reproduce, make available, | ||||
|     modify, display, perform, distribute, and otherwise exploit its | ||||
|     Contributions, either on an unmodified basis, with Modifications, or | ||||
|     as part of a Larger Work; and | ||||
|  | ||||
| (b) under Patent Claims of such Contributor to make, use, sell, offer | ||||
|     for sale, have made, import, and otherwise transfer either its | ||||
|     Contributions or its Contributor Version. | ||||
|  | ||||
| 2.2. Effective Date | ||||
|  | ||||
| The licenses granted in Section 2.1 with respect to any Contribution | ||||
| become effective for each Contribution on the date the Contributor first | ||||
| distributes such Contribution. | ||||
|  | ||||
| 2.3. Limitations on Grant Scope | ||||
|  | ||||
| The licenses granted in this Section 2 are the only rights granted under | ||||
| this License. No additional rights or licenses will be implied from the | ||||
| distribution or licensing of Covered Software under this License. | ||||
| Notwithstanding Section 2.1(b) above, no patent license is granted by a | ||||
| Contributor: | ||||
|  | ||||
| (a) for any code that a Contributor has removed from Covered Software; | ||||
|     or | ||||
|  | ||||
| (b) for infringements caused by: (i) Your and any other third party's | ||||
|     modifications of Covered Software, or (ii) the combination of its | ||||
|     Contributions with other software (except as part of its Contributor | ||||
|     Version); or | ||||
|  | ||||
| (c) under Patent Claims infringed by Covered Software in the absence of | ||||
|     its Contributions. | ||||
|  | ||||
| This License does not grant any rights in the trademarks, service marks, | ||||
| or logos of any Contributor (except as may be necessary to comply with | ||||
| the notice requirements in Section 3.4). | ||||
|  | ||||
| 2.4. Subsequent Licenses | ||||
|  | ||||
| No Contributor makes additional grants as a result of Your choice to | ||||
| distribute the Covered Software under a subsequent version of this | ||||
| License (see Section 10.2) or under the terms of a Secondary License (if | ||||
| permitted under the terms of Section 3.3). | ||||
|  | ||||
| 2.5. Representation | ||||
|  | ||||
| Each Contributor represents that the Contributor believes its | ||||
| Contributions are its original creation(s) or it has sufficient rights | ||||
| to grant the rights to its Contributions conveyed by this License. | ||||
|  | ||||
| 2.6. Fair Use | ||||
|  | ||||
| This License is not intended to limit any rights You have under | ||||
| applicable copyright doctrines of fair use, fair dealing, or other | ||||
| equivalents. | ||||
|  | ||||
| 2.7. Conditions | ||||
|  | ||||
| Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted | ||||
| in Section 2.1. | ||||
|  | ||||
| 3. Responsibilities | ||||
| ------------------- | ||||
|  | ||||
| 3.1. Distribution of Source Form | ||||
|  | ||||
| All distribution of Covered Software in Source Code Form, including any | ||||
| Modifications that You create or to which You contribute, must be under | ||||
| the terms of this License. You must inform recipients that the Source | ||||
| Code Form of the Covered Software is governed by the terms of this | ||||
| License, and how they can obtain a copy of this License. You may not | ||||
| attempt to alter or restrict the recipients' rights in the Source Code | ||||
| Form. | ||||
|  | ||||
| 3.2. Distribution of Executable Form | ||||
|  | ||||
| If You distribute Covered Software in Executable Form then: | ||||
|  | ||||
| (a) such Covered Software must also be made available in Source Code | ||||
|     Form, as described in Section 3.1, and You must inform recipients of | ||||
|     the Executable Form how they can obtain a copy of such Source Code | ||||
|     Form by reasonable means in a timely manner, at a charge no more | ||||
|     than the cost of distribution to the recipient; and | ||||
|  | ||||
| (b) You may distribute such Executable Form under the terms of this | ||||
|     License, or sublicense it under different terms, provided that the | ||||
|     license for the Executable Form does not attempt to limit or alter | ||||
|     the recipients' rights in the Source Code Form under this License. | ||||
|  | ||||
| 3.3. Distribution of a Larger Work | ||||
|  | ||||
| You may create and distribute a Larger Work under terms of Your choice, | ||||
| provided that You also comply with the requirements of this License for | ||||
| the Covered Software. If the Larger Work is a combination of Covered | ||||
| Software with a work governed by one or more Secondary Licenses, and the | ||||
| Covered Software is not Incompatible With Secondary Licenses, this | ||||
| License permits You to additionally distribute such Covered Software | ||||
| under the terms of such Secondary License(s), so that the recipient of | ||||
| the Larger Work may, at their option, further distribute the Covered | ||||
| Software under the terms of either this License or such Secondary | ||||
| License(s). | ||||
|  | ||||
| 3.4. Notices | ||||
|  | ||||
| You may not remove or alter the substance of any license notices | ||||
| (including copyright notices, patent notices, disclaimers of warranty, | ||||
| or limitations of liability) contained within the Source Code Form of | ||||
| the Covered Software, except that You may alter any license notices to | ||||
| the extent required to remedy known factual inaccuracies. | ||||
|  | ||||
| 3.5. Application of Additional Terms | ||||
|  | ||||
| You may choose to offer, and to charge a fee for, warranty, support, | ||||
| indemnity or liability obligations to one or more recipients of Covered | ||||
| Software. However, You may do so only on Your own behalf, and not on | ||||
| behalf of any Contributor. You must make it absolutely clear that any | ||||
| such warranty, support, indemnity, or liability obligation is offered by | ||||
| You alone, and You hereby agree to indemnify every Contributor for any | ||||
| liability incurred by such Contributor as a result of warranty, support, | ||||
| indemnity or liability terms You offer. You may include additional | ||||
| disclaimers of warranty and limitations of liability specific to any | ||||
| jurisdiction. | ||||
|  | ||||
| 4. Inability to Comply Due to Statute or Regulation | ||||
| --------------------------------------------------- | ||||
|  | ||||
| If it is impossible for You to comply with any of the terms of this | ||||
| License with respect to some or all of the Covered Software due to | ||||
| statute, judicial order, or regulation then You must: (a) comply with | ||||
| the terms of this License to the maximum extent possible; and (b) | ||||
| describe the limitations and the code they affect. Such description must | ||||
| be placed in a text file included with all distributions of the Covered | ||||
| Software under this License. Except to the extent prohibited by statute | ||||
| or regulation, such description must be sufficiently detailed for a | ||||
| recipient of ordinary skill to be able to understand it. | ||||
|  | ||||
| 5. Termination | ||||
| -------------- | ||||
|  | ||||
| 5.1. The rights granted under this License will terminate automatically | ||||
| if You fail to comply with any of its terms. However, if You become | ||||
| compliant, then the rights granted under this License from a particular | ||||
| Contributor are reinstated (a) provisionally, unless and until such | ||||
| Contributor explicitly and finally terminates Your grants, and (b) on an | ||||
| ongoing basis, if such Contributor fails to notify You of the | ||||
| non-compliance by some reasonable means prior to 60 days after You have | ||||
| come back into compliance. Moreover, Your grants from a particular | ||||
| Contributor are reinstated on an ongoing basis if such Contributor | ||||
| notifies You of the non-compliance by some reasonable means, this is the | ||||
| first time You have received notice of non-compliance with this License | ||||
| from such Contributor, and You become compliant prior to 30 days after | ||||
| Your receipt of the notice. | ||||
|  | ||||
| 5.2. If You initiate litigation against any entity by asserting a patent | ||||
| infringement claim (excluding declaratory judgment actions, | ||||
| counter-claims, and cross-claims) alleging that a Contributor Version | ||||
| directly or indirectly infringes any patent, then the rights granted to | ||||
| You by any and all Contributors for the Covered Software under Section | ||||
| 2.1 of this License shall terminate. | ||||
|  | ||||
| 5.3. In the event of termination under Sections 5.1 or 5.2 above, all | ||||
| end user license agreements (excluding distributors and resellers) which | ||||
| have been validly granted by You or Your distributors under this License | ||||
| prior to termination shall survive termination. | ||||
|  | ||||
| ************************************************************************ | ||||
| *                                                                      * | ||||
| *  6. Disclaimer of Warranty                                           * | ||||
| *  -------------------------                                           * | ||||
| *                                                                      * | ||||
| *  Covered Software is provided under this License on an "as is"       * | ||||
| *  basis, without warranty of any kind, either expressed, implied, or  * | ||||
| *  statutory, including, without limitation, warranties that the       * | ||||
| *  Covered Software is free of defects, merchantable, fit for a        * | ||||
| *  particular purpose or non-infringing. The entire risk as to the     * | ||||
| *  quality and performance of the Covered Software is with You.        * | ||||
| *  Should any Covered Software prove defective in any respect, You     * | ||||
| *  (not any Contributor) assume the cost of any necessary servicing,   * | ||||
| *  repair, or correction. This disclaimer of warranty constitutes an   * | ||||
| *  essential part of this License. No use of any Covered Software is   * | ||||
| *  authorized under this License except under this disclaimer.         * | ||||
| *                                                                      * | ||||
| ************************************************************************ | ||||
|  | ||||
| ************************************************************************ | ||||
| *                                                                      * | ||||
| *  7. Limitation of Liability                                          * | ||||
| *  --------------------------                                          * | ||||
| *                                                                      * | ||||
| *  Under no circumstances and under no legal theory, whether tort      * | ||||
| *  (including negligence), contract, or otherwise, shall any           * | ||||
| *  Contributor, or anyone who distributes Covered Software as          * | ||||
| *  permitted above, be liable to You for any direct, indirect,         * | ||||
| *  special, incidental, or consequential damages of any character      * | ||||
| *  including, without limitation, damages for lost profits, loss of    * | ||||
| *  goodwill, work stoppage, computer failure or malfunction, or any    * | ||||
| *  and all other commercial damages or losses, even if such party      * | ||||
| *  shall have been informed of the possibility of such damages. This   * | ||||
| *  limitation of liability shall not apply to liability for death or   * | ||||
| *  personal injury resulting from such party's negligence to the       * | ||||
| *  extent applicable law prohibits such limitation. Some               * | ||||
| *  jurisdictions do not allow the exclusion or limitation of           * | ||||
| *  incidental or consequential damages, so this exclusion and          * | ||||
| *  limitation may not apply to You.                                    * | ||||
| *                                                                      * | ||||
| ************************************************************************ | ||||
|  | ||||
| 8. Litigation | ||||
| ------------- | ||||
|  | ||||
| Any litigation relating to this License may be brought only in the | ||||
| courts of a jurisdiction where the defendant maintains its principal | ||||
| place of business and such litigation shall be governed by laws of that | ||||
| jurisdiction, without reference to its conflict-of-law provisions. | ||||
| Nothing in this Section shall prevent a party's ability to bring | ||||
| cross-claims or counter-claims. | ||||
|  | ||||
| 9. Miscellaneous | ||||
| ---------------- | ||||
|  | ||||
| This License represents the complete agreement concerning the subject | ||||
| matter hereof. If any provision of this License is held to be | ||||
| unenforceable, such provision shall be reformed only to the extent | ||||
| necessary to make it enforceable. Any law or regulation which provides | ||||
| that the language of a contract shall be construed against the drafter | ||||
| shall not be used to construe this License against a Contributor. | ||||
|  | ||||
| 10. Versions of the License | ||||
| --------------------------- | ||||
|  | ||||
| 10.1. New Versions | ||||
|  | ||||
| Mozilla Foundation is the license steward. Except as provided in Section | ||||
| 10.3, no one other than the license steward has the right to modify or | ||||
| publish new versions of this License. Each version will be given a | ||||
| distinguishing version number. | ||||
|  | ||||
| 10.2. Effect of New Versions | ||||
|  | ||||
| You may distribute the Covered Software under the terms of the version | ||||
| of the License under which You originally received the Covered Software, | ||||
| or under the terms of any subsequent version published by the license | ||||
| steward. | ||||
|  | ||||
| 10.3. Modified Versions | ||||
|  | ||||
| If you create software not governed by this License, and you want to | ||||
| create a new license for such software, you may create and use a | ||||
| modified version of this License if you rename the license and remove | ||||
| any references to the name of the license steward (except to note that | ||||
| such modified license differs from this License). | ||||
|  | ||||
| 10.4. Distributing Source Code Form that is Incompatible With Secondary | ||||
| Licenses | ||||
|  | ||||
| If You choose to distribute Source Code Form that is Incompatible With | ||||
| Secondary Licenses under the terms of this version of the License, the | ||||
| notice described in Exhibit B of this License must be attached. | ||||
|  | ||||
| Exhibit A - Source Code Form License Notice | ||||
| ------------------------------------------- | ||||
|  | ||||
|   This Source Code Form is subject to the terms of the Mozilla Public | ||||
|   License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|   file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| If it is not possible or desirable to put the notice in a particular | ||||
| file, then You may include the notice in a location (such as a LICENSE | ||||
| file in a relevant directory) where a recipient would be likely to look | ||||
| for such a notice. | ||||
|  | ||||
| You may add additional accurate notices of copyright ownership. | ||||
|  | ||||
| Exhibit B - "Incompatible With Secondary Licenses" Notice | ||||
| --------------------------------------------------------- | ||||
|  | ||||
|   This Source Code Form is "Incompatible With Secondary Licenses", as | ||||
|   defined by the Mozilla Public License, v. 2.0. | ||||
							
								
								
									
										21
									
								
								vendor/github.com/cyphar/filepath-securejoin/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/cyphar/filepath-securejoin/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -67,7 +67,8 @@ func SecureJoin(root, unsafePath string) (string, error) { | ||||
| [libpathrs]: https://github.com/openSUSE/libpathrs | ||||
| [go#20126]: https://github.com/golang/go/issues/20126 | ||||
|  | ||||
| ### New API ### | ||||
| ### <a name="new-api" /> New API ### | ||||
| [#new-api]: #new-api | ||||
|  | ||||
| While we recommend users switch to [libpathrs][libpathrs] as soon as it has a | ||||
| stable release, some methods implemented by libpathrs have been ported to this | ||||
| @ -165,5 +166,19 @@ after `MkdirAll`). | ||||
|  | ||||
| ### License ### | ||||
|  | ||||
| The license of this project is the same as Go, which is a BSD 3-clause license | ||||
| available in the `LICENSE` file. | ||||
| `SPDX-License-Identifier: BSD-3-Clause AND MPL-2.0` | ||||
|  | ||||
| Some of the code in this project is derived from Go, and is licensed under a | ||||
| BSD 3-clause license (available in `LICENSE.BSD`). Other files (many of which | ||||
| are derived from [libpathrs][libpathrs]) are licensed under the Mozilla Public | ||||
| License version 2.0 (available in `LICENSE.MPL-2.0`). If you are using the | ||||
| ["New API" described above][#new-api], you are probably using code from files | ||||
| released under this license. | ||||
|  | ||||
| Every source file in this project has a copyright header describing its | ||||
| license. Please check the license headers of each file to see what license | ||||
| applies to it. | ||||
|  | ||||
| See [COPYING.md](./COPYING.md) for some more details. | ||||
|  | ||||
| [umoci]: https://github.com/opencontainers/umoci | ||||
|  | ||||
							
								
								
									
										2
									
								
								vendor/github.com/cyphar/filepath-securejoin/VERSION
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/cyphar/filepath-securejoin/VERSION
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1 +1 @@ | ||||
| 0.4.1 | ||||
| 0.5.0 | ||||
|  | ||||
							
								
								
									
										29
									
								
								vendor/github.com/cyphar/filepath-securejoin/codecov.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								vendor/github.com/cyphar/filepath-securejoin/codecov.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| # SPDX-License-Identifier: MPL-2.0 | ||||
|  | ||||
| # Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| # Copyright (C) 2025 SUSE LLC | ||||
| # | ||||
| # This Source Code Form is subject to the terms of the Mozilla Public | ||||
| # License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| # file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| comment: | ||||
|   layout: "condensed_header, reach, diff, components, condensed_files, condensed_footer" | ||||
|   require_changes: true | ||||
|   branches: | ||||
|     - main | ||||
|  | ||||
| coverage: | ||||
|   range: 60..100 | ||||
|   status: | ||||
|     project: | ||||
|       default: | ||||
|         target: 85% | ||||
|         threshold: 0% | ||||
|     patch: | ||||
|       default: | ||||
|         target: auto | ||||
|         informational: true | ||||
|  | ||||
| github_checks: | ||||
|   annotations: false | ||||
							
								
								
									
										48
									
								
								vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
|  | ||||
| //go:build linux | ||||
|  | ||||
| // Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| // Copyright (C) 2024-2025 SUSE LLC | ||||
| // | ||||
| // This Source Code Form is subject to the terms of the Mozilla Public | ||||
| // License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| package securejoin | ||||
|  | ||||
| import ( | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// MkdirAll is a wrapper around [pathrs.MkdirAll]. | ||||
| 	// | ||||
| 	// Deprecated: You should use [pathrs.MkdirAll] directly instead. This | ||||
| 	// wrapper will be removed in filepath-securejoin v0.6. | ||||
| 	MkdirAll = pathrs.MkdirAll | ||||
|  | ||||
| 	// MkdirAllHandle is a wrapper around [pathrs.MkdirAllHandle]. | ||||
| 	// | ||||
| 	// Deprecated: You should use [pathrs.MkdirAllHandle] directly instead. | ||||
| 	// This wrapper will be removed in filepath-securejoin v0.6. | ||||
| 	MkdirAllHandle = pathrs.MkdirAllHandle | ||||
|  | ||||
| 	// OpenInRoot is a wrapper around [pathrs.OpenInRoot]. | ||||
| 	// | ||||
| 	// Deprecated: You should use [pathrs.OpenInRoot] directly instead. This | ||||
| 	// wrapper will be removed in filepath-securejoin v0.6. | ||||
| 	OpenInRoot = pathrs.OpenInRoot | ||||
|  | ||||
| 	// OpenatInRoot is a wrapper around [pathrs.OpenatInRoot]. | ||||
| 	// | ||||
| 	// Deprecated: You should use [pathrs.OpenatInRoot] directly instead. This | ||||
| 	// wrapper will be removed in filepath-securejoin v0.6. | ||||
| 	OpenatInRoot = pathrs.OpenatInRoot | ||||
|  | ||||
| 	// Reopen is a wrapper around [pathrs.Reopen]. | ||||
| 	// | ||||
| 	// Deprecated: You should use [pathrs.Reopen] directly instead. This | ||||
| 	// wrapper will be removed in filepath-securejoin v0.6. | ||||
| 	Reopen = pathrs.Reopen | ||||
| ) | ||||
							
								
								
									
										34
									
								
								vendor/github.com/cyphar/filepath-securejoin/doc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										34
									
								
								vendor/github.com/cyphar/filepath-securejoin/doc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,3 +1,5 @@ | ||||
| // SPDX-License-Identifier: BSD-3-Clause | ||||
|  | ||||
| // Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. | ||||
| // Copyright (C) 2017-2024 SUSE LLC. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| @ -14,14 +16,13 @@ | ||||
| // **not** safe against race conditions where an attacker changes the | ||||
| // filesystem after (or during) the [SecureJoin] operation. | ||||
| // | ||||
| // The new API is made up of [OpenInRoot] and [MkdirAll] (and derived | ||||
| // functions). These are safe against racing attackers and have several other | ||||
| // protections that are not provided by the legacy API. There are many more | ||||
| // operations that most programs expect to be able to do safely, but we do not | ||||
| // provide explicit support for them because we want to encourage users to | ||||
| // switch to [libpathrs](https://github.com/openSUSE/libpathrs) which is a | ||||
| // cross-language next-generation library that is entirely designed around | ||||
| // operating on paths safely. | ||||
| // The new API is available in the [pathrs-lite] subpackage, and provide | ||||
| // protections against racing attackers as well as several other key | ||||
| // protections against attacks often seen by container runtimes. As the name | ||||
| // suggests, [pathrs-lite] is a stripped down (pure Go) reimplementation of | ||||
| // [libpathrs]. The main APIs provided are [OpenInRoot], [MkdirAll], and | ||||
| // [procfs.Handle] -- other APIs are not planned to be ported. The long-term | ||||
| // goal is for users to migrate to [libpathrs] which is more fully-featured. | ||||
| // | ||||
| // securejoin has been used by several container runtimes (Docker, runc, | ||||
| // Kubernetes, etc) for quite a few years as a de-facto standard for operating | ||||
| @ -31,9 +32,16 @@ | ||||
| // API as soon as possible (or even better, switch to libpathrs). | ||||
| // | ||||
| // This project was initially intended to be included in the Go standard | ||||
| // library, but [it was rejected](https://go.dev/issue/20126). There is now a | ||||
| // [new Go proposal](https://go.dev/issue/67002) for a safe path resolution API | ||||
| // that shares some of the goals of filepath-securejoin. However, that design | ||||
| // is intended to work like `openat2(RESOLVE_BENEATH)` which does not fit the | ||||
| // usecase of container runtimes and most system tools. | ||||
| // library, but it was rejected (see https://go.dev/issue/20126). Much later, | ||||
| // [os.Root] was added to the Go stdlib that shares some of the goals of | ||||
| // filepath-securejoin. However, its design is intended to work like | ||||
| // openat2(RESOLVE_BENEATH) which does not fit the usecase of container | ||||
| // runtimes and most system tools. | ||||
| // | ||||
| // [pathrs-lite]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite | ||||
| // [libpathrs]: https://github.com/openSUSE/libpathrs | ||||
| // [OpenInRoot]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite#OpenInRoot | ||||
| // [MkdirAll]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite#MkdirAll | ||||
| // [procfs.Handle]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs#Handle | ||||
| // [os.Root]: https:///pkg.go.dev/os#Root | ||||
| package securejoin | ||||
|  | ||||
							
								
								
									
										32
									
								
								vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_go121.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										32
									
								
								vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_go121.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,32 +0,0 @@ | ||||
| //go:build linux && go1.21 | ||||
|  | ||||
| // Copyright (C) 2024 SUSE LLC. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package securejoin | ||||
|  | ||||
| import ( | ||||
| 	"slices" | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| func slices_DeleteFunc[S ~[]E, E any](slice S, delFn func(E) bool) S { | ||||
| 	return slices.DeleteFunc(slice, delFn) | ||||
| } | ||||
|  | ||||
| func slices_Contains[S ~[]E, E comparable](slice S, val E) bool { | ||||
| 	return slices.Contains(slice, val) | ||||
| } | ||||
|  | ||||
| func slices_Clone[S ~[]E, E any](slice S) S { | ||||
| 	return slices.Clone(slice) | ||||
| } | ||||
|  | ||||
| func sync_OnceValue[T any](f func() T) func() T { | ||||
| 	return sync.OnceValue(f) | ||||
| } | ||||
|  | ||||
| func sync_OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { | ||||
| 	return sync.OnceValues(f) | ||||
| } | ||||
							
								
								
									
										124
									
								
								vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_unsupported.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										124
									
								
								vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_unsupported.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,124 +0,0 @@ | ||||
| //go:build linux && !go1.21 | ||||
|  | ||||
| // Copyright (C) 2024 SUSE LLC. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package securejoin | ||||
|  | ||||
| import ( | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| // These are very minimal implementations of functions that appear in Go 1.21's | ||||
| // stdlib, included so that we can build on older Go versions. Most are | ||||
| // borrowed directly from the stdlib, and a few are modified to be "obviously | ||||
| // correct" without needing to copy too many other helpers. | ||||
|  | ||||
| // clearSlice is equivalent to the builtin clear from Go 1.21. | ||||
| // Copied from the Go 1.24 stdlib implementation. | ||||
| func clearSlice[S ~[]E, E any](slice S) { | ||||
| 	var zero E | ||||
| 	for i := range slice { | ||||
| 		slice[i] = zero | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Copied from the Go 1.24 stdlib implementation. | ||||
| func slices_IndexFunc[S ~[]E, E any](s S, f func(E) bool) int { | ||||
| 	for i := range s { | ||||
| 		if f(s[i]) { | ||||
| 			return i | ||||
| 		} | ||||
| 	} | ||||
| 	return -1 | ||||
| } | ||||
|  | ||||
| // Copied from the Go 1.24 stdlib implementation. | ||||
| func slices_DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S { | ||||
| 	i := slices_IndexFunc(s, del) | ||||
| 	if i == -1 { | ||||
| 		return s | ||||
| 	} | ||||
| 	// Don't start copying elements until we find one to delete. | ||||
| 	for j := i + 1; j < len(s); j++ { | ||||
| 		if v := s[j]; !del(v) { | ||||
| 			s[i] = v | ||||
| 			i++ | ||||
| 		} | ||||
| 	} | ||||
| 	clearSlice(s[i:]) // zero/nil out the obsolete elements, for GC | ||||
| 	return s[:i] | ||||
| } | ||||
|  | ||||
| // Similar to the stdlib slices.Contains, except that we don't have | ||||
| // slices.Index so we need to use slices.IndexFunc for this non-Func helper. | ||||
| func slices_Contains[S ~[]E, E comparable](s S, v E) bool { | ||||
| 	return slices_IndexFunc(s, func(e E) bool { return e == v }) >= 0 | ||||
| } | ||||
|  | ||||
| // Copied from the Go 1.24 stdlib implementation. | ||||
| func slices_Clone[S ~[]E, E any](s S) S { | ||||
| 	// Preserve nil in case it matters. | ||||
| 	if s == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return append(S([]E{}), s...) | ||||
| } | ||||
|  | ||||
| // Copied from the Go 1.24 stdlib implementation. | ||||
| func sync_OnceValue[T any](f func() T) func() T { | ||||
| 	var ( | ||||
| 		once   sync.Once | ||||
| 		valid  bool | ||||
| 		p      any | ||||
| 		result T | ||||
| 	) | ||||
| 	g := func() { | ||||
| 		defer func() { | ||||
| 			p = recover() | ||||
| 			if !valid { | ||||
| 				panic(p) | ||||
| 			} | ||||
| 		}() | ||||
| 		result = f() | ||||
| 		f = nil | ||||
| 		valid = true | ||||
| 	} | ||||
| 	return func() T { | ||||
| 		once.Do(g) | ||||
| 		if !valid { | ||||
| 			panic(p) | ||||
| 		} | ||||
| 		return result | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Copied from the Go 1.24 stdlib implementation. | ||||
| func sync_OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { | ||||
| 	var ( | ||||
| 		once  sync.Once | ||||
| 		valid bool | ||||
| 		p     any | ||||
| 		r1    T1 | ||||
| 		r2    T2 | ||||
| 	) | ||||
| 	g := func() { | ||||
| 		defer func() { | ||||
| 			p = recover() | ||||
| 			if !valid { | ||||
| 				panic(p) | ||||
| 			} | ||||
| 		}() | ||||
| 		r1, r2 = f() | ||||
| 		f = nil | ||||
| 		valid = true | ||||
| 	} | ||||
| 	return func() (T1, T2) { | ||||
| 		once.Do(g) | ||||
| 		if !valid { | ||||
| 			panic(p) | ||||
| 		} | ||||
| 		return r1, r2 | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										15
									
								
								vendor/github.com/cyphar/filepath-securejoin/internal/consts/consts.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								vendor/github.com/cyphar/filepath-securejoin/internal/consts/consts.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| // SPDX-License-Identifier: BSD-3-Clause | ||||
|  | ||||
| // Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. | ||||
| // Copyright (C) 2017-2025 SUSE LLC. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // Package consts contains the definitions of internal constants used | ||||
| // throughout filepath-securejoin. | ||||
| package consts | ||||
|  | ||||
| // MaxSymlinkLimit is the maximum number of symlinks that can be encountered | ||||
| // during a single lookup before returning -ELOOP. At time of writing, Linux | ||||
| // has an internal limit of 40. | ||||
| const MaxSymlinkLimit = 255 | ||||
							
								
								
									
										23
									
								
								vendor/github.com/cyphar/filepath-securejoin/join.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								vendor/github.com/cyphar/filepath-securejoin/join.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,3 +1,5 @@ | ||||
| // SPDX-License-Identifier: BSD-3-Clause | ||||
|  | ||||
| // Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. | ||||
| // Copyright (C) 2017-2025 SUSE LLC. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| @ -11,9 +13,9 @@ import ( | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"syscall" | ||||
| ) | ||||
|  | ||||
| const maxSymlinkLimit = 255 | ||||
| 	"github.com/cyphar/filepath-securejoin/internal/consts" | ||||
| ) | ||||
|  | ||||
| // IsNotExist tells you if err is an error that implies that either the path | ||||
| // accessed does not exist (or path components don't exist). This is | ||||
| @ -49,12 +51,13 @@ func hasDotDot(path string) bool { | ||||
| 	return strings.Contains("/"+path+"/", "/../") | ||||
| } | ||||
|  | ||||
| // SecureJoinVFS joins the two given path components (similar to [filepath.Join]) except | ||||
| // that the returned path is guaranteed to be scoped inside the provided root | ||||
| // path (when evaluated). Any symbolic links in the path are evaluated with the | ||||
| // given root treated as the root of the filesystem, similar to a chroot. The | ||||
| // filesystem state is evaluated through the given [VFS] interface (if nil, the | ||||
| // standard [os].* family of functions are used). | ||||
| // SecureJoinVFS joins the two given path components (similar to | ||||
| // [filepath.Join]) except that the returned path is guaranteed to be scoped | ||||
| // inside the provided root path (when evaluated). Any symbolic links in the | ||||
| // path are evaluated with the given root treated as the root of the | ||||
| // filesystem, similar to a chroot. The filesystem state is evaluated through | ||||
| // the given [VFS] interface (if nil, the standard [os].* family of functions | ||||
| // are used). | ||||
| // | ||||
| // Note that the guarantees provided by this function only apply if the path | ||||
| // components in the returned string are not modified (in other words are not | ||||
| @ -78,7 +81,7 @@ func hasDotDot(path string) bool { | ||||
| // fully resolved using [filepath.EvalSymlinks] or otherwise constructed to | ||||
| // avoid containing symlink components. Of course, the root also *must not* be | ||||
| // attacker-controlled. | ||||
| func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) { | ||||
| func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) { //nolint:revive // name is part of public API | ||||
| 	// The root path must not contain ".." components, otherwise when we join | ||||
| 	// the subpath we will end up with a weird path. We could work around this | ||||
| 	// in other ways but users shouldn't be giving us non-lexical root paths in | ||||
| @ -138,7 +141,7 @@ func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) { | ||||
| 		// It's a symlink, so get its contents and expand it by prepending it | ||||
| 		// to the yet-unparsed path. | ||||
| 		linksWalked++ | ||||
| 		if linksWalked > maxSymlinkLimit { | ||||
| 		if linksWalked > consts.MaxSymlinkLimit { | ||||
| 			return "", &os.PathError{Op: "SecureJoin", Path: root + string(filepath.Separator) + unsafePath, Err: syscall.ELOOP} | ||||
| 		} | ||||
|  | ||||
|  | ||||
							
								
								
									
										127
									
								
								vendor/github.com/cyphar/filepath-securejoin/openat2_linux.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										127
									
								
								vendor/github.com/cyphar/filepath-securejoin/openat2_linux.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,127 +0,0 @@ | ||||
| //go:build linux | ||||
|  | ||||
| // Copyright (C) 2024 SUSE LLC. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package securejoin | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
|  | ||||
| 	"golang.org/x/sys/unix" | ||||
| ) | ||||
|  | ||||
| var hasOpenat2 = sync_OnceValue(func() bool { | ||||
| 	fd, err := unix.Openat2(unix.AT_FDCWD, ".", &unix.OpenHow{ | ||||
| 		Flags:   unix.O_PATH | unix.O_CLOEXEC, | ||||
| 		Resolve: unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_IN_ROOT, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	_ = unix.Close(fd) | ||||
| 	return true | ||||
| }) | ||||
|  | ||||
| func scopedLookupShouldRetry(how *unix.OpenHow, err error) bool { | ||||
| 	// RESOLVE_IN_ROOT (and RESOLVE_BENEATH) can return -EAGAIN if we resolve | ||||
| 	// ".." while a mount or rename occurs anywhere on the system. This could | ||||
| 	// happen spuriously, or as the result of an attacker trying to mess with | ||||
| 	// us during lookup. | ||||
| 	// | ||||
| 	// In addition, scoped lookups have a "safety check" at the end of | ||||
| 	// complete_walk which will return -EXDEV if the final path is not in the | ||||
| 	// root. | ||||
| 	return how.Resolve&(unix.RESOLVE_IN_ROOT|unix.RESOLVE_BENEATH) != 0 && | ||||
| 		(errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EXDEV)) | ||||
| } | ||||
|  | ||||
| const scopedLookupMaxRetries = 10 | ||||
|  | ||||
| func openat2File(dir *os.File, path string, how *unix.OpenHow) (*os.File, error) { | ||||
| 	fullPath := dir.Name() + "/" + path | ||||
| 	// Make sure we always set O_CLOEXEC. | ||||
| 	how.Flags |= unix.O_CLOEXEC | ||||
| 	var tries int | ||||
| 	for tries < scopedLookupMaxRetries { | ||||
| 		fd, err := unix.Openat2(int(dir.Fd()), path, how) | ||||
| 		if err != nil { | ||||
| 			if scopedLookupShouldRetry(how, err) { | ||||
| 				// We retry a couple of times to avoid the spurious errors, and | ||||
| 				// if we are being attacked then returning -EAGAIN is the best | ||||
| 				// we can do. | ||||
| 				tries++ | ||||
| 				continue | ||||
| 			} | ||||
| 			return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: err} | ||||
| 		} | ||||
| 		// If we are using RESOLVE_IN_ROOT, the name we generated may be wrong. | ||||
| 		// NOTE: The procRoot code MUST NOT use RESOLVE_IN_ROOT, otherwise | ||||
| 		//       you'll get infinite recursion here. | ||||
| 		if how.Resolve&unix.RESOLVE_IN_ROOT == unix.RESOLVE_IN_ROOT { | ||||
| 			if actualPath, err := rawProcSelfFdReadlink(fd); err == nil { | ||||
| 				fullPath = actualPath | ||||
| 			} | ||||
| 		} | ||||
| 		return os.NewFile(uintptr(fd), fullPath), nil | ||||
| 	} | ||||
| 	return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: errPossibleAttack} | ||||
| } | ||||
|  | ||||
| func lookupOpenat2(root *os.File, unsafePath string, partial bool) (*os.File, string, error) { | ||||
| 	if !partial { | ||||
| 		file, err := openat2File(root, unsafePath, &unix.OpenHow{ | ||||
| 			Flags:   unix.O_PATH | unix.O_CLOEXEC, | ||||
| 			Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS, | ||||
| 		}) | ||||
| 		return file, "", err | ||||
| 	} | ||||
| 	return partialLookupOpenat2(root, unsafePath) | ||||
| } | ||||
|  | ||||
| // partialLookupOpenat2 is an alternative implementation of | ||||
| // partialLookupInRoot, using openat2(RESOLVE_IN_ROOT) to more safely get a | ||||
| // handle to the deepest existing child of the requested path within the root. | ||||
| func partialLookupOpenat2(root *os.File, unsafePath string) (*os.File, string, error) { | ||||
| 	// TODO: Implement this as a git-bisect-like binary search. | ||||
|  | ||||
| 	unsafePath = filepath.ToSlash(unsafePath) // noop | ||||
| 	endIdx := len(unsafePath) | ||||
| 	var lastError error | ||||
| 	for endIdx > 0 { | ||||
| 		subpath := unsafePath[:endIdx] | ||||
|  | ||||
| 		handle, err := openat2File(root, subpath, &unix.OpenHow{ | ||||
| 			Flags:   unix.O_PATH | unix.O_CLOEXEC, | ||||
| 			Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS, | ||||
| 		}) | ||||
| 		if err == nil { | ||||
| 			// Jump over the slash if we have a non-"" remainingPath. | ||||
| 			if endIdx < len(unsafePath) { | ||||
| 				endIdx += 1 | ||||
| 			} | ||||
| 			// We found a subpath! | ||||
| 			return handle, unsafePath[endIdx:], lastError | ||||
| 		} | ||||
| 		if errors.Is(err, unix.ENOENT) || errors.Is(err, unix.ENOTDIR) { | ||||
| 			// That path doesn't exist, let's try the next directory up. | ||||
| 			endIdx = strings.LastIndexByte(subpath, '/') | ||||
| 			lastError = err | ||||
| 			continue | ||||
| 		} | ||||
| 		return nil, "", fmt.Errorf("open subpath: %w", err) | ||||
| 	} | ||||
| 	// If we couldn't open anything, the whole subpath is missing. Return a | ||||
| 	// copy of the root fd so that the caller doesn't close this one by | ||||
| 	// accident. | ||||
| 	rootClone, err := dupFile(root) | ||||
| 	if err != nil { | ||||
| 		return nil, "", err | ||||
| 	} | ||||
| 	return rootClone, unsafePath, lastError | ||||
| } | ||||
							
								
								
									
										59
									
								
								vendor/github.com/cyphar/filepath-securejoin/openat_linux.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										59
									
								
								vendor/github.com/cyphar/filepath-securejoin/openat_linux.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,59 +0,0 @@ | ||||
| //go:build linux | ||||
|  | ||||
| // Copyright (C) 2024 SUSE LLC. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package securejoin | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
|  | ||||
| 	"golang.org/x/sys/unix" | ||||
| ) | ||||
|  | ||||
| func dupFile(f *os.File) (*os.File, error) { | ||||
| 	fd, err := unix.FcntlInt(f.Fd(), unix.F_DUPFD_CLOEXEC, 0) | ||||
| 	if err != nil { | ||||
| 		return nil, os.NewSyscallError("fcntl(F_DUPFD_CLOEXEC)", err) | ||||
| 	} | ||||
| 	return os.NewFile(uintptr(fd), f.Name()), nil | ||||
| } | ||||
|  | ||||
| func openatFile(dir *os.File, path string, flags int, mode int) (*os.File, error) { | ||||
| 	// Make sure we always set O_CLOEXEC. | ||||
| 	flags |= unix.O_CLOEXEC | ||||
| 	fd, err := unix.Openat(int(dir.Fd()), path, flags, uint32(mode)) | ||||
| 	if err != nil { | ||||
| 		return nil, &os.PathError{Op: "openat", Path: dir.Name() + "/" + path, Err: err} | ||||
| 	} | ||||
| 	// All of the paths we use with openatFile(2) are guaranteed to be | ||||
| 	// lexically safe, so we can use path.Join here. | ||||
| 	fullPath := filepath.Join(dir.Name(), path) | ||||
| 	return os.NewFile(uintptr(fd), fullPath), nil | ||||
| } | ||||
|  | ||||
| func fstatatFile(dir *os.File, path string, flags int) (unix.Stat_t, error) { | ||||
| 	var stat unix.Stat_t | ||||
| 	if err := unix.Fstatat(int(dir.Fd()), path, &stat, flags); err != nil { | ||||
| 		return stat, &os.PathError{Op: "fstatat", Path: dir.Name() + "/" + path, Err: err} | ||||
| 	} | ||||
| 	return stat, nil | ||||
| } | ||||
|  | ||||
| func readlinkatFile(dir *os.File, path string) (string, error) { | ||||
| 	size := 4096 | ||||
| 	for { | ||||
| 		linkBuf := make([]byte, size) | ||||
| 		n, err := unix.Readlinkat(int(dir.Fd()), path, linkBuf) | ||||
| 		if err != nil { | ||||
| 			return "", &os.PathError{Op: "readlinkat", Path: dir.Name() + "/" + path, Err: err} | ||||
| 		} | ||||
| 		if n != size { | ||||
| 			return string(linkBuf[:n]), nil | ||||
| 		} | ||||
| 		// Possible truncation, resize the buffer. | ||||
| 		size *= 2 | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										33
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| ## `pathrs-lite` ## | ||||
|  | ||||
| `github.com/cyphar/filepath-securejoin/pathrs-lite` provides a minimal **pure | ||||
| Go** implementation of the core bits of [libpathrs][]. This is not intended to | ||||
| be a complete replacement for libpathrs, instead it is mainly intended to be | ||||
| useful as a transition tool for existing Go projects. | ||||
|  | ||||
| The long-term plan for `pathrs-lite` is to provide a build tag that will cause | ||||
| all `pathrs-lite` operations to call into libpathrs directly, thus removing | ||||
| code duplication for projects that wish to make use of libpathrs (and providing | ||||
| the ability for software packagers to opt-in to libpathrs support without | ||||
| needing to patch upstream). | ||||
|  | ||||
| [libpathrs]: https://github.com/cyphar/libpathrs | ||||
|  | ||||
| ### License ### | ||||
|  | ||||
| Most of this subpackage is licensed under the Mozilla Public License (version | ||||
| 2.0). For more information, see the top-level [COPYING.md][] and | ||||
| [LICENSE.MPL-2.0][] files, as well as the individual license headers for each | ||||
| file. | ||||
|  | ||||
| ``` | ||||
| Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| Copyright (C) 2024-2025 SUSE LLC | ||||
|  | ||||
| This Source Code Form is subject to the terms of the Mozilla Public | ||||
| License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
| ``` | ||||
|  | ||||
| [COPYING.md]: ../COPYING.md | ||||
| [LICENSE.MPL-2.0]: ../LICENSE.MPL-2.0 | ||||
							
								
								
									
										14
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
|  | ||||
| //go:build linux | ||||
|  | ||||
| // Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| // Copyright (C) 2024-2025 SUSE LLC | ||||
| // | ||||
| // This Source Code Form is subject to the terms of the Mozilla Public | ||||
| // License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| // Package pathrs (pathrs-lite) is a less complete pure Go implementation of | ||||
| // some of the APIs provided by [libpathrs]. | ||||
| package pathrs | ||||
							
								
								
									
										30
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
|  | ||||
| // Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| // Copyright (C) 2025 SUSE LLC | ||||
| // | ||||
| // This Source Code Form is subject to the terms of the Mozilla Public | ||||
| // License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| // Package assert provides some basic assertion helpers for Go. | ||||
| package assert | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| ) | ||||
|  | ||||
| // Assert panics if the predicate is false with the provided argument. | ||||
| func Assert(predicate bool, msg any) { | ||||
| 	if !predicate { | ||||
| 		panic(msg) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Assertf panics if the predicate is false and formats the message using the | ||||
| // same formatting as [fmt.Printf]. | ||||
| // | ||||
| // [fmt.Printf]: https://pkg.go.dev/fmt#Printf | ||||
| func Assertf(predicate bool, fmtMsg string, args ...any) { | ||||
| 	Assert(predicate, fmt.Sprintf(fmtMsg, args...)) | ||||
| } | ||||
							
								
								
									
										30
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
|  | ||||
| // Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| // Copyright (C) 2024-2025 SUSE LLC | ||||
| // | ||||
| // This Source Code Form is subject to the terms of the Mozilla Public | ||||
| // License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| // Package internal contains unexported common code for filepath-securejoin. | ||||
| package internal | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// ErrPossibleAttack indicates that some attack was detected. | ||||
| 	ErrPossibleAttack = errors.New("possible attack detected") | ||||
|  | ||||
| 	// ErrPossibleBreakout indicates that during an operation we ended up in a | ||||
| 	// state that could be a breakout but we detected it. | ||||
| 	ErrPossibleBreakout = errors.New("possible breakout detected") | ||||
|  | ||||
| 	// ErrInvalidDirectory indicates an unlinked directory. | ||||
| 	ErrInvalidDirectory = errors.New("wandered into deleted directory") | ||||
|  | ||||
| 	// ErrDeletedInode indicates an unlinked file (non-directory). | ||||
| 	ErrDeletedInode = errors.New("cannot verify path of deleted inode") | ||||
| ) | ||||
							
								
								
									
										148
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,148 @@ | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
|  | ||||
| //go:build linux | ||||
|  | ||||
| // Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| // Copyright (C) 2024-2025 SUSE LLC | ||||
| // | ||||
| // This Source Code Form is subject to the terms of the Mozilla Public | ||||
| // License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| package fd | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"runtime" | ||||
|  | ||||
| 	"golang.org/x/sys/unix" | ||||
|  | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" | ||||
| ) | ||||
|  | ||||
| // prepareAtWith returns -EBADF (an invalid fd) if dir is nil, otherwise using | ||||
| // the dir.Fd(). We use -EBADF because in filepath-securejoin we generally | ||||
| // don't want to allow relative-to-cwd paths. The returned path is an | ||||
| // *informational* string that describes a reasonable pathname for the given | ||||
| // *at(2) arguments. You must not use the full path for any actual filesystem | ||||
| // operations. | ||||
| func prepareAt(dir Fd, path string) (dirFd int, unsafeUnmaskedPath string) { | ||||
| 	dirFd, dirPath := -int(unix.EBADF), "." | ||||
| 	if dir != nil { | ||||
| 		dirFd, dirPath = int(dir.Fd()), dir.Name() | ||||
| 	} | ||||
| 	if !filepath.IsAbs(path) { | ||||
| 		// only prepend the dirfd path for relative paths | ||||
| 		path = dirPath + "/" + path | ||||
| 	} | ||||
| 	// NOTE: If path is "." or "", the returned path won't be filepath.Clean, | ||||
| 	// but that's okay since this path is either used for errors (in which case | ||||
| 	// a trailing "/" or "/." is important information) or will be | ||||
| 	// filepath.Clean'd later (in the case of fd.Openat). | ||||
| 	return dirFd, path | ||||
| } | ||||
|  | ||||
| // Openat is an [Fd]-based wrapper around unix.Openat. | ||||
| func Openat(dir Fd, path string, flags int, mode int) (*os.File, error) { //nolint:unparam // wrapper func | ||||
| 	dirFd, fullPath := prepareAt(dir, path) | ||||
| 	// Make sure we always set O_CLOEXEC. | ||||
| 	flags |= unix.O_CLOEXEC | ||||
| 	fd, err := unix.Openat(dirFd, path, flags, uint32(mode)) | ||||
| 	if err != nil { | ||||
| 		return nil, &os.PathError{Op: "openat", Path: fullPath, Err: err} | ||||
| 	} | ||||
| 	runtime.KeepAlive(dir) | ||||
| 	// openat is only used with lexically-safe paths so we can use | ||||
| 	// filepath.Clean here, and also the path itself is not going to be used | ||||
| 	// for actual path operations. | ||||
| 	fullPath = filepath.Clean(fullPath) | ||||
| 	return os.NewFile(uintptr(fd), fullPath), nil | ||||
| } | ||||
|  | ||||
| // Fstatat is an [Fd]-based wrapper around unix.Fstatat. | ||||
| func Fstatat(dir Fd, path string, flags int) (unix.Stat_t, error) { | ||||
| 	dirFd, fullPath := prepareAt(dir, path) | ||||
| 	var stat unix.Stat_t | ||||
| 	if err := unix.Fstatat(dirFd, path, &stat, flags); err != nil { | ||||
| 		return stat, &os.PathError{Op: "fstatat", Path: fullPath, Err: err} | ||||
| 	} | ||||
| 	runtime.KeepAlive(dir) | ||||
| 	return stat, nil | ||||
| } | ||||
|  | ||||
| // Faccessat is an [Fd]-based wrapper around unix.Faccessat. | ||||
| func Faccessat(dir Fd, path string, mode uint32, flags int) error { | ||||
| 	dirFd, fullPath := prepareAt(dir, path) | ||||
| 	err := unix.Faccessat(dirFd, path, mode, flags) | ||||
| 	if err != nil { | ||||
| 		err = &os.PathError{Op: "faccessat", Path: fullPath, Err: err} | ||||
| 	} | ||||
| 	runtime.KeepAlive(dir) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // Readlinkat is an [Fd]-based wrapper around unix.Readlinkat. | ||||
| func Readlinkat(dir Fd, path string) (string, error) { | ||||
| 	dirFd, fullPath := prepareAt(dir, path) | ||||
| 	size := 4096 | ||||
| 	for { | ||||
| 		linkBuf := make([]byte, size) | ||||
| 		n, err := unix.Readlinkat(dirFd, path, linkBuf) | ||||
| 		if err != nil { | ||||
| 			return "", &os.PathError{Op: "readlinkat", Path: fullPath, Err: err} | ||||
| 		} | ||||
| 		runtime.KeepAlive(dir) | ||||
| 		if n != size { | ||||
| 			return string(linkBuf[:n]), nil | ||||
| 		} | ||||
| 		// Possible truncation, resize the buffer. | ||||
| 		size *= 2 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	// STATX_MNT_ID_UNIQUE is provided in golang.org/x/sys@v0.20.0, but in order to | ||||
| 	// avoid bumping the requirement for a single constant we can just define it | ||||
| 	// ourselves. | ||||
| 	_STATX_MNT_ID_UNIQUE = 0x4000 //nolint:revive // unix.* name | ||||
|  | ||||
| 	// We don't care which mount ID we get. The kernel will give us the unique | ||||
| 	// one if it is supported. If the kernel doesn't support | ||||
| 	// STATX_MNT_ID_UNIQUE, the bit is ignored and the returned request mask | ||||
| 	// will only contain STATX_MNT_ID (if supported). | ||||
| 	wantStatxMntMask = _STATX_MNT_ID_UNIQUE | unix.STATX_MNT_ID | ||||
| ) | ||||
|  | ||||
| var hasStatxMountID = gocompat.SyncOnceValue(func() bool { | ||||
| 	var stx unix.Statx_t | ||||
| 	err := unix.Statx(-int(unix.EBADF), "/", 0, wantStatxMntMask, &stx) | ||||
| 	return err == nil && stx.Mask&wantStatxMntMask != 0 | ||||
| }) | ||||
|  | ||||
| // GetMountID gets the mount identifier associated with the fd and path | ||||
| // combination. It is effectively a wrapper around fetching | ||||
| // STATX_MNT_ID{,_UNIQUE} with unix.Statx, but with a fallback to 0 if the | ||||
| // kernel doesn't support the feature. | ||||
| func GetMountID(dir Fd, path string) (uint64, error) { | ||||
| 	// If we don't have statx(STATX_MNT_ID*) support, we can't do anything. | ||||
| 	if !hasStatxMountID() { | ||||
| 		return 0, nil | ||||
| 	} | ||||
|  | ||||
| 	dirFd, fullPath := prepareAt(dir, path) | ||||
|  | ||||
| 	var stx unix.Statx_t | ||||
| 	err := unix.Statx(dirFd, path, unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW, wantStatxMntMask, &stx) | ||||
| 	if stx.Mask&wantStatxMntMask == 0 { | ||||
| 		// It's not a kernel limitation, for some reason we couldn't get a | ||||
| 		// mount ID. Assume it's some kind of attack. | ||||
| 		err = fmt.Errorf("could not get mount id: %w", err) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return 0, &os.PathError{Op: "statx(STATX_MNT_ID_...)", Path: fullPath, Err: err} | ||||
| 	} | ||||
| 	runtime.KeepAlive(dir) | ||||
| 	return stx.Mnt_id, nil | ||||
| } | ||||
							
								
								
									
										55
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
|  | ||||
| // Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| // Copyright (C) 2025 SUSE LLC | ||||
| // | ||||
| // This Source Code Form is subject to the terms of the Mozilla Public | ||||
| // License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| // Package fd provides a drop-in interface-based replacement of [*os.File] that | ||||
| // allows for things like noop-Close wrappers to be used. | ||||
| // | ||||
| // [*os.File]: https://pkg.go.dev/os#File | ||||
| package fd | ||||
|  | ||||
| import ( | ||||
| 	"io" | ||||
| 	"os" | ||||
| ) | ||||
|  | ||||
| // Fd is an interface that mirrors most of the API of [*os.File], allowing you | ||||
| // to create wrappers that can be used in place of [*os.File]. | ||||
| // | ||||
| // [*os.File]: https://pkg.go.dev/os#File | ||||
| type Fd interface { | ||||
| 	io.Closer | ||||
| 	Name() string | ||||
| 	Fd() uintptr | ||||
| } | ||||
|  | ||||
| // Compile-time interface checks. | ||||
| var ( | ||||
| 	_ Fd = (*os.File)(nil) | ||||
| 	_ Fd = noClose{} | ||||
| ) | ||||
|  | ||||
| type noClose struct{ inner Fd } | ||||
|  | ||||
| func (f noClose) Name() string { return f.inner.Name() } | ||||
| func (f noClose) Fd() uintptr  { return f.inner.Fd() } | ||||
|  | ||||
| func (f noClose) Close() error { return nil } | ||||
|  | ||||
| // NopCloser returns an [*os.File]-like object where the [Close] method is now | ||||
| // a no-op. | ||||
| // | ||||
| // Note that for [*os.File] and similar objects, the Go garbage collector will | ||||
| // still call [Close] on the underlying file unless you use | ||||
| // [runtime.SetFinalizer] to disable this behaviour. This is up to the caller | ||||
| // to do (if necessary). | ||||
| // | ||||
| // [*os.File]: https://pkg.go.dev/os#File | ||||
| // [Close]: https://pkg.go.dev/io#Closer | ||||
| // [runtime.SetFinalizer]: https://pkg.go.dev/runtime#SetFinalizer | ||||
| func NopCloser(f Fd) Fd { return noClose{inner: f} } | ||||
							
								
								
									
										78
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,78 @@ | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
|  | ||||
| //go:build linux | ||||
|  | ||||
| // Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| // Copyright (C) 2024-2025 SUSE LLC | ||||
| // | ||||
| // This Source Code Form is subject to the terms of the Mozilla Public | ||||
| // License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| package fd | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"runtime" | ||||
|  | ||||
| 	"golang.org/x/sys/unix" | ||||
|  | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal" | ||||
| ) | ||||
|  | ||||
| // DupWithName creates a new file descriptor referencing the same underlying | ||||
| // file, but with the provided name instead of fd.Name(). | ||||
| func DupWithName(fd Fd, name string) (*os.File, error) { | ||||
| 	fd2, err := unix.FcntlInt(fd.Fd(), unix.F_DUPFD_CLOEXEC, 0) | ||||
| 	if err != nil { | ||||
| 		return nil, os.NewSyscallError("fcntl(F_DUPFD_CLOEXEC)", err) | ||||
| 	} | ||||
| 	runtime.KeepAlive(fd) | ||||
| 	return os.NewFile(uintptr(fd2), name), nil | ||||
| } | ||||
|  | ||||
| // Dup creates a new file description referencing the same underlying file. | ||||
| func Dup(fd Fd) (*os.File, error) { | ||||
| 	return DupWithName(fd, fd.Name()) | ||||
| } | ||||
|  | ||||
| // Fstat is an [Fd]-based wrapper around unix.Fstat. | ||||
| func Fstat(fd Fd) (unix.Stat_t, error) { | ||||
| 	var stat unix.Stat_t | ||||
| 	if err := unix.Fstat(int(fd.Fd()), &stat); err != nil { | ||||
| 		return stat, &os.PathError{Op: "fstat", Path: fd.Name(), Err: err} | ||||
| 	} | ||||
| 	runtime.KeepAlive(fd) | ||||
| 	return stat, nil | ||||
| } | ||||
|  | ||||
| // Fstatfs is an [Fd]-based wrapper around unix.Fstatfs. | ||||
| func Fstatfs(fd Fd) (unix.Statfs_t, error) { | ||||
| 	var statfs unix.Statfs_t | ||||
| 	if err := unix.Fstatfs(int(fd.Fd()), &statfs); err != nil { | ||||
| 		return statfs, &os.PathError{Op: "fstatfs", Path: fd.Name(), Err: err} | ||||
| 	} | ||||
| 	runtime.KeepAlive(fd) | ||||
| 	return statfs, nil | ||||
| } | ||||
|  | ||||
| // IsDeadInode detects whether the file has been unlinked from a filesystem and | ||||
| // is thus a "dead inode" from the kernel's perspective. | ||||
| func IsDeadInode(file Fd) error { | ||||
| 	// If the nlink of a file drops to 0, there is an attacker deleting | ||||
| 	// directories during our walk, which could result in weird /proc values. | ||||
| 	// It's better to error out in this case. | ||||
| 	stat, err := Fstat(file) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("check for dead inode: %w", err) | ||||
| 	} | ||||
| 	if stat.Nlink == 0 { | ||||
| 		err := internal.ErrDeletedInode | ||||
| 		if stat.Mode&unix.S_IFMT == unix.S_IFDIR { | ||||
| 			err = internal.ErrInvalidDirectory | ||||
| 		} | ||||
| 		return fmt.Errorf("%w %q", err, file.Name()) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										54
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
|  | ||||
| //go:build linux | ||||
|  | ||||
| // Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| // Copyright (C) 2024-2025 SUSE LLC | ||||
| // | ||||
| // This Source Code Form is subject to the terms of the Mozilla Public | ||||
| // License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| package fd | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
| 	"runtime" | ||||
|  | ||||
| 	"golang.org/x/sys/unix" | ||||
| ) | ||||
|  | ||||
| // Fsopen is an [Fd]-based wrapper around unix.Fsopen. | ||||
| func Fsopen(fsName string, flags int) (*os.File, error) { | ||||
| 	// Make sure we always set O_CLOEXEC. | ||||
| 	flags |= unix.FSOPEN_CLOEXEC | ||||
| 	fd, err := unix.Fsopen(fsName, flags) | ||||
| 	if err != nil { | ||||
| 		return nil, os.NewSyscallError("fsopen "+fsName, err) | ||||
| 	} | ||||
| 	return os.NewFile(uintptr(fd), "fscontext:"+fsName), nil | ||||
| } | ||||
|  | ||||
| // Fsmount is an [Fd]-based wrapper around unix.Fsmount. | ||||
| func Fsmount(ctx Fd, flags, mountAttrs int) (*os.File, error) { | ||||
| 	// Make sure we always set O_CLOEXEC. | ||||
| 	flags |= unix.FSMOUNT_CLOEXEC | ||||
| 	fd, err := unix.Fsmount(int(ctx.Fd()), flags, mountAttrs) | ||||
| 	if err != nil { | ||||
| 		return nil, os.NewSyscallError("fsmount "+ctx.Name(), err) | ||||
| 	} | ||||
| 	return os.NewFile(uintptr(fd), "fsmount:"+ctx.Name()), nil | ||||
| } | ||||
|  | ||||
| // OpenTree is an [Fd]-based wrapper around unix.OpenTree. | ||||
| func OpenTree(dir Fd, path string, flags uint) (*os.File, error) { | ||||
| 	dirFd, fullPath := prepareAt(dir, path) | ||||
| 	// Make sure we always set O_CLOEXEC. | ||||
| 	flags |= unix.OPEN_TREE_CLOEXEC | ||||
| 	fd, err := unix.OpenTree(dirFd, path, flags) | ||||
| 	if err != nil { | ||||
| 		return nil, &os.PathError{Op: "open_tree", Path: fullPath, Err: err} | ||||
| 	} | ||||
| 	runtime.KeepAlive(dir) | ||||
| 	return os.NewFile(uintptr(fd), fullPath), nil | ||||
| } | ||||
							
								
								
									
										62
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,62 @@ | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
|  | ||||
| //go:build linux | ||||
|  | ||||
| // Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| // Copyright (C) 2024-2025 SUSE LLC | ||||
| // | ||||
| // This Source Code Form is subject to the terms of the Mozilla Public | ||||
| // License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| package fd | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"os" | ||||
| 	"runtime" | ||||
|  | ||||
| 	"golang.org/x/sys/unix" | ||||
|  | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal" | ||||
| ) | ||||
|  | ||||
| func scopedLookupShouldRetry(how *unix.OpenHow, err error) bool { | ||||
| 	// RESOLVE_IN_ROOT (and RESOLVE_BENEATH) can return -EAGAIN if we resolve | ||||
| 	// ".." while a mount or rename occurs anywhere on the system. This could | ||||
| 	// happen spuriously, or as the result of an attacker trying to mess with | ||||
| 	// us during lookup. | ||||
| 	// | ||||
| 	// In addition, scoped lookups have a "safety check" at the end of | ||||
| 	// complete_walk which will return -EXDEV if the final path is not in the | ||||
| 	// root. | ||||
| 	return how.Resolve&(unix.RESOLVE_IN_ROOT|unix.RESOLVE_BENEATH) != 0 && | ||||
| 		(errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EXDEV)) | ||||
| } | ||||
|  | ||||
| const scopedLookupMaxRetries = 32 | ||||
|  | ||||
| // Openat2 is an [Fd]-based wrapper around unix.Openat2, but with some retry | ||||
| // logic in case of EAGAIN errors. | ||||
| func Openat2(dir Fd, path string, how *unix.OpenHow) (*os.File, error) { | ||||
| 	dirFd, fullPath := prepareAt(dir, path) | ||||
| 	// Make sure we always set O_CLOEXEC. | ||||
| 	how.Flags |= unix.O_CLOEXEC | ||||
| 	var tries int | ||||
| 	for tries < scopedLookupMaxRetries { | ||||
| 		fd, err := unix.Openat2(dirFd, path, how) | ||||
| 		if err != nil { | ||||
| 			if scopedLookupShouldRetry(how, err) { | ||||
| 				// We retry a couple of times to avoid the spurious errors, and | ||||
| 				// if we are being attacked then returning -EAGAIN is the best | ||||
| 				// we can do. | ||||
| 				tries++ | ||||
| 				continue | ||||
| 			} | ||||
| 			return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: err} | ||||
| 		} | ||||
| 		runtime.KeepAlive(dir) | ||||
| 		return os.NewFile(uintptr(fd), fullPath), nil | ||||
| 	} | ||||
| 	return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: internal.ErrPossibleAttack} | ||||
| } | ||||
							
								
								
									
										10
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| ## gocompat ## | ||||
|  | ||||
| This directory contains backports of stdlib functions from later Go versions so | ||||
| the filepath-securejoin can continue to be used by projects that are stuck with | ||||
| Go 1.18 support. Note that often filepath-securejoin is added in security | ||||
| patches for old releases, so avoiding the need to bump Go compiler requirements | ||||
| is a huge plus to downstreams. | ||||
|  | ||||
| The source code is licensed under the same license as the Go stdlib. See the | ||||
| source files for the precise license information. | ||||
							
								
								
									
										13
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| // SPDX-License-Identifier: BSD-3-Clause | ||||
| //go:build linux && go1.20 | ||||
|  | ||||
| // Copyright (C) 2025 SUSE LLC. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // Package gocompat includes compatibility shims (backported from future Go | ||||
| // stdlib versions) to permit filepath-securejoin to be used with older Go | ||||
| // versions (often filepath-securejoin is added in security patches for old | ||||
| // releases, so avoiding the need to bump Go compiler requirements is a huge | ||||
| // plus to downstreams). | ||||
| package gocompat | ||||
| @ -1,18 +1,19 @@ | ||||
| // SPDX-License-Identifier: BSD-3-Clause | ||||
| //go:build linux && go1.20 | ||||
| 
 | ||||
| // Copyright (C) 2024 SUSE LLC. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
| 
 | ||||
| package securejoin | ||||
| package gocompat | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| ) | ||||
| 
 | ||||
| // wrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except | ||||
| // WrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except | ||||
| // that on pre-1.20 Go versions only errors.Is() works properly (errors.Unwrap) | ||||
| // is only guaranteed to give you baseErr. | ||||
| func wrapBaseError(baseErr, extraErr error) error { | ||||
| func WrapBaseError(baseErr, extraErr error) error { | ||||
| 	return fmt.Errorf("%w: %w", extraErr, baseErr) | ||||
| } | ||||
| @ -1,10 +1,12 @@ | ||||
| // SPDX-License-Identifier: BSD-3-Clause | ||||
| 
 | ||||
| //go:build linux && !go1.20 | ||||
| 
 | ||||
| // Copyright (C) 2024 SUSE LLC. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
| 
 | ||||
| package securejoin | ||||
| package gocompat | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| @ -27,10 +29,10 @@ func (err wrappedError) Error() string { | ||||
| 	return fmt.Sprintf("%v: %v", err.isError, err.inner) | ||||
| } | ||||
| 
 | ||||
| // wrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except | ||||
| // WrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except | ||||
| // that on pre-1.20 Go versions only errors.Is() works properly (errors.Unwrap) | ||||
| // is only guaranteed to give you baseErr. | ||||
| func wrapBaseError(baseErr, extraErr error) error { | ||||
| func WrapBaseError(baseErr, extraErr error) error { | ||||
| 	return wrappedError{ | ||||
| 		inner:   baseErr, | ||||
| 		isError: extraErr, | ||||
							
								
								
									
										53
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_go121.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_go121.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | ||||
| // SPDX-License-Identifier: BSD-3-Clause | ||||
|  | ||||
| //go:build linux && go1.21 | ||||
|  | ||||
| // Copyright (C) 2024-2025 SUSE LLC. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package gocompat | ||||
|  | ||||
| import ( | ||||
| 	"cmp" | ||||
| 	"slices" | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| // SlicesDeleteFunc is equivalent to Go 1.21's slices.DeleteFunc. | ||||
| func SlicesDeleteFunc[S ~[]E, E any](slice S, delFn func(E) bool) S { | ||||
| 	return slices.DeleteFunc(slice, delFn) | ||||
| } | ||||
|  | ||||
| // SlicesContains is equivalent to Go 1.21's slices.Contains. | ||||
| func SlicesContains[S ~[]E, E comparable](slice S, val E) bool { | ||||
| 	return slices.Contains(slice, val) | ||||
| } | ||||
|  | ||||
| // SlicesClone is equivalent to Go 1.21's slices.Clone. | ||||
| func SlicesClone[S ~[]E, E any](slice S) S { | ||||
| 	return slices.Clone(slice) | ||||
| } | ||||
|  | ||||
| // SyncOnceValue is equivalent to Go 1.21's sync.OnceValue. | ||||
| func SyncOnceValue[T any](f func() T) func() T { | ||||
| 	return sync.OnceValue(f) | ||||
| } | ||||
|  | ||||
| // SyncOnceValues is equivalent to Go 1.21's sync.OnceValues. | ||||
| func SyncOnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { | ||||
| 	return sync.OnceValues(f) | ||||
| } | ||||
|  | ||||
| // CmpOrdered is equivalent to Go 1.21's cmp.Ordered generic type definition. | ||||
| type CmpOrdered = cmp.Ordered | ||||
|  | ||||
| // CmpCompare is equivalent to Go 1.21's cmp.Compare. | ||||
| func CmpCompare[T CmpOrdered](x, y T) int { | ||||
| 	return cmp.Compare(x, y) | ||||
| } | ||||
|  | ||||
| // Max2 is equivalent to Go 1.21's max builtin (but only for two parameters). | ||||
| func Max2[T CmpOrdered](x, y T) T { | ||||
| 	return max(x, y) | ||||
| } | ||||
							
								
								
									
										187
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_unsupported.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_unsupported.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,187 @@ | ||||
| // SPDX-License-Identifier: BSD-3-Clause | ||||
|  | ||||
| //go:build linux && !go1.21 | ||||
|  | ||||
| // Copyright (C) 2021, 2022 The Go Authors. All rights reserved. | ||||
| // Copyright (C) 2024-2025 SUSE LLC. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE.BSD file. | ||||
|  | ||||
| package gocompat | ||||
|  | ||||
| import ( | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| // These are very minimal implementations of functions that appear in Go 1.21's | ||||
| // stdlib, included so that we can build on older Go versions. Most are | ||||
| // borrowed directly from the stdlib, and a few are modified to be "obviously | ||||
| // correct" without needing to copy too many other helpers. | ||||
|  | ||||
| // clearSlice is equivalent to Go 1.21's builtin clear. | ||||
| // Copied from the Go 1.24 stdlib implementation. | ||||
| func clearSlice[S ~[]E, E any](slice S) { | ||||
| 	var zero E | ||||
| 	for i := range slice { | ||||
| 		slice[i] = zero | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // slicesIndexFunc is equivalent to Go 1.21's slices.IndexFunc. | ||||
| // Copied from the Go 1.24 stdlib implementation. | ||||
| func slicesIndexFunc[S ~[]E, E any](s S, f func(E) bool) int { | ||||
| 	for i := range s { | ||||
| 		if f(s[i]) { | ||||
| 			return i | ||||
| 		} | ||||
| 	} | ||||
| 	return -1 | ||||
| } | ||||
|  | ||||
| // SlicesDeleteFunc is equivalent to Go 1.21's slices.DeleteFunc. | ||||
| // Copied from the Go 1.24 stdlib implementation. | ||||
| func SlicesDeleteFunc[S ~[]E, E any](s S, del func(E) bool) S { | ||||
| 	i := slicesIndexFunc(s, del) | ||||
| 	if i == -1 { | ||||
| 		return s | ||||
| 	} | ||||
| 	// Don't start copying elements until we find one to delete. | ||||
| 	for j := i + 1; j < len(s); j++ { | ||||
| 		if v := s[j]; !del(v) { | ||||
| 			s[i] = v | ||||
| 			i++ | ||||
| 		} | ||||
| 	} | ||||
| 	clearSlice(s[i:]) // zero/nil out the obsolete elements, for GC | ||||
| 	return s[:i] | ||||
| } | ||||
|  | ||||
| // SlicesContains is equivalent to Go 1.21's slices.Contains. | ||||
| // Similar to the stdlib slices.Contains, except that we don't have | ||||
| // slices.Index so we need to use slices.IndexFunc for this non-Func helper. | ||||
| func SlicesContains[S ~[]E, E comparable](s S, v E) bool { | ||||
| 	return slicesIndexFunc(s, func(e E) bool { return e == v }) >= 0 | ||||
| } | ||||
|  | ||||
| // SlicesClone is equivalent to Go 1.21's slices.Clone. | ||||
| // Copied from the Go 1.24 stdlib implementation. | ||||
| func SlicesClone[S ~[]E, E any](s S) S { | ||||
| 	// Preserve nil in case it matters. | ||||
| 	if s == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return append(S([]E{}), s...) | ||||
| } | ||||
|  | ||||
| // SyncOnceValue is equivalent to Go 1.21's sync.OnceValue. | ||||
| // Copied from the Go 1.25 stdlib implementation. | ||||
| func SyncOnceValue[T any](f func() T) func() T { | ||||
| 	// Use a struct so that there's a single heap allocation. | ||||
| 	d := struct { | ||||
| 		f      func() T | ||||
| 		once   sync.Once | ||||
| 		valid  bool | ||||
| 		p      any | ||||
| 		result T | ||||
| 	}{ | ||||
| 		f: f, | ||||
| 	} | ||||
| 	return func() T { | ||||
| 		d.once.Do(func() { | ||||
| 			defer func() { | ||||
| 				d.f = nil | ||||
| 				d.p = recover() | ||||
| 				if !d.valid { | ||||
| 					panic(d.p) | ||||
| 				} | ||||
| 			}() | ||||
| 			d.result = d.f() | ||||
| 			d.valid = true | ||||
| 		}) | ||||
| 		if !d.valid { | ||||
| 			panic(d.p) | ||||
| 		} | ||||
| 		return d.result | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // SyncOnceValues is equivalent to Go 1.21's sync.OnceValues. | ||||
| // Copied from the Go 1.25 stdlib implementation. | ||||
| func SyncOnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { | ||||
| 	// Use a struct so that there's a single heap allocation. | ||||
| 	d := struct { | ||||
| 		f     func() (T1, T2) | ||||
| 		once  sync.Once | ||||
| 		valid bool | ||||
| 		p     any | ||||
| 		r1    T1 | ||||
| 		r2    T2 | ||||
| 	}{ | ||||
| 		f: f, | ||||
| 	} | ||||
| 	return func() (T1, T2) { | ||||
| 		d.once.Do(func() { | ||||
| 			defer func() { | ||||
| 				d.f = nil | ||||
| 				d.p = recover() | ||||
| 				if !d.valid { | ||||
| 					panic(d.p) | ||||
| 				} | ||||
| 			}() | ||||
| 			d.r1, d.r2 = d.f() | ||||
| 			d.valid = true | ||||
| 		}) | ||||
| 		if !d.valid { | ||||
| 			panic(d.p) | ||||
| 		} | ||||
| 		return d.r1, d.r2 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // CmpOrdered is equivalent to Go 1.21's cmp.Ordered generic type definition. | ||||
| // Copied from the Go 1.25 stdlib implementation. | ||||
| type CmpOrdered interface { | ||||
| 	~int | ~int8 | ~int16 | ~int32 | ~int64 | | ||||
| 		~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | | ||||
| 		~float32 | ~float64 | | ||||
| 		~string | ||||
| } | ||||
|  | ||||
| // isNaN reports whether x is a NaN without requiring the math package. | ||||
| // This will always return false if T is not floating-point. | ||||
| // Copied from the Go 1.25 stdlib implementation. | ||||
| func isNaN[T CmpOrdered](x T) bool { | ||||
| 	return x != x | ||||
| } | ||||
|  | ||||
| // CmpCompare is equivalent to Go 1.21's cmp.Compare. | ||||
| // Copied from the Go 1.25 stdlib implementation. | ||||
| func CmpCompare[T CmpOrdered](x, y T) int { | ||||
| 	xNaN := isNaN(x) | ||||
| 	yNaN := isNaN(y) | ||||
| 	if xNaN { | ||||
| 		if yNaN { | ||||
| 			return 0 | ||||
| 		} | ||||
| 		return -1 | ||||
| 	} | ||||
| 	if yNaN { | ||||
| 		return +1 | ||||
| 	} | ||||
| 	if x < y { | ||||
| 		return -1 | ||||
| 	} | ||||
| 	if x > y { | ||||
| 		return +1 | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| // Max2 is equivalent to Go 1.21's max builtin for two parameters. | ||||
| func Max2[T CmpOrdered](x, y T) T { | ||||
| 	m := x | ||||
| 	if y > m { | ||||
| 		m = y | ||||
| 	} | ||||
| 	return m | ||||
| } | ||||
							
								
								
									
										123
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion/kernel_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion/kernel_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,123 @@ | ||||
| // SPDX-License-Identifier: BSD-3-Clause | ||||
|  | ||||
| // Copyright (C) 2022 The Go Authors. All rights reserved. | ||||
| // Copyright (C) 2025 SUSE LLC. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE.BSD file. | ||||
|  | ||||
| // The parsing logic is very loosely based on the Go stdlib's | ||||
| // src/internal/syscall/unix/kernel_version_linux.go but with an API that looks | ||||
| // a bit like runc's libcontainer/system/kernelversion. | ||||
| // | ||||
| // TODO(cyphar): This API has been copied around to a lot of different projects | ||||
| // (Docker, containerd, runc, and now filepath-securejoin) -- maybe we should | ||||
| // put it in a separate project? | ||||
|  | ||||
| // Package kernelversion provides a simple mechanism for checking whether the | ||||
| // running kernel is at least as new as some baseline kernel version. This is | ||||
| // often useful when checking for features that would be too complicated to | ||||
| // test support for (or in cases where we know that some kernel features in | ||||
| // backport-heavy kernels are broken and need to be avoided). | ||||
| package kernelversion | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"golang.org/x/sys/unix" | ||||
|  | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" | ||||
| ) | ||||
|  | ||||
| // KernelVersion is a numeric representation of the key numerical elements of a | ||||
| // kernel version (for instance, "4.1.2-default-1" would be represented as | ||||
| // KernelVersion{4, 1, 2}). | ||||
| type KernelVersion []uint64 | ||||
|  | ||||
| func (kver KernelVersion) String() string { | ||||
| 	var str strings.Builder | ||||
| 	for idx, elem := range kver { | ||||
| 		if idx != 0 { | ||||
| 			_, _ = str.WriteRune('.') | ||||
| 		} | ||||
| 		_, _ = str.WriteString(strconv.FormatUint(elem, 10)) | ||||
| 	} | ||||
| 	return str.String() | ||||
| } | ||||
|  | ||||
| var errInvalidKernelVersion = errors.New("invalid kernel version") | ||||
|  | ||||
| // parseKernelVersion parses a string and creates a KernelVersion based on it. | ||||
| func parseKernelVersion(kverStr string) (KernelVersion, error) { | ||||
| 	kver := make(KernelVersion, 1, 3) | ||||
| 	for idx, ch := range kverStr { | ||||
| 		if '0' <= ch && ch <= '9' { | ||||
| 			v := &kver[len(kver)-1] | ||||
| 			*v = (*v * 10) + uint64(ch-'0') | ||||
| 		} else { | ||||
| 			if idx == 0 || kverStr[idx-1] < '0' || '9' < kverStr[idx-1] { | ||||
| 				// "." must be preceded by a digit while in version section | ||||
| 				return nil, fmt.Errorf("%w %q: kernel version has dot(s) followed by non-digit in version section", errInvalidKernelVersion, kverStr) | ||||
| 			} | ||||
| 			if ch != '.' { | ||||
| 				break | ||||
| 			} | ||||
| 			kver = append(kver, 0) | ||||
| 		} | ||||
| 	} | ||||
| 	if len(kver) < 2 { | ||||
| 		return nil, fmt.Errorf("%w %q: kernel versions must contain at least two components", errInvalidKernelVersion, kverStr) | ||||
| 	} | ||||
| 	return kver, nil | ||||
| } | ||||
|  | ||||
| // getKernelVersion gets the current kernel version. | ||||
| var getKernelVersion = gocompat.SyncOnceValues(func() (KernelVersion, error) { | ||||
| 	var uts unix.Utsname | ||||
| 	if err := unix.Uname(&uts); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	// Remove the \x00 from the release. | ||||
| 	release := uts.Release[:] | ||||
| 	return parseKernelVersion(string(release[:bytes.IndexByte(release, 0)])) | ||||
| }) | ||||
|  | ||||
| // GreaterEqualThan returns true if the the host kernel version is greater than | ||||
| // or equal to the provided [KernelVersion]. When doing this comparison, any | ||||
| // non-numerical suffixes of the host kernel version are ignored. | ||||
| // | ||||
| // If the number of components provided is not equal to the number of numerical | ||||
| // components of the host kernel version, any missing components are treated as | ||||
| // 0. This means that GreaterEqualThan(KernelVersion{4}) will be treated the | ||||
| // same as GreaterEqualThan(KernelVersion{4, 0, 0, ..., 0, 0}), and that if the | ||||
| // host kernel version is "4" then GreaterEqualThan(KernelVersion{4, 1}) will | ||||
| // return false (because the host version will be treated as "4.0"). | ||||
| func GreaterEqualThan(wantKver KernelVersion) (bool, error) { | ||||
| 	hostKver, err := getKernelVersion() | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
|  | ||||
| 	// Pad out the kernel version lengths to match one another. | ||||
| 	cmpLen := gocompat.Max2(len(hostKver), len(wantKver)) | ||||
| 	hostKver = append(hostKver, make(KernelVersion, cmpLen-len(hostKver))...) | ||||
| 	wantKver = append(wantKver, make(KernelVersion, cmpLen-len(wantKver))...) | ||||
|  | ||||
| 	for i := 0; i < cmpLen; i++ { | ||||
| 		switch gocompat.CmpCompare(hostKver[i], wantKver[i]) { | ||||
| 		case -1: | ||||
| 			// host < want | ||||
| 			return false, nil | ||||
| 		case +1: | ||||
| 			// host > want | ||||
| 			return true, nil | ||||
| 		case 0: | ||||
| 			continue | ||||
| 		} | ||||
| 	} | ||||
| 	// equal version values | ||||
| 	return true, nil | ||||
| } | ||||
							
								
								
									
										12
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
|  | ||||
| // Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| // Copyright (C) 2024-2025 SUSE LLC | ||||
| // | ||||
| // This Source Code Form is subject to the terms of the Mozilla Public | ||||
| // License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| // Package linux returns information about what features are supported on the | ||||
| // running kernel. | ||||
| package linux | ||||
							
								
								
									
										47
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
|  | ||||
| //go:build linux | ||||
|  | ||||
| // Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| // Copyright (C) 2024-2025 SUSE LLC | ||||
| // | ||||
| // This Source Code Form is subject to the terms of the Mozilla Public | ||||
| // License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| package linux | ||||
|  | ||||
| import ( | ||||
| 	"golang.org/x/sys/unix" | ||||
|  | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion" | ||||
| ) | ||||
|  | ||||
| // HasNewMountAPI returns whether the new fsopen(2) mount API is supported on | ||||
| // the running kernel. | ||||
| var HasNewMountAPI = gocompat.SyncOnceValue(func() bool { | ||||
| 	// All of the pieces of the new mount API we use (fsopen, fsconfig, | ||||
| 	// fsmount, open_tree) were added together in Linux 5.2[1,2], so we can | ||||
| 	// just check for one of the syscalls and the others should also be | ||||
| 	// available. | ||||
| 	// | ||||
| 	// Just try to use open_tree(2) to open a file without OPEN_TREE_CLONE. | ||||
| 	// This is equivalent to openat(2), but tells us if open_tree is | ||||
| 	// available (and thus all of the other basic new mount API syscalls). | ||||
| 	// open_tree(2) is most light-weight syscall to test here. | ||||
| 	// | ||||
| 	// [1]: merge commit 400913252d09 | ||||
| 	// [2]: <https://lore.kernel.org/lkml/153754740781.17872.7869536526927736855.stgit@warthog.procyon.org.uk/> | ||||
| 	fd, err := unix.OpenTree(-int(unix.EBADF), "/", unix.OPEN_TREE_CLOEXEC) | ||||
| 	if err != nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	_ = unix.Close(fd) | ||||
|  | ||||
| 	// RHEL 8 has a backport of fsopen(2) that appears to have some very | ||||
| 	// difficult to debug performance pathology. As such, it seems prudent to | ||||
| 	// simply reject pre-5.2 kernels. | ||||
| 	isNotBackport, _ := kernelversion.GreaterEqualThan(kernelversion.KernelVersion{5, 2}) | ||||
| 	return isNotBackport | ||||
| }) | ||||
							
								
								
									
										31
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
|  | ||||
| //go:build linux | ||||
|  | ||||
| // Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| // Copyright (C) 2024-2025 SUSE LLC | ||||
| // | ||||
| // This Source Code Form is subject to the terms of the Mozilla Public | ||||
| // License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| package linux | ||||
|  | ||||
| import ( | ||||
| 	"golang.org/x/sys/unix" | ||||
|  | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" | ||||
| ) | ||||
|  | ||||
| // HasOpenat2 returns whether openat2(2) is supported on the running kernel. | ||||
| var HasOpenat2 = gocompat.SyncOnceValue(func() bool { | ||||
| 	fd, err := unix.Openat2(unix.AT_FDCWD, ".", &unix.OpenHow{ | ||||
| 		Flags:   unix.O_PATH | unix.O_CLOEXEC, | ||||
| 		Resolve: unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_IN_ROOT, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	_ = unix.Close(fd) | ||||
| 	return true | ||||
| }) | ||||
							
								
								
									
										544
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										544
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,544 @@ | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
|  | ||||
| //go:build linux | ||||
|  | ||||
| // Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| // Copyright (C) 2024-2025 SUSE LLC | ||||
| // | ||||
| // This Source Code Form is subject to the terms of the Mozilla Public | ||||
| // License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| // Package procfs provides a safe API for operating on /proc on Linux. Note | ||||
| // that this is the *internal* procfs API, mainy needed due to Go's | ||||
| // restrictions on cyclic dependencies and its incredibly minimal visibility | ||||
| // system without making a separate internal/ package. | ||||
| package procfs | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"runtime" | ||||
| 	"strconv" | ||||
|  | ||||
| 	"golang.org/x/sys/unix" | ||||
|  | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal" | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert" | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd" | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux" | ||||
| ) | ||||
|  | ||||
| // The kernel guarantees that the root inode of a procfs mount has an | ||||
| // f_type of PROC_SUPER_MAGIC and st_ino of PROC_ROOT_INO. | ||||
| const ( | ||||
| 	procSuperMagic = 0x9fa0 // PROC_SUPER_MAGIC | ||||
| 	procRootIno    = 1      // PROC_ROOT_INO | ||||
| ) | ||||
|  | ||||
| // verifyProcHandle checks that the handle is from a procfs filesystem. | ||||
| // Contrast this to [verifyProcRoot], which also verifies that the handle is | ||||
| // the root of a procfs mount. | ||||
| func verifyProcHandle(procHandle fd.Fd) error { | ||||
| 	if statfs, err := fd.Fstatfs(procHandle); err != nil { | ||||
| 		return err | ||||
| 	} else if statfs.Type != procSuperMagic { | ||||
| 		return fmt.Errorf("%w: incorrect procfs root filesystem type 0x%x", errUnsafeProcfs, statfs.Type) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // verifyProcRoot verifies that the handle is the root of a procfs filesystem. | ||||
| // Contrast this to [verifyProcHandle], which only verifies if the handle is | ||||
| // some file on procfs (regardless of what file it is). | ||||
| func verifyProcRoot(procRoot fd.Fd) error { | ||||
| 	if err := verifyProcHandle(procRoot); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if stat, err := fd.Fstat(procRoot); err != nil { | ||||
| 		return err | ||||
| 	} else if stat.Ino != procRootIno { | ||||
| 		return fmt.Errorf("%w: incorrect procfs root inode number %d", errUnsafeProcfs, stat.Ino) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type procfsFeatures struct { | ||||
| 	// hasSubsetPid was added in Linux 5.8, along with hidepid=ptraceable (and | ||||
| 	// string-based hidepid= values). Before this patchset, it was not really | ||||
| 	// safe to try to modify procfs superblock flags because the superblock was | ||||
| 	// shared -- so if this feature is not available, **you should not set any | ||||
| 	// superblock flags**. | ||||
| 	// | ||||
| 	// 6814ef2d992a ("proc: add option to mount only a pids subset") | ||||
| 	// fa10fed30f25 ("proc: allow to mount many instances of proc in one pid namespace") | ||||
| 	// 24a71ce5c47f ("proc: instantiate only pids that we can ptrace on 'hidepid=4' mount option") | ||||
| 	// 1c6c4d112e81 ("proc: use human-readable values for hidepid") | ||||
| 	// 9ff7258575d5 ("Merge branch 'proc-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace") | ||||
| 	hasSubsetPid bool | ||||
| } | ||||
|  | ||||
| var getProcfsFeatures = gocompat.SyncOnceValue(func() procfsFeatures { | ||||
| 	if !linux.HasNewMountAPI() { | ||||
| 		return procfsFeatures{} | ||||
| 	} | ||||
| 	procfsCtx, err := fd.Fsopen("proc", unix.FSOPEN_CLOEXEC) | ||||
| 	if err != nil { | ||||
| 		return procfsFeatures{} | ||||
| 	} | ||||
| 	defer procfsCtx.Close() //nolint:errcheck // close failures aren't critical here | ||||
|  | ||||
| 	return procfsFeatures{ | ||||
| 		hasSubsetPid: unix.FsconfigSetString(int(procfsCtx.Fd()), "subset", "pid") == nil, | ||||
| 	} | ||||
| }) | ||||
|  | ||||
| func newPrivateProcMount(subset bool) (_ *Handle, Err error) { | ||||
| 	procfsCtx, err := fd.Fsopen("proc", unix.FSOPEN_CLOEXEC) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer procfsCtx.Close() //nolint:errcheck // close failures aren't critical here | ||||
|  | ||||
| 	if subset && getProcfsFeatures().hasSubsetPid { | ||||
| 		// Try to configure hidepid=ptraceable,subset=pid if possible, but | ||||
| 		// ignore errors. | ||||
| 		_ = unix.FsconfigSetString(int(procfsCtx.Fd()), "hidepid", "ptraceable") | ||||
| 		_ = unix.FsconfigSetString(int(procfsCtx.Fd()), "subset", "pid") | ||||
| 	} | ||||
|  | ||||
| 	// Get an actual handle. | ||||
| 	if err := unix.FsconfigCreate(int(procfsCtx.Fd())); err != nil { | ||||
| 		return nil, os.NewSyscallError("fsconfig create procfs", err) | ||||
| 	} | ||||
| 	// TODO: Output any information from the fscontext log to debug logs. | ||||
| 	procRoot, err := fd.Fsmount(procfsCtx, unix.FSMOUNT_CLOEXEC, unix.MS_NODEV|unix.MS_NOEXEC|unix.MS_NOSUID) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer func() { | ||||
| 		if Err != nil { | ||||
| 			_ = procRoot.Close() | ||||
| 		} | ||||
| 	}() | ||||
| 	return newHandle(procRoot) | ||||
| } | ||||
|  | ||||
| func clonePrivateProcMount() (_ *Handle, Err error) { | ||||
| 	// Try to make a clone without using AT_RECURSIVE if we can. If this works, | ||||
| 	// we can be sure there are no over-mounts and so if the root is valid then | ||||
| 	// we're golden. Otherwise, we have to deal with over-mounts. | ||||
| 	procRoot, err := fd.OpenTree(nil, "/proc", unix.OPEN_TREE_CLONE) | ||||
| 	if err != nil || hookForcePrivateProcRootOpenTreeAtRecursive(procRoot) { | ||||
| 		procRoot, err = fd.OpenTree(nil, "/proc", unix.OPEN_TREE_CLONE|unix.AT_RECURSIVE) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("creating a detached procfs clone: %w", err) | ||||
| 	} | ||||
| 	defer func() { | ||||
| 		if Err != nil { | ||||
| 			_ = procRoot.Close() | ||||
| 		} | ||||
| 	}() | ||||
| 	return newHandle(procRoot) | ||||
| } | ||||
|  | ||||
| func privateProcRoot(subset bool) (*Handle, error) { | ||||
| 	if !linux.HasNewMountAPI() || hookForceGetProcRootUnsafe() { | ||||
| 		return nil, fmt.Errorf("new mount api: %w", unix.ENOTSUP) | ||||
| 	} | ||||
| 	// Try to create a new procfs mount from scratch if we can. This ensures we | ||||
| 	// can get a procfs mount even if /proc is fake (for whatever reason). | ||||
| 	procRoot, err := newPrivateProcMount(subset) | ||||
| 	if err != nil || hookForcePrivateProcRootOpenTree(procRoot) { | ||||
| 		// Try to clone /proc then... | ||||
| 		procRoot, err = clonePrivateProcMount() | ||||
| 	} | ||||
| 	return procRoot, err | ||||
| } | ||||
|  | ||||
| func unsafeHostProcRoot() (_ *Handle, Err error) { | ||||
| 	procRoot, err := os.OpenFile("/proc", unix.O_PATH|unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer func() { | ||||
| 		if Err != nil { | ||||
| 			_ = procRoot.Close() | ||||
| 		} | ||||
| 	}() | ||||
| 	return newHandle(procRoot) | ||||
| } | ||||
|  | ||||
| // Handle is a wrapper around an *os.File handle to "/proc", which can be used | ||||
| // to do further procfs-related operations in a safe way. | ||||
| type Handle struct { | ||||
| 	Inner fd.Fd | ||||
| 	// Does this handle have subset=pid set? | ||||
| 	isSubset bool | ||||
| } | ||||
|  | ||||
| func newHandle(procRoot fd.Fd) (*Handle, error) { | ||||
| 	if err := verifyProcRoot(procRoot); err != nil { | ||||
| 		// This is only used in methods that | ||||
| 		_ = procRoot.Close() | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	proc := &Handle{Inner: procRoot} | ||||
| 	// With subset=pid we can be sure that /proc/uptime will not exist. | ||||
| 	if err := fd.Faccessat(proc.Inner, "uptime", unix.F_OK, unix.AT_SYMLINK_NOFOLLOW); err != nil { | ||||
| 		proc.isSubset = errors.Is(err, os.ErrNotExist) | ||||
| 	} | ||||
| 	return proc, nil | ||||
| } | ||||
|  | ||||
| // Close closes the underlying file for the Handle. | ||||
| func (proc *Handle) Close() error { return proc.Inner.Close() } | ||||
|  | ||||
| var getCachedProcRoot = gocompat.SyncOnceValue(func() *Handle { | ||||
| 	procRoot, err := getProcRoot(true) | ||||
| 	if err != nil { | ||||
| 		return nil // just don't cache if we see an error | ||||
| 	} | ||||
| 	if !procRoot.isSubset { | ||||
| 		return nil // we only cache verified subset=pid handles | ||||
| 	} | ||||
|  | ||||
| 	// Disarm (*Handle).Close() to stop someone from accidentally closing | ||||
| 	// the global handle. | ||||
| 	procRoot.Inner = fd.NopCloser(procRoot.Inner) | ||||
| 	return procRoot | ||||
| }) | ||||
|  | ||||
| // OpenProcRoot tries to open a "safer" handle to "/proc". | ||||
| func OpenProcRoot() (*Handle, error) { | ||||
| 	if proc := getCachedProcRoot(); proc != nil { | ||||
| 		return proc, nil | ||||
| 	} | ||||
| 	return getProcRoot(true) | ||||
| } | ||||
|  | ||||
| // OpenUnsafeProcRoot opens a handle to "/proc" without any overmounts or | ||||
| // masked paths (but also without "subset=pid"). | ||||
| func OpenUnsafeProcRoot() (*Handle, error) { return getProcRoot(false) } | ||||
|  | ||||
| func getProcRoot(subset bool) (*Handle, error) { | ||||
| 	proc, err := privateProcRoot(subset) | ||||
| 	if err != nil { | ||||
| 		// Fall back to using a /proc handle if making a private mount failed. | ||||
| 		// If we have openat2, at least we can avoid some kinds of over-mount | ||||
| 		// attacks, but without openat2 there's not much we can do. | ||||
| 		proc, err = unsafeHostProcRoot() | ||||
| 	} | ||||
| 	return proc, err | ||||
| } | ||||
|  | ||||
| var hasProcThreadSelf = gocompat.SyncOnceValue(func() bool { | ||||
| 	return unix.Access("/proc/thread-self/", unix.F_OK) == nil | ||||
| }) | ||||
|  | ||||
| var errUnsafeProcfs = errors.New("unsafe procfs detected") | ||||
|  | ||||
| // lookup is a very minimal wrapper around [procfsLookupInRoot] which is | ||||
| // intended to be called from the external API. | ||||
| func (proc *Handle) lookup(subpath string) (*os.File, error) { | ||||
| 	handle, err := procfsLookupInRoot(proc.Inner, subpath) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return handle, nil | ||||
| } | ||||
|  | ||||
| // procfsBase is an enum indicating the prefix of a subpath in operations | ||||
| // involving [Handle]s. | ||||
| type procfsBase string | ||||
|  | ||||
| const ( | ||||
| 	// ProcRoot refers to the root of the procfs (i.e., "/proc/<subpath>"). | ||||
| 	ProcRoot procfsBase = "/proc" | ||||
| 	// ProcSelf refers to the current process' subdirectory (i.e., | ||||
| 	// "/proc/self/<subpath>"). | ||||
| 	ProcSelf procfsBase = "/proc/self" | ||||
| 	// ProcThreadSelf refers to the current thread's subdirectory (i.e., | ||||
| 	// "/proc/thread-self/<subpath>"). In multi-threaded programs (i.e., all Go | ||||
| 	// programs) where one thread has a different CLONE_FS, it is possible for | ||||
| 	// "/proc/self" to point the wrong thread and so "/proc/thread-self" may be | ||||
| 	// necessary. Note that on pre-3.17 kernels, "/proc/thread-self" doesn't | ||||
| 	// exist and so a fallback will be used in that case. | ||||
| 	ProcThreadSelf procfsBase = "/proc/thread-self" | ||||
| 	// TODO: Switch to an interface setup so we can have a more type-safe | ||||
| 	// version of ProcPid and remove the need to worry about invalid string | ||||
| 	// values. | ||||
| ) | ||||
|  | ||||
| // prefix returns a prefix that can be used with the given [Handle]. | ||||
| func (base procfsBase) prefix(proc *Handle) (string, error) { | ||||
| 	switch base { | ||||
| 	case ProcRoot: | ||||
| 		return ".", nil | ||||
| 	case ProcSelf: | ||||
| 		return "self", nil | ||||
| 	case ProcThreadSelf: | ||||
| 		threadSelf := "thread-self" | ||||
| 		if !hasProcThreadSelf() || hookForceProcSelfTask() { | ||||
| 			// Pre-3.17 kernels don't have /proc/thread-self, so do it | ||||
| 			// manually. | ||||
| 			threadSelf = "self/task/" + strconv.Itoa(unix.Gettid()) | ||||
| 			if err := fd.Faccessat(proc.Inner, threadSelf, unix.F_OK, unix.AT_SYMLINK_NOFOLLOW); err != nil || hookForceProcSelf() { | ||||
| 				// In this case, we running in a pid namespace that doesn't | ||||
| 				// match the /proc mount we have. This can happen inside runc. | ||||
| 				// | ||||
| 				// Unfortunately, there is no nice way to get the correct TID | ||||
| 				// to use here because of the age of the kernel, so we have to | ||||
| 				// just use /proc/self and hope that it works. | ||||
| 				threadSelf = "self" | ||||
| 			} | ||||
| 		} | ||||
| 		return threadSelf, nil | ||||
| 	} | ||||
| 	return "", fmt.Errorf("invalid procfs base %q", base) | ||||
| } | ||||
|  | ||||
| // ProcThreadSelfCloser is a callback that needs to be called when you are done | ||||
| // operating on an [os.File] fetched using [ProcThreadSelf]. | ||||
| // | ||||
| // [os.File]: https://pkg.go.dev/os#File | ||||
| type ProcThreadSelfCloser func() | ||||
|  | ||||
| // open is the core lookup operation for [Handle]. It returns a handle to | ||||
| // "/proc/<base>/<subpath>". If the returned [ProcThreadSelfCloser] is non-nil, | ||||
| // you should call it after you are done interacting with the returned handle. | ||||
| // | ||||
| // In general you should use prefer to use the other helpers, as they remove | ||||
| // the need to interact with [procfsBase] and do not return a nil | ||||
| // [ProcThreadSelfCloser] for [procfsBase] values other than [ProcThreadSelf] | ||||
| // where it is necessary. | ||||
| func (proc *Handle) open(base procfsBase, subpath string) (_ *os.File, closer ProcThreadSelfCloser, Err error) { | ||||
| 	prefix, err := base.prefix(proc) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	subpath = prefix + "/" + subpath | ||||
|  | ||||
| 	switch base { | ||||
| 	case ProcRoot: | ||||
| 		file, err := proc.lookup(subpath) | ||||
| 		if errors.Is(err, os.ErrNotExist) { | ||||
| 			// The Handle handle in use might be a subset=pid one, which will | ||||
| 			// result in spurious errors. In this case, just open a temporary | ||||
| 			// unmasked procfs handle for this operation. | ||||
| 			proc, err2 := OpenUnsafeProcRoot() // !subset=pid | ||||
| 			if err2 != nil { | ||||
| 				return nil, nil, err | ||||
| 			} | ||||
| 			defer proc.Close() //nolint:errcheck // close failures aren't critical here | ||||
|  | ||||
| 			file, err = proc.lookup(subpath) | ||||
| 		} | ||||
| 		return file, nil, err | ||||
|  | ||||
| 	case ProcSelf: | ||||
| 		file, err := proc.lookup(subpath) | ||||
| 		return file, nil, err | ||||
|  | ||||
| 	case ProcThreadSelf: | ||||
| 		// We need to lock our thread until the caller is done with the handle | ||||
| 		// because between getting the handle and using it we could get | ||||
| 		// interrupted by the Go runtime and hit the case where the underlying | ||||
| 		// thread is swapped out and the original thread is killed, resulting | ||||
| 		// in pull-your-hair-out-hard-to-debug issues in the caller. | ||||
| 		runtime.LockOSThread() | ||||
| 		defer func() { | ||||
| 			if Err != nil { | ||||
| 				runtime.UnlockOSThread() | ||||
| 				closer = nil | ||||
| 			} | ||||
| 		}() | ||||
|  | ||||
| 		file, err := proc.lookup(subpath) | ||||
| 		return file, runtime.UnlockOSThread, err | ||||
| 	} | ||||
| 	// should never be reached | ||||
| 	return nil, nil, fmt.Errorf("[internal error] invalid procfs base %q", base) | ||||
| } | ||||
|  | ||||
| // OpenThreadSelf returns a handle to "/proc/thread-self/<subpath>" (or an | ||||
| // equivalent handle on older kernels where "/proc/thread-self" doesn't exist). | ||||
| // Once finished with the handle, you must call the returned closer function | ||||
| // (runtime.UnlockOSThread). You must not pass the returned *os.File to other | ||||
| // Go threads or use the handle after calling the closer. | ||||
| func (proc *Handle) OpenThreadSelf(subpath string) (_ *os.File, _ ProcThreadSelfCloser, Err error) { | ||||
| 	return proc.open(ProcThreadSelf, subpath) | ||||
| } | ||||
|  | ||||
| // OpenSelf returns a handle to /proc/self/<subpath>. | ||||
| func (proc *Handle) OpenSelf(subpath string) (*os.File, error) { | ||||
| 	file, closer, err := proc.open(ProcSelf, subpath) | ||||
| 	assert.Assert(closer == nil, "closer for ProcSelf must be nil") | ||||
| 	return file, err | ||||
| } | ||||
|  | ||||
| // OpenRoot returns a handle to /proc/<subpath>. | ||||
| func (proc *Handle) OpenRoot(subpath string) (*os.File, error) { | ||||
| 	file, closer, err := proc.open(ProcRoot, subpath) | ||||
| 	assert.Assert(closer == nil, "closer for ProcRoot must be nil") | ||||
| 	return file, err | ||||
| } | ||||
|  | ||||
| // OpenPid returns a handle to /proc/$pid/<subpath> (pid can be a pid or tid). | ||||
| // This is mainly intended for usage when operating on other processes. | ||||
| func (proc *Handle) OpenPid(pid int, subpath string) (*os.File, error) { | ||||
| 	return proc.OpenRoot(strconv.Itoa(pid) + "/" + subpath) | ||||
| } | ||||
|  | ||||
| // checkSubpathOvermount checks if the dirfd and path combination is on the | ||||
| // same mount as the given root. | ||||
| func checkSubpathOvermount(root, dir fd.Fd, path string) error { | ||||
| 	// Get the mntID of our procfs handle. | ||||
| 	expectedMountID, err := fd.GetMountID(root, "") | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("get root mount id: %w", err) | ||||
| 	} | ||||
| 	// Get the mntID of the target magic-link. | ||||
| 	gotMountID, err := fd.GetMountID(dir, path) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("get subpath mount id: %w", err) | ||||
| 	} | ||||
| 	// As long as the directory mount is alive, even with wrapping mount IDs, | ||||
| 	// we would expect to see a different mount ID here. (Of course, if we're | ||||
| 	// using unsafeHostProcRoot() then an attaker could change this after we | ||||
| 	// did this check.) | ||||
| 	if expectedMountID != gotMountID { | ||||
| 		return fmt.Errorf("%w: subpath %s/%s has an overmount obscuring the real path (mount ids do not match %d != %d)", | ||||
| 			errUnsafeProcfs, dir.Name(), path, expectedMountID, gotMountID) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Readlink performs a readlink operation on "/proc/<base>/<subpath>" in a way | ||||
| // that should be free from race attacks. This is most commonly used to get the | ||||
| // real path of a file by looking at "/proc/self/fd/$n", with the same safety | ||||
| // protections as [Open] (as well as some additional checks against | ||||
| // overmounts). | ||||
| func (proc *Handle) Readlink(base procfsBase, subpath string) (string, error) { | ||||
| 	link, closer, err := proc.open(base, subpath) | ||||
| 	if closer != nil { | ||||
| 		defer closer() | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("get safe %s/%s handle: %w", base, subpath, err) | ||||
| 	} | ||||
| 	defer link.Close() //nolint:errcheck // close failures aren't critical here | ||||
|  | ||||
| 	// Try to detect if there is a mount on top of the magic-link. This should | ||||
| 	// be safe in general (a mount on top of the path afterwards would not | ||||
| 	// affect the handle itself) and will definitely be safe if we are using | ||||
| 	// privateProcRoot() (at least since Linux 5.12[1], when anonymous mount | ||||
| 	// namespaces were completely isolated from external mounts including mount | ||||
| 	// propagation events). | ||||
| 	// | ||||
| 	// [1]: Linux commit ee2e3f50629f ("mount: fix mounting of detached mounts | ||||
| 	// onto targets that reside on shared mounts"). | ||||
| 	if err := checkSubpathOvermount(proc.Inner, link, ""); err != nil { | ||||
| 		return "", fmt.Errorf("check safety of %s/%s magiclink: %w", base, subpath, err) | ||||
| 	} | ||||
|  | ||||
| 	// readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See Linux commit | ||||
| 	// 65cfc6722361 ("readlinkat(), fchownat() and fstatat() with empty | ||||
| 	// relative pathnames"). | ||||
| 	return fd.Readlinkat(link, "") | ||||
| } | ||||
|  | ||||
| // ProcSelfFdReadlink gets the real path of the given file by looking at | ||||
| // readlink(/proc/thread-self/fd/$n). | ||||
| // | ||||
| // This is just a wrapper around [Handle.Readlink]. | ||||
| func ProcSelfFdReadlink(fd fd.Fd) (string, error) { | ||||
| 	procRoot, err := OpenProcRoot() // subset=pid | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	defer procRoot.Close() //nolint:errcheck // close failures aren't critical here | ||||
|  | ||||
| 	fdPath := "fd/" + strconv.Itoa(int(fd.Fd())) | ||||
| 	return procRoot.Readlink(ProcThreadSelf, fdPath) | ||||
| } | ||||
|  | ||||
| // CheckProcSelfFdPath returns whether the given file handle matches the | ||||
| // expected path. (This is inherently racy.) | ||||
| func CheckProcSelfFdPath(path string, file fd.Fd) error { | ||||
| 	if err := fd.IsDeadInode(file); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	actualPath, err := ProcSelfFdReadlink(file) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("get path of handle: %w", err) | ||||
| 	} | ||||
| 	if actualPath != path { | ||||
| 		return fmt.Errorf("%w: handle path %q doesn't match expected path %q", internal.ErrPossibleBreakout, actualPath, path) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ReopenFd takes an existing file descriptor and "re-opens" it through | ||||
| // /proc/thread-self/fd/<fd>. This allows for O_PATH file descriptors to be | ||||
| // upgraded to regular file descriptors, as well as changing the open mode of a | ||||
| // regular file descriptor. Some filesystems have unique handling of open(2) | ||||
| // which make this incredibly useful (such as /dev/ptmx). | ||||
| func ReopenFd(handle fd.Fd, flags int) (*os.File, error) { | ||||
| 	procRoot, err := OpenProcRoot() // subset=pid | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer procRoot.Close() //nolint:errcheck // close failures aren't critical here | ||||
|  | ||||
| 	// We can't operate on /proc/thread-self/fd/$n directly when doing a | ||||
| 	// re-open, so we need to open /proc/thread-self/fd and then open a single | ||||
| 	// final component. | ||||
| 	procFdDir, closer, err := procRoot.OpenThreadSelf("fd/") | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("get safe /proc/thread-self/fd handle: %w", err) | ||||
| 	} | ||||
| 	defer procFdDir.Close() //nolint:errcheck // close failures aren't critical here | ||||
| 	defer closer() | ||||
|  | ||||
| 	// Try to detect if there is a mount on top of the magic-link we are about | ||||
| 	// to open. If we are using unsafeHostProcRoot(), this could change after | ||||
| 	// we check it (and there's nothing we can do about that) but for | ||||
| 	// privateProcRoot() this should be guaranteed to be safe (at least since | ||||
| 	// Linux 5.12[1], when anonymous mount namespaces were completely isolated | ||||
| 	// from external mounts including mount propagation events). | ||||
| 	// | ||||
| 	// [1]: Linux commit ee2e3f50629f ("mount: fix mounting of detached mounts | ||||
| 	// onto targets that reside on shared mounts"). | ||||
| 	fdStr := strconv.Itoa(int(handle.Fd())) | ||||
| 	if err := checkSubpathOvermount(procRoot.Inner, procFdDir, fdStr); err != nil { | ||||
| 		return nil, fmt.Errorf("check safety of /proc/thread-self/fd/%s magiclink: %w", fdStr, err) | ||||
| 	} | ||||
|  | ||||
| 	flags |= unix.O_CLOEXEC | ||||
| 	// Rather than just wrapping fd.Openat, open-code it so we can copy | ||||
| 	// handle.Name(). | ||||
| 	reopenFd, err := unix.Openat(int(procFdDir.Fd()), fdStr, flags, 0) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("reopen fd %d: %w", handle.Fd(), err) | ||||
| 	} | ||||
| 	return os.NewFile(uintptr(reopenFd), handle.Name()), nil | ||||
| } | ||||
|  | ||||
| // Test hooks used in the procfs tests to verify that the fallback logic works. | ||||
| // See testing_mocks_linux_test.go and procfs_linux_test.go for more details. | ||||
| var ( | ||||
| 	hookForcePrivateProcRootOpenTree            = hookDummyFile | ||||
| 	hookForcePrivateProcRootOpenTreeAtRecursive = hookDummyFile | ||||
| 	hookForceGetProcRootUnsafe                  = hookDummy | ||||
|  | ||||
| 	hookForceProcSelfTask = hookDummy | ||||
| 	hookForceProcSelf     = hookDummy | ||||
| ) | ||||
|  | ||||
| func hookDummy() bool                { return false } | ||||
| func hookDummyFile(_ io.Closer) bool { return false } | ||||
							
								
								
									
										222
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_lookup_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_lookup_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,222 @@ | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
|  | ||||
| //go:build linux | ||||
|  | ||||
| // Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| // Copyright (C) 2024-2025 SUSE LLC | ||||
| // | ||||
| // This Source Code Form is subject to the terms of the Mozilla Public | ||||
| // License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| // This code is adapted to be a minimal version of the libpathrs proc resolver | ||||
| // <https://github.com/opensuse/libpathrs/blob/v0.1.3/src/resolvers/procfs.rs>. | ||||
| // As we only need O_PATH|O_NOFOLLOW support, this is not too much to port. | ||||
|  | ||||
| package procfs | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
|  | ||||
| 	"golang.org/x/sys/unix" | ||||
|  | ||||
| 	"github.com/cyphar/filepath-securejoin/internal/consts" | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal" | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd" | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux" | ||||
| ) | ||||
|  | ||||
| // procfsLookupInRoot is a stripped down version of completeLookupInRoot, | ||||
| // entirely designed to support the very small set of features necessary to | ||||
| // make procfs handling work. Unlike completeLookupInRoot, we always have | ||||
| // O_PATH|O_NOFOLLOW behaviour for trailing symlinks. | ||||
| // | ||||
| // The main restrictions are: | ||||
| // | ||||
| //   - ".." is not supported (as it requires either os.Root-style replays, | ||||
| //     which is more bug-prone; or procfs verification, which is not possible | ||||
| //     due to re-entrancy issues). | ||||
| //   - Absolute symlinks for the same reason (and all absolute symlinks in | ||||
| //     procfs are magic-links, which we want to skip anyway). | ||||
| //   - If statx is supported (checkSymlinkOvermount), any mount-point crossings | ||||
| //     (which is the main attack of concern against /proc). | ||||
| //   - Partial lookups are not supported, so the symlink stack is not needed. | ||||
| //   - Trailing slash special handling is not necessary in most cases (if we | ||||
| //     operating on procfs, it's usually with programmer-controlled strings | ||||
| //     that will then be re-opened), so we skip it since whatever re-opens it | ||||
| //     can deal with it. It's a creature comfort anyway. | ||||
| // | ||||
| // If the system supports openat2(), this is implemented using equivalent flags | ||||
| // (RESOLVE_BENEATH | RESOLVE_NO_XDEV | RESOLVE_NO_MAGICLINKS). | ||||
| func procfsLookupInRoot(procRoot fd.Fd, unsafePath string) (Handle *os.File, _ error) { | ||||
| 	unsafePath = filepath.ToSlash(unsafePath) // noop | ||||
|  | ||||
| 	// Make sure that an empty unsafe path still returns something sane, even | ||||
| 	// with openat2 (which doesn't have AT_EMPTY_PATH semantics yet). | ||||
| 	if unsafePath == "" { | ||||
| 		unsafePath = "." | ||||
| 	} | ||||
|  | ||||
| 	// This is already checked by getProcRoot, but make sure here since the | ||||
| 	// core security of this lookup is based on this assumption. | ||||
| 	if err := verifyProcRoot(procRoot); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if linux.HasOpenat2() { | ||||
| 		// We prefer being able to use RESOLVE_NO_XDEV if we can, to be | ||||
| 		// absolutely sure we are operating on a clean /proc handle that | ||||
| 		// doesn't have any cheeky overmounts that could trick us (including | ||||
| 		// symlink mounts on top of /proc/thread-self). RESOLVE_BENEATH isn't | ||||
| 		// strictly needed, but just use it since we have it. | ||||
| 		// | ||||
| 		// NOTE: /proc/self is technically a magic-link (the contents of the | ||||
| 		//       symlink are generated dynamically), but it doesn't use | ||||
| 		//       nd_jump_link() so RESOLVE_NO_MAGICLINKS allows it. | ||||
| 		// | ||||
| 		// TODO: It would be nice to have RESOLVE_NO_DOTDOT, purely for | ||||
| 		//       self-consistency with the backup O_PATH resolver. | ||||
| 		handle, err := fd.Openat2(procRoot, unsafePath, &unix.OpenHow{ | ||||
| 			Flags:   unix.O_PATH | unix.O_NOFOLLOW | unix.O_CLOEXEC, | ||||
| 			Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_XDEV | unix.RESOLVE_NO_MAGICLINKS, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			// TODO: Once we bump the minimum Go version to 1.20, we can use | ||||
| 			// multiple %w verbs for this wrapping. For now we need to use a | ||||
| 			// compatibility shim for older Go versions. | ||||
| 			// err = fmt.Errorf("%w: %w", errUnsafeProcfs, err) | ||||
| 			return nil, gocompat.WrapBaseError(err, errUnsafeProcfs) | ||||
| 		} | ||||
| 		return handle, nil | ||||
| 	} | ||||
|  | ||||
| 	// To mirror openat2(RESOLVE_BENEATH), we need to return an error if the | ||||
| 	// path is absolute. | ||||
| 	if path.IsAbs(unsafePath) { | ||||
| 		return nil, fmt.Errorf("%w: cannot resolve absolute paths in procfs resolver", internal.ErrPossibleBreakout) | ||||
| 	} | ||||
|  | ||||
| 	currentDir, err := fd.Dup(procRoot) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("clone root fd: %w", err) | ||||
| 	} | ||||
| 	defer func() { | ||||
| 		// If a handle is not returned, close the internal handle. | ||||
| 		if Handle == nil { | ||||
| 			_ = currentDir.Close() | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	var ( | ||||
| 		linksWalked   int | ||||
| 		currentPath   string | ||||
| 		remainingPath = unsafePath | ||||
| 	) | ||||
| 	for remainingPath != "" { | ||||
| 		// Get the next path component. | ||||
| 		var part string | ||||
| 		if i := strings.IndexByte(remainingPath, '/'); i == -1 { | ||||
| 			part, remainingPath = remainingPath, "" | ||||
| 		} else { | ||||
| 			part, remainingPath = remainingPath[:i], remainingPath[i+1:] | ||||
| 		} | ||||
| 		if part == "" { | ||||
| 			// no-op component, but treat it the same as "." | ||||
| 			part = "." | ||||
| 		} | ||||
| 		if part == ".." { | ||||
| 			// not permitted | ||||
| 			return nil, fmt.Errorf("%w: cannot walk into '..' in procfs resolver", internal.ErrPossibleBreakout) | ||||
| 		} | ||||
|  | ||||
| 		// Apply the component lexically to the path we are building. | ||||
| 		// currentPath does not contain any symlinks, and we are lexically | ||||
| 		// dealing with a single component, so it's okay to do a filepath.Clean | ||||
| 		// here. (Not to mention that ".." isn't allowed.) | ||||
| 		nextPath := path.Join("/", currentPath, part) | ||||
| 		// If we logically hit the root, just clone the root rather than | ||||
| 		// opening the part and doing all of the other checks. | ||||
| 		if nextPath == "/" { | ||||
| 			// Jump to root. | ||||
| 			rootClone, err := fd.Dup(procRoot) | ||||
| 			if err != nil { | ||||
| 				return nil, fmt.Errorf("clone root fd: %w", err) | ||||
| 			} | ||||
| 			_ = currentDir.Close() | ||||
| 			currentDir = rootClone | ||||
| 			currentPath = nextPath | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// Try to open the next component. | ||||
| 		nextDir, err := fd.Openat(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		// Make sure we are still on procfs and haven't crossed mounts. | ||||
| 		if err := verifyProcHandle(nextDir); err != nil { | ||||
| 			_ = nextDir.Close() | ||||
| 			return nil, fmt.Errorf("check %q component is on procfs: %w", part, err) | ||||
| 		} | ||||
| 		if err := checkSubpathOvermount(procRoot, nextDir, ""); err != nil { | ||||
| 			_ = nextDir.Close() | ||||
| 			return nil, fmt.Errorf("check %q component is not overmounted: %w", part, err) | ||||
| 		} | ||||
|  | ||||
| 		// We are emulating O_PATH|O_NOFOLLOW, so we only need to traverse into | ||||
| 		// trailing symlinks if we are not the final component. Otherwise we | ||||
| 		// can just return the currentDir. | ||||
| 		if remainingPath != "" { | ||||
| 			st, err := nextDir.Stat() | ||||
| 			if err != nil { | ||||
| 				_ = nextDir.Close() | ||||
| 				return nil, fmt.Errorf("stat component %q: %w", part, err) | ||||
| 			} | ||||
|  | ||||
| 			if st.Mode()&os.ModeType == os.ModeSymlink { | ||||
| 				// readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See | ||||
| 				// Linux commit 65cfc6722361 ("readlinkat(), fchownat() and | ||||
| 				// fstatat() with empty relative pathnames"). | ||||
| 				linkDest, err := fd.Readlinkat(nextDir, "") | ||||
| 				// We don't need the handle anymore. | ||||
| 				_ = nextDir.Close() | ||||
| 				if err != nil { | ||||
| 					return nil, err | ||||
| 				} | ||||
|  | ||||
| 				linksWalked++ | ||||
| 				if linksWalked > consts.MaxSymlinkLimit { | ||||
| 					return nil, &os.PathError{Op: "securejoin.procfsLookupInRoot", Path: "/proc/" + unsafePath, Err: unix.ELOOP} | ||||
| 				} | ||||
|  | ||||
| 				// Update our logical remaining path. | ||||
| 				remainingPath = linkDest + "/" + remainingPath | ||||
| 				// Absolute symlinks are probably magiclinks, we reject them. | ||||
| 				if path.IsAbs(linkDest) { | ||||
| 					return nil, fmt.Errorf("%w: cannot jump to / in procfs resolver -- possible magiclink", internal.ErrPossibleBreakout) | ||||
| 				} | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Walk into the next component. | ||||
| 		_ = currentDir.Close() | ||||
| 		currentDir = nextDir | ||||
| 		currentPath = nextPath | ||||
| 	} | ||||
|  | ||||
| 	// One final sanity-check. | ||||
| 	if err := verifyProcHandle(currentDir); err != nil { | ||||
| 		return nil, fmt.Errorf("check final handle is on procfs: %w", err) | ||||
| 	} | ||||
| 	if err := checkSubpathOvermount(procRoot, currentDir, ""); err != nil { | ||||
| 		return nil, fmt.Errorf("check final handle is not overmounted: %w", err) | ||||
| 	} | ||||
| 	return currentDir, nil | ||||
| } | ||||
| @ -1,10 +1,15 @@ | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| //go:build linux | ||||
| 
 | ||||
| // Copyright (C) 2024 SUSE LLC. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
| // Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| // Copyright (C) 2024-2025 SUSE LLC | ||||
| // | ||||
| // This Source Code Form is subject to the terms of the Mozilla Public | ||||
| // License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
| 
 | ||||
| package securejoin | ||||
| package pathrs | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| @ -15,6 +20,12 @@ import ( | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"golang.org/x/sys/unix" | ||||
| 
 | ||||
| 	"github.com/cyphar/filepath-securejoin/internal/consts" | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd" | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux" | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs" | ||||
| ) | ||||
| 
 | ||||
| type symlinkStackEntry struct { | ||||
| @ -112,12 +123,12 @@ func (s *symlinkStack) push(dir *os.File, remainingPath, linkTarget string) erro | ||||
| 		return nil | ||||
| 	} | ||||
| 	// Split the link target and clean up any "" parts. | ||||
| 	linkTargetParts := slices_DeleteFunc( | ||||
| 	linkTargetParts := gocompat.SlicesDeleteFunc( | ||||
| 		strings.Split(linkTarget, "/"), | ||||
| 		func(part string) bool { return part == "" || part == "." }) | ||||
| 
 | ||||
| 	// Copy the directory so the caller doesn't close our copy. | ||||
| 	dirCopy, err := dupFile(dir) | ||||
| 	dirCopy, err := fd.Dup(dir) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @ -159,11 +170,11 @@ func (s *symlinkStack) PopTopSymlink() (*os.File, string, bool) { | ||||
| // within the provided root (a-la RESOLVE_IN_ROOT) and opens the final existing | ||||
| // component of the requested path, returning a file handle to the final | ||||
| // existing component and a string containing the remaining path components. | ||||
| func partialLookupInRoot(root *os.File, unsafePath string) (*os.File, string, error) { | ||||
| func partialLookupInRoot(root fd.Fd, unsafePath string) (*os.File, string, error) { | ||||
| 	return lookupInRoot(root, unsafePath, true) | ||||
| } | ||||
| 
 | ||||
| func completeLookupInRoot(root *os.File, unsafePath string) (*os.File, error) { | ||||
| func completeLookupInRoot(root fd.Fd, unsafePath string) (*os.File, error) { | ||||
| 	handle, remainingPath, err := lookupInRoot(root, unsafePath, false) | ||||
| 	if remainingPath != "" && err == nil { | ||||
| 		// should never happen | ||||
| @ -174,7 +185,7 @@ func completeLookupInRoot(root *os.File, unsafePath string) (*os.File, error) { | ||||
| 	return handle, err | ||||
| } | ||||
| 
 | ||||
| func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.File, _ string, _ error) { | ||||
| func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File, _ string, _ error) { | ||||
| 	unsafePath = filepath.ToSlash(unsafePath) // noop | ||||
| 
 | ||||
| 	// This is very similar to SecureJoin, except that we operate on the | ||||
| @ -182,20 +193,20 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi | ||||
| 	// managed open, along with the remaining path components not opened. | ||||
| 
 | ||||
| 	// Try to use openat2 if possible. | ||||
| 	if hasOpenat2() { | ||||
| 	if linux.HasOpenat2() { | ||||
| 		return lookupOpenat2(root, unsafePath, partial) | ||||
| 	} | ||||
| 
 | ||||
| 	// Get the "actual" root path from /proc/self/fd. This is necessary if the | ||||
| 	// root is some magic-link like /proc/$pid/root, in which case we want to | ||||
| 	// make sure when we do checkProcSelfFdPath that we are using the correct | ||||
| 	// root path. | ||||
| 	logicalRootPath, err := procSelfFdReadlink(root) | ||||
| 	// make sure when we do procfs.CheckProcSelfFdPath that we are using the | ||||
| 	// correct root path. | ||||
| 	logicalRootPath, err := procfs.ProcSelfFdReadlink(root) | ||||
| 	if err != nil { | ||||
| 		return nil, "", fmt.Errorf("get real root path: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	currentDir, err := dupFile(root) | ||||
| 	currentDir, err := fd.Dup(root) | ||||
| 	if err != nil { | ||||
| 		return nil, "", fmt.Errorf("clone root fd: %w", err) | ||||
| 	} | ||||
| @ -260,7 +271,7 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi | ||||
| 				return nil, "", fmt.Errorf("walking into root with part %q failed: %w", part, err) | ||||
| 			} | ||||
| 			// Jump to root. | ||||
| 			rootClone, err := dupFile(root) | ||||
| 			rootClone, err := fd.Dup(root) | ||||
| 			if err != nil { | ||||
| 				return nil, "", fmt.Errorf("clone root fd: %w", err) | ||||
| 			} | ||||
| @ -271,21 +282,21 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi | ||||
| 		} | ||||
| 
 | ||||
| 		// Try to open the next component. | ||||
| 		nextDir, err := openatFile(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) | ||||
| 		switch { | ||||
| 		case err == nil: | ||||
| 		nextDir, err := fd.Openat(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) | ||||
| 		switch err { | ||||
| 		case nil: | ||||
| 			st, err := nextDir.Stat() | ||||
| 			if err != nil { | ||||
| 				_ = nextDir.Close() | ||||
| 				return nil, "", fmt.Errorf("stat component %q: %w", part, err) | ||||
| 			} | ||||
| 
 | ||||
| 			switch st.Mode() & os.ModeType { | ||||
| 			switch st.Mode() & os.ModeType { //nolint:exhaustive // just a glorified if statement | ||||
| 			case os.ModeSymlink: | ||||
| 				// readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See | ||||
| 				// Linux commit 65cfc6722361 ("readlinkat(), fchownat() and | ||||
| 				// fstatat() with empty relative pathnames"). | ||||
| 				linkDest, err := readlinkatFile(nextDir, "") | ||||
| 				linkDest, err := fd.Readlinkat(nextDir, "") | ||||
| 				// We don't need the handle anymore. | ||||
| 				_ = nextDir.Close() | ||||
| 				if err != nil { | ||||
| @ -293,7 +304,7 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi | ||||
| 				} | ||||
| 
 | ||||
| 				linksWalked++ | ||||
| 				if linksWalked > maxSymlinkLimit { | ||||
| 				if linksWalked > consts.MaxSymlinkLimit { | ||||
| 					return nil, "", &os.PathError{Op: "securejoin.lookupInRoot", Path: logicalRootPath + "/" + unsafePath, Err: unix.ELOOP} | ||||
| 				} | ||||
| 
 | ||||
| @ -307,7 +318,7 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi | ||||
| 				// Absolute symlinks reset any work we've already done. | ||||
| 				if path.IsAbs(linkDest) { | ||||
| 					// Jump to root. | ||||
| 					rootClone, err := dupFile(root) | ||||
| 					rootClone, err := fd.Dup(root) | ||||
| 					if err != nil { | ||||
| 						return nil, "", fmt.Errorf("clone root fd: %w", err) | ||||
| 					} | ||||
| @ -335,12 +346,12 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi | ||||
| 				// rename or mount on the system. | ||||
| 				if part == ".." { | ||||
| 					// Make sure the root hasn't moved. | ||||
| 					if err := checkProcSelfFdPath(logicalRootPath, root); err != nil { | ||||
| 					if err := procfs.CheckProcSelfFdPath(logicalRootPath, root); err != nil { | ||||
| 						return nil, "", fmt.Errorf("root path moved during lookup: %w", err) | ||||
| 					} | ||||
| 					// Make sure the path is what we expect. | ||||
| 					fullPath := logicalRootPath + nextPath | ||||
| 					if err := checkProcSelfFdPath(fullPath, currentDir); err != nil { | ||||
| 					if err := procfs.CheckProcSelfFdPath(fullPath, currentDir); err != nil { | ||||
| 						return nil, "", fmt.Errorf("walking into %q had unexpected result: %w", part, err) | ||||
| 					} | ||||
| 				} | ||||
| @ -371,7 +382,7 @@ func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.Fi | ||||
| 	// context of openat2, a trailing slash and a trailing "/." are completely | ||||
| 	// equivalent. | ||||
| 	if strings.HasSuffix(unsafePath, "/") { | ||||
| 		nextDir, err := openatFile(currentDir, ".", unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) | ||||
| 		nextDir, err := fd.Openat(currentDir, ".", unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) | ||||
| 		if err != nil { | ||||
| 			if !partial { | ||||
| 				_ = currentDir.Close() | ||||
| @ -1,10 +1,15 @@ | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| //go:build linux | ||||
| 
 | ||||
| // Copyright (C) 2024 SUSE LLC. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
| // Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| // Copyright (C) 2024-2025 SUSE LLC | ||||
| // | ||||
| // This Source Code Form is subject to the terms of the Mozilla Public | ||||
| // License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
| 
 | ||||
| package securejoin | ||||
| package pathrs | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| @ -14,12 +19,13 @@ import ( | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"golang.org/x/sys/unix" | ||||
| 
 | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd" | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	errInvalidMode    = errors.New("invalid permission mode") | ||||
| 	errPossibleAttack = errors.New("possible attack detected") | ||||
| ) | ||||
| var errInvalidMode = errors.New("invalid permission mode") | ||||
| 
 | ||||
| // modePermExt is like os.ModePerm except that it also includes the set[ug]id | ||||
| // and sticky bits. | ||||
| @ -66,6 +72,8 @@ func toUnixMode(mode os.FileMode) (uint32, error) { | ||||
| // a brand new lookup of unsafePath (such as with [SecureJoin] or openat2) after | ||||
| // doing [MkdirAll]. If you intend to open the directory after creating it, you | ||||
| // should use MkdirAllHandle. | ||||
| // | ||||
| // [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin | ||||
| func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.File, Err error) { | ||||
| 	unixMode, err := toUnixMode(mode) | ||||
| 	if err != nil { | ||||
| @ -102,7 +110,7 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F | ||||
| 	// | ||||
| 	// This is mostly a quality-of-life check, because mkdir will simply fail | ||||
| 	// later if the attacker deletes the tree after this check. | ||||
| 	if err := isDeadInode(currentDir); err != nil { | ||||
| 	if err := fd.IsDeadInode(currentDir); err != nil { | ||||
| 		return nil, fmt.Errorf("finding existing subpath of %q: %w", unsafePath, err) | ||||
| 	} | ||||
| 
 | ||||
| @ -113,13 +121,13 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F | ||||
| 		return nil, fmt.Errorf("cannot create subdirectories in %q: %w", currentDir.Name(), unix.ENOTDIR) | ||||
| 	} else if err != nil { | ||||
| 		return nil, fmt.Errorf("re-opening handle to %q: %w", currentDir.Name(), err) | ||||
| 	} else { | ||||
| 	} else { //nolint:revive // indent-error-flow lint doesn't make sense here | ||||
| 		_ = currentDir.Close() | ||||
| 		currentDir = reopenDir | ||||
| 	} | ||||
| 
 | ||||
| 	remainingParts := strings.Split(remainingPath, string(filepath.Separator)) | ||||
| 	if slices_Contains(remainingParts, "..") { | ||||
| 	if gocompat.SlicesContains(remainingParts, "..") { | ||||
| 		// The path contained ".." components after the end of the "real" | ||||
| 		// components. We could try to safely resolve ".." here but that would | ||||
| 		// add a bunch of extra logic for something that it's not clear even | ||||
| @ -150,12 +158,12 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F | ||||
| 		if err := unix.Mkdirat(int(currentDir.Fd()), part, unixMode); err != nil && !errors.Is(err, unix.EEXIST) { | ||||
| 			err = &os.PathError{Op: "mkdirat", Path: currentDir.Name() + "/" + part, Err: err} | ||||
| 			// Make the error a bit nicer if the directory is dead. | ||||
| 			if deadErr := isDeadInode(currentDir); deadErr != nil { | ||||
| 			if deadErr := fd.IsDeadInode(currentDir); deadErr != nil { | ||||
| 				// TODO: Once we bump the minimum Go version to 1.20, we can use | ||||
| 				// multiple %w verbs for this wrapping. For now we need to use a | ||||
| 				// compatibility shim for older Go versions. | ||||
| 				//err = fmt.Errorf("%w (%w)", err, deadErr) | ||||
| 				err = wrapBaseError(err, deadErr) | ||||
| 				// err = fmt.Errorf("%w (%w)", err, deadErr) | ||||
| 				err = gocompat.WrapBaseError(err, deadErr) | ||||
| 			} | ||||
| 			return nil, err | ||||
| 		} | ||||
| @ -163,13 +171,13 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F | ||||
| 		// Get a handle to the next component. O_DIRECTORY means we don't need | ||||
| 		// to use O_PATH. | ||||
| 		var nextDir *os.File | ||||
| 		if hasOpenat2() { | ||||
| 			nextDir, err = openat2File(currentDir, part, &unix.OpenHow{ | ||||
| 		if linux.HasOpenat2() { | ||||
| 			nextDir, err = openat2(currentDir, part, &unix.OpenHow{ | ||||
| 				Flags:   unix.O_NOFOLLOW | unix.O_DIRECTORY | unix.O_CLOEXEC, | ||||
| 				Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_XDEV, | ||||
| 			}) | ||||
| 		} else { | ||||
| 			nextDir, err = openatFile(currentDir, part, unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) | ||||
| 			nextDir, err = fd.Openat(currentDir, part, unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| @ -220,12 +228,14 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F | ||||
| // If you plan to open the directory after you have created it or want to use | ||||
| // an open directory handle as the root, you should use [MkdirAllHandle] instead. | ||||
| // This function is a wrapper around [MkdirAllHandle]. | ||||
| // | ||||
| // [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin | ||||
| func MkdirAll(root, unsafePath string, mode os.FileMode) error { | ||||
| 	rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer rootDir.Close() | ||||
| 	defer rootDir.Close() //nolint:errcheck // close failures aren't critical here | ||||
| 
 | ||||
| 	f, err := MkdirAllHandle(rootDir, unsafePath, mode) | ||||
| 	if err != nil { | ||||
| @ -1,17 +1,22 @@ | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
| 
 | ||||
| //go:build linux | ||||
| 
 | ||||
| // Copyright (C) 2024 SUSE LLC. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
| // Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| // Copyright (C) 2024-2025 SUSE LLC | ||||
| // | ||||
| // This Source Code Form is subject to the terms of the Mozilla Public | ||||
| // License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
| 
 | ||||
| package securejoin | ||||
| package pathrs | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| 
 | ||||
| 	"golang.org/x/sys/unix" | ||||
| 
 | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs" | ||||
| ) | ||||
| 
 | ||||
| // OpenatInRoot is equivalent to [OpenInRoot], except that the root is provided | ||||
| @ -40,12 +45,14 @@ func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) { | ||||
| // disconnected TTY that could cause a DoS, or some other issue). In order to | ||||
| // use the returned handle, you can "upgrade" it to a proper handle using | ||||
| // [Reopen]. | ||||
| // | ||||
| // [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin | ||||
| func OpenInRoot(root, unsafePath string) (*os.File, error) { | ||||
| 	rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer rootDir.Close() | ||||
| 	defer rootDir.Close() //nolint:errcheck // close failures aren't critical here | ||||
| 	return OpenatInRoot(rootDir, unsafePath) | ||||
| } | ||||
| 
 | ||||
| @ -63,41 +70,5 @@ func OpenInRoot(root, unsafePath string) (*os.File, error) { | ||||
| // | ||||
| // [CVE-2019-19921]: https://github.com/advisories/GHSA-fh74-hm69-rqjw | ||||
| func Reopen(handle *os.File, flags int) (*os.File, error) { | ||||
| 	procRoot, err := getProcRoot() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	// We can't operate on /proc/thread-self/fd/$n directly when doing a | ||||
| 	// re-open, so we need to open /proc/thread-self/fd and then open a single | ||||
| 	// final component. | ||||
| 	procFdDir, closer, err := procThreadSelf(procRoot, "fd/") | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("get safe /proc/thread-self/fd handle: %w", err) | ||||
| 	} | ||||
| 	defer procFdDir.Close() | ||||
| 	defer closer() | ||||
| 
 | ||||
| 	// Try to detect if there is a mount on top of the magic-link we are about | ||||
| 	// to open. If we are using unsafeHostProcRoot(), this could change after | ||||
| 	// we check it (and there's nothing we can do about that) but for | ||||
| 	// privateProcRoot() this should be guaranteed to be safe (at least since | ||||
| 	// Linux 5.12[1], when anonymous mount namespaces were completely isolated | ||||
| 	// from external mounts including mount propagation events). | ||||
| 	// | ||||
| 	// [1]: Linux commit ee2e3f50629f ("mount: fix mounting of detached mounts | ||||
| 	// onto targets that reside on shared mounts"). | ||||
| 	fdStr := strconv.Itoa(int(handle.Fd())) | ||||
| 	if err := checkSymlinkOvermount(procRoot, procFdDir, fdStr); err != nil { | ||||
| 		return nil, fmt.Errorf("check safety of /proc/thread-self/fd/%s magiclink: %w", fdStr, err) | ||||
| 	} | ||||
| 
 | ||||
| 	flags |= unix.O_CLOEXEC | ||||
| 	// Rather than just wrapping openatFile, open-code it so we can copy | ||||
| 	// handle.Name(). | ||||
| 	reopenFd, err := unix.Openat(int(procFdDir.Fd()), fdStr, flags, 0) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("reopen fd %d: %w", handle.Fd(), err) | ||||
| 	} | ||||
| 	return os.NewFile(uintptr(reopenFd), handle.Name()), nil | ||||
| 	return procfs.ReopenFd(handle, flags) | ||||
| } | ||||
							
								
								
									
										101
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/openat2_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/openat2_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,101 @@ | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
|  | ||||
| //go:build linux | ||||
|  | ||||
| // Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| // Copyright (C) 2024-2025 SUSE LLC | ||||
| // | ||||
| // This Source Code Form is subject to the terms of the Mozilla Public | ||||
| // License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| package pathrs | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
|  | ||||
| 	"golang.org/x/sys/unix" | ||||
|  | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd" | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/procfs" | ||||
| ) | ||||
|  | ||||
| func openat2(dir fd.Fd, path string, how *unix.OpenHow) (*os.File, error) { | ||||
| 	file, err := fd.Openat2(dir, path, how) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	// If we are using RESOLVE_IN_ROOT, the name we generated may be wrong. | ||||
| 	if how.Resolve&unix.RESOLVE_IN_ROOT == unix.RESOLVE_IN_ROOT { | ||||
| 		if actualPath, err := procfs.ProcSelfFdReadlink(file); err == nil { | ||||
| 			// TODO: Ideally we would not need to dup the fd, but you cannot | ||||
| 			//       easily just swap an *os.File with one from the same fd | ||||
| 			//       (the GC will close the old one, and you cannot clear the | ||||
| 			//       finaliser easily because it is associated with an internal | ||||
| 			//       field of *os.File not *os.File itself). | ||||
| 			newFile, err := fd.DupWithName(file, actualPath) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 			file = newFile | ||||
| 		} | ||||
| 	} | ||||
| 	return file, nil | ||||
| } | ||||
|  | ||||
| func lookupOpenat2(root fd.Fd, unsafePath string, partial bool) (*os.File, string, error) { | ||||
| 	if !partial { | ||||
| 		file, err := openat2(root, unsafePath, &unix.OpenHow{ | ||||
| 			Flags:   unix.O_PATH | unix.O_CLOEXEC, | ||||
| 			Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS, | ||||
| 		}) | ||||
| 		return file, "", err | ||||
| 	} | ||||
| 	return partialLookupOpenat2(root, unsafePath) | ||||
| } | ||||
|  | ||||
| // partialLookupOpenat2 is an alternative implementation of | ||||
| // partialLookupInRoot, using openat2(RESOLVE_IN_ROOT) to more safely get a | ||||
| // handle to the deepest existing child of the requested path within the root. | ||||
| func partialLookupOpenat2(root fd.Fd, unsafePath string) (*os.File, string, error) { | ||||
| 	// TODO: Implement this as a git-bisect-like binary search. | ||||
|  | ||||
| 	unsafePath = filepath.ToSlash(unsafePath) // noop | ||||
| 	endIdx := len(unsafePath) | ||||
| 	var lastError error | ||||
| 	for endIdx > 0 { | ||||
| 		subpath := unsafePath[:endIdx] | ||||
|  | ||||
| 		handle, err := openat2(root, subpath, &unix.OpenHow{ | ||||
| 			Flags:   unix.O_PATH | unix.O_CLOEXEC, | ||||
| 			Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS, | ||||
| 		}) | ||||
| 		if err == nil { | ||||
| 			// Jump over the slash if we have a non-"" remainingPath. | ||||
| 			if endIdx < len(unsafePath) { | ||||
| 				endIdx++ | ||||
| 			} | ||||
| 			// We found a subpath! | ||||
| 			return handle, unsafePath[endIdx:], lastError | ||||
| 		} | ||||
| 		if errors.Is(err, unix.ENOENT) || errors.Is(err, unix.ENOTDIR) { | ||||
| 			// That path doesn't exist, let's try the next directory up. | ||||
| 			endIdx = strings.LastIndexByte(subpath, '/') | ||||
| 			lastError = err | ||||
| 			continue | ||||
| 		} | ||||
| 		return nil, "", fmt.Errorf("open subpath: %w", err) | ||||
| 	} | ||||
| 	// If we couldn't open anything, the whole subpath is missing. Return a | ||||
| 	// copy of the root fd so that the caller doesn't close this one by | ||||
| 	// accident. | ||||
| 	rootClone, err := fd.Dup(root) | ||||
| 	if err != nil { | ||||
| 		return nil, "", err | ||||
| 	} | ||||
| 	return rootClone, unsafePath, lastError | ||||
| } | ||||
							
								
								
									
										157
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,157 @@ | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
|  | ||||
| //go:build linux | ||||
|  | ||||
| // Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| // Copyright (C) 2024-2025 SUSE LLC | ||||
| // | ||||
| // This Source Code Form is subject to the terms of the Mozilla Public | ||||
| // License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| // Package procfs provides a safe API for operating on /proc on Linux. | ||||
| package procfs | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs" | ||||
| ) | ||||
|  | ||||
| // This package mostly just wraps internal/procfs APIs. This is necessary | ||||
| // because we are forced to export some things from internal/procfs in order to | ||||
| // avoid some dependency cycle issues, but we don't want users to see or use | ||||
| // them. | ||||
|  | ||||
| // ProcThreadSelfCloser is a callback that needs to be called when you are done | ||||
| // operating on an [os.File] fetched using [Handle.OpenThreadSelf]. | ||||
| // | ||||
| // [os.File]: https://pkg.go.dev/os#File | ||||
| type ProcThreadSelfCloser = procfs.ProcThreadSelfCloser | ||||
|  | ||||
| // Handle is a wrapper around an *os.File handle to "/proc", which can be used | ||||
| // to do further procfs-related operations in a safe way. | ||||
| type Handle struct { | ||||
| 	inner *procfs.Handle | ||||
| } | ||||
|  | ||||
| // Close close the resources associated with this [Handle]. Note that if this | ||||
| // [Handle] was created with [OpenProcRoot], on some kernels the underlying | ||||
| // procfs handle is cached and so this Close operation may be a no-op. However, | ||||
| // you should always call Close on [Handle]s once you are done with them. | ||||
| func (proc *Handle) Close() error { return proc.inner.Close() } | ||||
|  | ||||
| // OpenProcRoot tries to open a "safer" handle to "/proc" (i.e., one with the | ||||
| // "subset=pid" mount option applied, available from Linux 5.8). Unless you | ||||
| // plan to do many [Handle.OpenRoot] operations, users should prefer to use | ||||
| // this over [OpenUnsafeProcRoot] which is far more dangerous to keep open. | ||||
| // | ||||
| // If a safe handle cannot be opened, OpenProcRoot will fall back to opening a | ||||
| // regular "/proc" handle. | ||||
| // | ||||
| // Note that using [Handle.OpenRoot] will still work with handles returned by | ||||
| // this function. If a subpath cannot be operated on with a safe "/proc" | ||||
| // handle, then [OpenUnsafeProcRoot] will be called internally and a temporary | ||||
| // unsafe handle will be used. | ||||
| func OpenProcRoot() (*Handle, error) { | ||||
| 	proc, err := procfs.OpenProcRoot() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &Handle{inner: proc}, nil | ||||
| } | ||||
|  | ||||
| // OpenUnsafeProcRoot opens a handle to "/proc" without any overmounts or | ||||
| // masked paths. You must be extremely careful to make sure this handle is | ||||
| // never leaked to a container and that you program cannot be tricked into | ||||
| // writing to arbitrary paths within it. | ||||
| // | ||||
| // This is not necessary if you just wish to use [Handle.OpenRoot], as handles | ||||
| // returned by [OpenProcRoot] will fall back to using a *temporary* unsafe | ||||
| // handle in that case. You should only really use this if you need to do many | ||||
| // operations with [Handle.OpenRoot] and the performance overhead of making | ||||
| // many procfs handles is an issue. If you do use OpenUnsafeProcRoot, you | ||||
| // should make sure to close the handle as soon as possible to avoid | ||||
| // known-fd-number attacks. | ||||
| func OpenUnsafeProcRoot() (*Handle, error) { | ||||
| 	proc, err := procfs.OpenUnsafeProcRoot() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &Handle{inner: proc}, nil | ||||
| } | ||||
|  | ||||
| // OpenThreadSelf returns a handle to "/proc/thread-self/<subpath>" (or an | ||||
| // equivalent handle on older kernels where "/proc/thread-self" doesn't exist). | ||||
| // Once finished with the handle, you must call the returned closer function | ||||
| // ([runtime.UnlockOSThread]). You must not pass the returned *os.File to other | ||||
| // Go threads or use the handle after calling the closer. | ||||
| // | ||||
| // [runtime.UnlockOSThread]: https://pkg.go.dev/runtime#UnlockOSThread | ||||
| func (proc *Handle) OpenThreadSelf(subpath string) (*os.File, ProcThreadSelfCloser, error) { | ||||
| 	return proc.inner.OpenThreadSelf(subpath) | ||||
| } | ||||
|  | ||||
| // OpenSelf returns a handle to /proc/self/<subpath>. | ||||
| // | ||||
| // Note that in Go programs with non-homogenous threads, this may result in | ||||
| // spurious errors. If you are monkeying around with APIs that are | ||||
| // thread-specific, you probably want to use [Handle.OpenThreadSelf] instead | ||||
| // which will guarantee that the handle refers to the same thread as the caller | ||||
| // is executing on. | ||||
| func (proc *Handle) OpenSelf(subpath string) (*os.File, error) { | ||||
| 	return proc.inner.OpenSelf(subpath) | ||||
| } | ||||
|  | ||||
| // OpenRoot returns a handle to /proc/<subpath>. | ||||
| // | ||||
| // You should only use this when you need to operate on global procfs files | ||||
| // (such as sysctls in /proc/sys). Unlike [Handle.OpenThreadSelf], | ||||
| // [Handle.OpenSelf], and [Handle.OpenPid], the procfs handle used internally | ||||
| // for this operation will never use "subset=pid", which makes it a more juicy | ||||
| // target for [CVE-2024-21626]-style attacks (and doing something like opening | ||||
| // a directory with OpenRoot effectively leaks [OpenUnsafeProcRoot] as long as | ||||
| // the file descriptor is open). | ||||
| // | ||||
| // [CVE-2024-21626]: https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv | ||||
| func (proc *Handle) OpenRoot(subpath string) (*os.File, error) { | ||||
| 	return proc.inner.OpenRoot(subpath) | ||||
| } | ||||
|  | ||||
| // OpenPid returns a handle to /proc/$pid/<subpath> (pid can be a pid or tid). | ||||
| // This is mainly intended for usage when operating on other processes. | ||||
| // | ||||
| // You should not use this for the current thread, as special handling is | ||||
| // needed for /proc/thread-self (or /proc/self/task/<tid>) when dealing with | ||||
| // goroutine scheduling -- use [Handle.OpenThreadSelf] instead. | ||||
| // | ||||
| // To refer to the current thread-group, you should use prefer | ||||
| // [Handle.OpenSelf] to passing os.Getpid as the pid argument. | ||||
| func (proc *Handle) OpenPid(pid int, subpath string) (*os.File, error) { | ||||
| 	return proc.inner.OpenPid(pid, subpath) | ||||
| } | ||||
|  | ||||
| // ProcSelfFdReadlink gets the real path of the given file by looking at | ||||
| // /proc/self/fd/<fd> with [readlink]. It is effectively just shorthand for | ||||
| // something along the lines of: | ||||
| // | ||||
| //	proc, err := procfs.OpenProcRoot() | ||||
| //	if err != nil { | ||||
| //		return err | ||||
| //	} | ||||
| //	link, err := proc.OpenThreadSelf(fmt.Sprintf("fd/%d", f.Fd())) | ||||
| //	if err != nil { | ||||
| //		return err | ||||
| //	} | ||||
| //	defer link.Close() | ||||
| //	var buf [4096]byte | ||||
| //	n, err := unix.Readlinkat(int(link.Fd()), "", buf[:]) | ||||
| //	if err != nil { | ||||
| //		return err | ||||
| //	} | ||||
| //	pathname := buf[:n] | ||||
| // | ||||
| // [readlink]: https://pkg.go.dev/golang.org/x/sys/unix#Readlinkat | ||||
| func ProcSelfFdReadlink(f *os.File) (string, error) { | ||||
| 	return procfs.ProcSelfFdReadlink(f) | ||||
| } | ||||
							
								
								
									
										452
									
								
								vendor/github.com/cyphar/filepath-securejoin/procfs_linux.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										452
									
								
								vendor/github.com/cyphar/filepath-securejoin/procfs_linux.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,452 +0,0 @@ | ||||
| //go:build linux | ||||
|  | ||||
| // Copyright (C) 2024 SUSE LLC. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package securejoin | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"runtime" | ||||
| 	"strconv" | ||||
|  | ||||
| 	"golang.org/x/sys/unix" | ||||
| ) | ||||
|  | ||||
| func fstat(f *os.File) (unix.Stat_t, error) { | ||||
| 	var stat unix.Stat_t | ||||
| 	if err := unix.Fstat(int(f.Fd()), &stat); err != nil { | ||||
| 		return stat, &os.PathError{Op: "fstat", Path: f.Name(), Err: err} | ||||
| 	} | ||||
| 	return stat, nil | ||||
| } | ||||
|  | ||||
| func fstatfs(f *os.File) (unix.Statfs_t, error) { | ||||
| 	var statfs unix.Statfs_t | ||||
| 	if err := unix.Fstatfs(int(f.Fd()), &statfs); err != nil { | ||||
| 		return statfs, &os.PathError{Op: "fstatfs", Path: f.Name(), Err: err} | ||||
| 	} | ||||
| 	return statfs, nil | ||||
| } | ||||
|  | ||||
| // The kernel guarantees that the root inode of a procfs mount has an | ||||
| // f_type of PROC_SUPER_MAGIC and st_ino of PROC_ROOT_INO. | ||||
| const ( | ||||
| 	procSuperMagic = 0x9fa0 // PROC_SUPER_MAGIC | ||||
| 	procRootIno    = 1      // PROC_ROOT_INO | ||||
| ) | ||||
|  | ||||
| func verifyProcRoot(procRoot *os.File) error { | ||||
| 	if statfs, err := fstatfs(procRoot); err != nil { | ||||
| 		return err | ||||
| 	} else if statfs.Type != procSuperMagic { | ||||
| 		return fmt.Errorf("%w: incorrect procfs root filesystem type 0x%x", errUnsafeProcfs, statfs.Type) | ||||
| 	} | ||||
| 	if stat, err := fstat(procRoot); err != nil { | ||||
| 		return err | ||||
| 	} else if stat.Ino != procRootIno { | ||||
| 		return fmt.Errorf("%w: incorrect procfs root inode number %d", errUnsafeProcfs, stat.Ino) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| var hasNewMountApi = sync_OnceValue(func() bool { | ||||
| 	// All of the pieces of the new mount API we use (fsopen, fsconfig, | ||||
| 	// fsmount, open_tree) were added together in Linux 5.1[1,2], so we can | ||||
| 	// just check for one of the syscalls and the others should also be | ||||
| 	// available. | ||||
| 	// | ||||
| 	// Just try to use open_tree(2) to open a file without OPEN_TREE_CLONE. | ||||
| 	// This is equivalent to openat(2), but tells us if open_tree is | ||||
| 	// available (and thus all of the other basic new mount API syscalls). | ||||
| 	// open_tree(2) is most light-weight syscall to test here. | ||||
| 	// | ||||
| 	// [1]: merge commit 400913252d09 | ||||
| 	// [2]: <https://lore.kernel.org/lkml/153754740781.17872.7869536526927736855.stgit@warthog.procyon.org.uk/> | ||||
| 	fd, err := unix.OpenTree(-int(unix.EBADF), "/", unix.OPEN_TREE_CLOEXEC) | ||||
| 	if err != nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	_ = unix.Close(fd) | ||||
| 	return true | ||||
| }) | ||||
|  | ||||
| func fsopen(fsName string, flags int) (*os.File, error) { | ||||
| 	// Make sure we always set O_CLOEXEC. | ||||
| 	flags |= unix.FSOPEN_CLOEXEC | ||||
| 	fd, err := unix.Fsopen(fsName, flags) | ||||
| 	if err != nil { | ||||
| 		return nil, os.NewSyscallError("fsopen "+fsName, err) | ||||
| 	} | ||||
| 	return os.NewFile(uintptr(fd), "fscontext:"+fsName), nil | ||||
| } | ||||
|  | ||||
| func fsmount(ctx *os.File, flags, mountAttrs int) (*os.File, error) { | ||||
| 	// Make sure we always set O_CLOEXEC. | ||||
| 	flags |= unix.FSMOUNT_CLOEXEC | ||||
| 	fd, err := unix.Fsmount(int(ctx.Fd()), flags, mountAttrs) | ||||
| 	if err != nil { | ||||
| 		return nil, os.NewSyscallError("fsmount "+ctx.Name(), err) | ||||
| 	} | ||||
| 	return os.NewFile(uintptr(fd), "fsmount:"+ctx.Name()), nil | ||||
| } | ||||
|  | ||||
| func newPrivateProcMount() (*os.File, error) { | ||||
| 	procfsCtx, err := fsopen("proc", unix.FSOPEN_CLOEXEC) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer procfsCtx.Close() | ||||
|  | ||||
| 	// Try to configure hidepid=ptraceable,subset=pid if possible, but ignore errors. | ||||
| 	_ = unix.FsconfigSetString(int(procfsCtx.Fd()), "hidepid", "ptraceable") | ||||
| 	_ = unix.FsconfigSetString(int(procfsCtx.Fd()), "subset", "pid") | ||||
|  | ||||
| 	// Get an actual handle. | ||||
| 	if err := unix.FsconfigCreate(int(procfsCtx.Fd())); err != nil { | ||||
| 		return nil, os.NewSyscallError("fsconfig create procfs", err) | ||||
| 	} | ||||
| 	return fsmount(procfsCtx, unix.FSMOUNT_CLOEXEC, unix.MS_RDONLY|unix.MS_NODEV|unix.MS_NOEXEC|unix.MS_NOSUID) | ||||
| } | ||||
|  | ||||
| func openTree(dir *os.File, path string, flags uint) (*os.File, error) { | ||||
| 	dirFd := -int(unix.EBADF) | ||||
| 	dirName := "." | ||||
| 	if dir != nil { | ||||
| 		dirFd = int(dir.Fd()) | ||||
| 		dirName = dir.Name() | ||||
| 	} | ||||
| 	// Make sure we always set O_CLOEXEC. | ||||
| 	flags |= unix.OPEN_TREE_CLOEXEC | ||||
| 	fd, err := unix.OpenTree(dirFd, path, flags) | ||||
| 	if err != nil { | ||||
| 		return nil, &os.PathError{Op: "open_tree", Path: path, Err: err} | ||||
| 	} | ||||
| 	return os.NewFile(uintptr(fd), dirName+"/"+path), nil | ||||
| } | ||||
|  | ||||
| func clonePrivateProcMount() (_ *os.File, Err error) { | ||||
| 	// Try to make a clone without using AT_RECURSIVE if we can. If this works, | ||||
| 	// we can be sure there are no over-mounts and so if the root is valid then | ||||
| 	// we're golden. Otherwise, we have to deal with over-mounts. | ||||
| 	procfsHandle, err := openTree(nil, "/proc", unix.OPEN_TREE_CLONE) | ||||
| 	if err != nil || hookForcePrivateProcRootOpenTreeAtRecursive(procfsHandle) { | ||||
| 		procfsHandle, err = openTree(nil, "/proc", unix.OPEN_TREE_CLONE|unix.AT_RECURSIVE) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("creating a detached procfs clone: %w", err) | ||||
| 	} | ||||
| 	defer func() { | ||||
| 		if Err != nil { | ||||
| 			_ = procfsHandle.Close() | ||||
| 		} | ||||
| 	}() | ||||
| 	if err := verifyProcRoot(procfsHandle); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return procfsHandle, nil | ||||
| } | ||||
|  | ||||
| func privateProcRoot() (*os.File, error) { | ||||
| 	if !hasNewMountApi() || hookForceGetProcRootUnsafe() { | ||||
| 		return nil, fmt.Errorf("new mount api: %w", unix.ENOTSUP) | ||||
| 	} | ||||
| 	// Try to create a new procfs mount from scratch if we can. This ensures we | ||||
| 	// can get a procfs mount even if /proc is fake (for whatever reason). | ||||
| 	procRoot, err := newPrivateProcMount() | ||||
| 	if err != nil || hookForcePrivateProcRootOpenTree(procRoot) { | ||||
| 		// Try to clone /proc then... | ||||
| 		procRoot, err = clonePrivateProcMount() | ||||
| 	} | ||||
| 	return procRoot, err | ||||
| } | ||||
|  | ||||
| func unsafeHostProcRoot() (_ *os.File, Err error) { | ||||
| 	procRoot, err := os.OpenFile("/proc", unix.O_PATH|unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer func() { | ||||
| 		if Err != nil { | ||||
| 			_ = procRoot.Close() | ||||
| 		} | ||||
| 	}() | ||||
| 	if err := verifyProcRoot(procRoot); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return procRoot, nil | ||||
| } | ||||
|  | ||||
| func doGetProcRoot() (*os.File, error) { | ||||
| 	procRoot, err := privateProcRoot() | ||||
| 	if err != nil { | ||||
| 		// Fall back to using a /proc handle if making a private mount failed. | ||||
| 		// If we have openat2, at least we can avoid some kinds of over-mount | ||||
| 		// attacks, but without openat2 there's not much we can do. | ||||
| 		procRoot, err = unsafeHostProcRoot() | ||||
| 	} | ||||
| 	return procRoot, err | ||||
| } | ||||
|  | ||||
| var getProcRoot = sync_OnceValues(func() (*os.File, error) { | ||||
| 	return doGetProcRoot() | ||||
| }) | ||||
|  | ||||
| var hasProcThreadSelf = sync_OnceValue(func() bool { | ||||
| 	return unix.Access("/proc/thread-self/", unix.F_OK) == nil | ||||
| }) | ||||
|  | ||||
| var errUnsafeProcfs = errors.New("unsafe procfs detected") | ||||
|  | ||||
| type procThreadSelfCloser func() | ||||
|  | ||||
| // procThreadSelf returns a handle to /proc/thread-self/<subpath> (or an | ||||
| // equivalent handle on older kernels where /proc/thread-self doesn't exist). | ||||
| // Once finished with the handle, you must call the returned closer function | ||||
| // (runtime.UnlockOSThread). You must not pass the returned *os.File to other | ||||
| // Go threads or use the handle after calling the closer. | ||||
| // | ||||
| // This is similar to ProcThreadSelf from runc, but with extra hardening | ||||
| // applied and using *os.File. | ||||
| func procThreadSelf(procRoot *os.File, subpath string) (_ *os.File, _ procThreadSelfCloser, Err error) { | ||||
| 	// We need to lock our thread until the caller is done with the handle | ||||
| 	// because between getting the handle and using it we could get interrupted | ||||
| 	// by the Go runtime and hit the case where the underlying thread is | ||||
| 	// swapped out and the original thread is killed, resulting in | ||||
| 	// pull-your-hair-out-hard-to-debug issues in the caller. | ||||
| 	runtime.LockOSThread() | ||||
| 	defer func() { | ||||
| 		if Err != nil { | ||||
| 			runtime.UnlockOSThread() | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	// Figure out what prefix we want to use. | ||||
| 	threadSelf := "thread-self/" | ||||
| 	if !hasProcThreadSelf() || hookForceProcSelfTask() { | ||||
| 		/// Pre-3.17 kernels don't have /proc/thread-self, so do it manually. | ||||
| 		threadSelf = "self/task/" + strconv.Itoa(unix.Gettid()) + "/" | ||||
| 		if _, err := fstatatFile(procRoot, threadSelf, unix.AT_SYMLINK_NOFOLLOW); err != nil || hookForceProcSelf() { | ||||
| 			// In this case, we running in a pid namespace that doesn't match | ||||
| 			// the /proc mount we have. This can happen inside runc. | ||||
| 			// | ||||
| 			// Unfortunately, there is no nice way to get the correct TID to | ||||
| 			// use here because of the age of the kernel, so we have to just | ||||
| 			// use /proc/self and hope that it works. | ||||
| 			threadSelf = "self/" | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Grab the handle. | ||||
| 	var ( | ||||
| 		handle *os.File | ||||
| 		err    error | ||||
| 	) | ||||
| 	if hasOpenat2() { | ||||
| 		// We prefer being able to use RESOLVE_NO_XDEV if we can, to be | ||||
| 		// absolutely sure we are operating on a clean /proc handle that | ||||
| 		// doesn't have any cheeky overmounts that could trick us (including | ||||
| 		// symlink mounts on top of /proc/thread-self). RESOLVE_BENEATH isn't | ||||
| 		// strictly needed, but just use it since we have it. | ||||
| 		// | ||||
| 		// NOTE: /proc/self is technically a magic-link (the contents of the | ||||
| 		//       symlink are generated dynamically), but it doesn't use | ||||
| 		//       nd_jump_link() so RESOLVE_NO_MAGICLINKS allows it. | ||||
| 		// | ||||
| 		// NOTE: We MUST NOT use RESOLVE_IN_ROOT here, as openat2File uses | ||||
| 		//       procSelfFdReadlink to clean up the returned f.Name() if we use | ||||
| 		//       RESOLVE_IN_ROOT (which would lead to an infinite recursion). | ||||
| 		handle, err = openat2File(procRoot, threadSelf+subpath, &unix.OpenHow{ | ||||
| 			Flags:   unix.O_PATH | unix.O_NOFOLLOW | unix.O_CLOEXEC, | ||||
| 			Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_XDEV | unix.RESOLVE_NO_MAGICLINKS, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			// TODO: Once we bump the minimum Go version to 1.20, we can use | ||||
| 			// multiple %w verbs for this wrapping. For now we need to use a | ||||
| 			// compatibility shim for older Go versions. | ||||
| 			//err = fmt.Errorf("%w: %w", errUnsafeProcfs, err) | ||||
| 			return nil, nil, wrapBaseError(err, errUnsafeProcfs) | ||||
| 		} | ||||
| 	} else { | ||||
| 		handle, err = openatFile(procRoot, threadSelf+subpath, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) | ||||
| 		if err != nil { | ||||
| 			// TODO: Once we bump the minimum Go version to 1.20, we can use | ||||
| 			// multiple %w verbs for this wrapping. For now we need to use a | ||||
| 			// compatibility shim for older Go versions. | ||||
| 			//err = fmt.Errorf("%w: %w", errUnsafeProcfs, err) | ||||
| 			return nil, nil, wrapBaseError(err, errUnsafeProcfs) | ||||
| 		} | ||||
| 		defer func() { | ||||
| 			if Err != nil { | ||||
| 				_ = handle.Close() | ||||
| 			} | ||||
| 		}() | ||||
| 		// We can't detect bind-mounts of different parts of procfs on top of | ||||
| 		// /proc (a-la RESOLVE_NO_XDEV), but we can at least be sure that we | ||||
| 		// aren't on the wrong filesystem here. | ||||
| 		if statfs, err := fstatfs(handle); err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} else if statfs.Type != procSuperMagic { | ||||
| 			return nil, nil, fmt.Errorf("%w: incorrect /proc/self/fd filesystem type 0x%x", errUnsafeProcfs, statfs.Type) | ||||
| 		} | ||||
| 	} | ||||
| 	return handle, runtime.UnlockOSThread, nil | ||||
| } | ||||
|  | ||||
| // STATX_MNT_ID_UNIQUE is provided in golang.org/x/sys@v0.20.0, but in order to | ||||
| // avoid bumping the requirement for a single constant we can just define it | ||||
| // ourselves. | ||||
| const STATX_MNT_ID_UNIQUE = 0x4000 | ||||
|  | ||||
| var hasStatxMountId = sync_OnceValue(func() bool { | ||||
| 	var ( | ||||
| 		stx unix.Statx_t | ||||
| 		// We don't care which mount ID we get. The kernel will give us the | ||||
| 		// unique one if it is supported. | ||||
| 		wantStxMask uint32 = STATX_MNT_ID_UNIQUE | unix.STATX_MNT_ID | ||||
| 	) | ||||
| 	err := unix.Statx(-int(unix.EBADF), "/", 0, int(wantStxMask), &stx) | ||||
| 	return err == nil && stx.Mask&wantStxMask != 0 | ||||
| }) | ||||
|  | ||||
| func getMountId(dir *os.File, path string) (uint64, error) { | ||||
| 	// If we don't have statx(STATX_MNT_ID*) support, we can't do anything. | ||||
| 	if !hasStatxMountId() { | ||||
| 		return 0, nil | ||||
| 	} | ||||
|  | ||||
| 	var ( | ||||
| 		stx unix.Statx_t | ||||
| 		// We don't care which mount ID we get. The kernel will give us the | ||||
| 		// unique one if it is supported. | ||||
| 		wantStxMask uint32 = STATX_MNT_ID_UNIQUE | unix.STATX_MNT_ID | ||||
| 	) | ||||
|  | ||||
| 	err := unix.Statx(int(dir.Fd()), path, unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW, int(wantStxMask), &stx) | ||||
| 	if stx.Mask&wantStxMask == 0 { | ||||
| 		// It's not a kernel limitation, for some reason we couldn't get a | ||||
| 		// mount ID. Assume it's some kind of attack. | ||||
| 		err = fmt.Errorf("%w: could not get mount id", errUnsafeProcfs) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return 0, &os.PathError{Op: "statx(STATX_MNT_ID_...)", Path: dir.Name() + "/" + path, Err: err} | ||||
| 	} | ||||
| 	return stx.Mnt_id, nil | ||||
| } | ||||
|  | ||||
| func checkSymlinkOvermount(procRoot *os.File, dir *os.File, path string) error { | ||||
| 	// Get the mntId of our procfs handle. | ||||
| 	expectedMountId, err := getMountId(procRoot, "") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	// Get the mntId of the target magic-link. | ||||
| 	gotMountId, err := getMountId(dir, path) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	// As long as the directory mount is alive, even with wrapping mount IDs, | ||||
| 	// we would expect to see a different mount ID here. (Of course, if we're | ||||
| 	// using unsafeHostProcRoot() then an attaker could change this after we | ||||
| 	// did this check.) | ||||
| 	if expectedMountId != gotMountId { | ||||
| 		return fmt.Errorf("%w: symlink %s/%s has an overmount obscuring the real link (mount ids do not match %d != %d)", errUnsafeProcfs, dir.Name(), path, expectedMountId, gotMountId) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func doRawProcSelfFdReadlink(procRoot *os.File, fd int) (string, error) { | ||||
| 	fdPath := fmt.Sprintf("fd/%d", fd) | ||||
| 	procFdLink, closer, err := procThreadSelf(procRoot, fdPath) | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("get safe /proc/thread-self/%s handle: %w", fdPath, err) | ||||
| 	} | ||||
| 	defer procFdLink.Close() | ||||
| 	defer closer() | ||||
|  | ||||
| 	// Try to detect if there is a mount on top of the magic-link. Since we use the handle directly | ||||
| 	// provide to the closure. If the closure uses the handle directly, this | ||||
| 	// should be safe in general (a mount on top of the path afterwards would | ||||
| 	// not affect the handle itself) and will definitely be safe if we are | ||||
| 	// using privateProcRoot() (at least since Linux 5.12[1], when anonymous | ||||
| 	// mount namespaces were completely isolated from external mounts including | ||||
| 	// mount propagation events). | ||||
| 	// | ||||
| 	// [1]: Linux commit ee2e3f50629f ("mount: fix mounting of detached mounts | ||||
| 	// onto targets that reside on shared mounts"). | ||||
| 	if err := checkSymlinkOvermount(procRoot, procFdLink, ""); err != nil { | ||||
| 		return "", fmt.Errorf("check safety of /proc/thread-self/fd/%d magiclink: %w", fd, err) | ||||
| 	} | ||||
|  | ||||
| 	// readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See Linux commit | ||||
| 	// 65cfc6722361 ("readlinkat(), fchownat() and fstatat() with empty | ||||
| 	// relative pathnames"). | ||||
| 	return readlinkatFile(procFdLink, "") | ||||
| } | ||||
|  | ||||
| func rawProcSelfFdReadlink(fd int) (string, error) { | ||||
| 	procRoot, err := getProcRoot() | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return doRawProcSelfFdReadlink(procRoot, fd) | ||||
| } | ||||
|  | ||||
| func procSelfFdReadlink(f *os.File) (string, error) { | ||||
| 	return rawProcSelfFdReadlink(int(f.Fd())) | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	errPossibleBreakout = errors.New("possible breakout detected") | ||||
| 	errInvalidDirectory = errors.New("wandered into deleted directory") | ||||
| 	errDeletedInode     = errors.New("cannot verify path of deleted inode") | ||||
| ) | ||||
|  | ||||
| func isDeadInode(file *os.File) error { | ||||
| 	// If the nlink of a file drops to 0, there is an attacker deleting | ||||
| 	// directories during our walk, which could result in weird /proc values. | ||||
| 	// It's better to error out in this case. | ||||
| 	stat, err := fstat(file) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("check for dead inode: %w", err) | ||||
| 	} | ||||
| 	if stat.Nlink == 0 { | ||||
| 		err := errDeletedInode | ||||
| 		if stat.Mode&unix.S_IFMT == unix.S_IFDIR { | ||||
| 			err = errInvalidDirectory | ||||
| 		} | ||||
| 		return fmt.Errorf("%w %q", err, file.Name()) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func checkProcSelfFdPath(path string, file *os.File) error { | ||||
| 	if err := isDeadInode(file); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	actualPath, err := procSelfFdReadlink(file) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("get path of handle: %w", err) | ||||
| 	} | ||||
| 	if actualPath != path { | ||||
| 		return fmt.Errorf("%w: handle path %q doesn't match expected path %q", errPossibleBreakout, actualPath, path) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Test hooks used in the procfs tests to verify that the fallback logic works. | ||||
| // See testing_mocks_linux_test.go and procfs_linux_test.go for more details. | ||||
| var ( | ||||
| 	hookForcePrivateProcRootOpenTree            = hookDummyFile | ||||
| 	hookForcePrivateProcRootOpenTreeAtRecursive = hookDummyFile | ||||
| 	hookForceGetProcRootUnsafe                  = hookDummy | ||||
|  | ||||
| 	hookForceProcSelfTask = hookDummy | ||||
| 	hookForceProcSelf     = hookDummy | ||||
| ) | ||||
|  | ||||
| func hookDummy() bool               { return false } | ||||
| func hookDummyFile(_ *os.File) bool { return false } | ||||
							
								
								
									
										2
									
								
								vendor/github.com/cyphar/filepath-securejoin/vfs.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/cyphar/filepath-securejoin/vfs.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,3 +1,5 @@ | ||||
| // SPDX-License-Identifier: BSD-3-Clause | ||||
|  | ||||
| // Copyright (C) 2017-2024 SUSE LLC. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
							
								
								
									
										24
									
								
								vendor/github.com/docker/cli/cli/cobra.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								vendor/github.com/docker/cli/cli/cobra.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -168,34 +168,30 @@ func (tcmd *TopLevelCommand) Initialize(ops ...command.CLIOption) error { | ||||
| } | ||||
|  | ||||
| // VisitAll will traverse all commands from the root. | ||||
| // This is different from the VisitAll of cobra.Command where only parents | ||||
| // are checked. | ||||
| // | ||||
| // Deprecated: this utility was only used internally and will be removed in the next release. | ||||
| func VisitAll(root *cobra.Command, fn func(*cobra.Command)) { | ||||
| 	visitAll(root, fn) | ||||
| } | ||||
|  | ||||
| func visitAll(root *cobra.Command, fn func(*cobra.Command)) { | ||||
| 	for _, cmd := range root.Commands() { | ||||
| 		VisitAll(cmd, fn) | ||||
| 		visitAll(cmd, fn) | ||||
| 	} | ||||
| 	fn(root) | ||||
| } | ||||
|  | ||||
| // DisableFlagsInUseLine sets the DisableFlagsInUseLine flag on all | ||||
| // commands within the tree rooted at cmd. | ||||
| // | ||||
| // Deprecated: this utility was only used internally and will be removed in the next release. | ||||
| func DisableFlagsInUseLine(cmd *cobra.Command) { | ||||
| 	VisitAll(cmd, func(ccmd *cobra.Command) { | ||||
| 	visitAll(cmd, func(ccmd *cobra.Command) { | ||||
| 		// do not add a `[flags]` to the end of the usage line. | ||||
| 		ccmd.DisableFlagsInUseLine = true | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // HasCompletionArg returns true if a cobra completion arg request is found. | ||||
| func HasCompletionArg(args []string) bool { | ||||
| 	for _, arg := range args { | ||||
| 		if arg == cobra.ShellCompRequestCmd || arg == cobra.ShellCompNoDescRequestCmd { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| var helpCommand = &cobra.Command{ | ||||
| 	Use:               "help [command]", | ||||
| 	Short:             "Help about the command", | ||||
|  | ||||
							
								
								
									
										62
									
								
								vendor/github.com/docker/cli/cli/command/cli.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										62
									
								
								vendor/github.com/docker/cli/cli/command/cli.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -282,6 +282,17 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption) | ||||
| 	} | ||||
| 	filterResourceAttributesEnvvar() | ||||
|  | ||||
| 	// early return if GODEBUG is already set or the docker context is | ||||
| 	// the default context, i.e. is a virtual context where we won't override | ||||
| 	// any GODEBUG values. | ||||
| 	if v := os.Getenv("GODEBUG"); cli.currentContext == DefaultContextName || v != "" { | ||||
| 		return nil | ||||
| 	} | ||||
| 	meta, err := cli.contextStore.GetMetadata(cli.currentContext) | ||||
| 	if err == nil { | ||||
| 		setGoDebug(meta) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @ -475,6 +486,57 @@ func (cli *DockerCli) getDockerEndPoint() (ep docker.Endpoint, err error) { | ||||
| 	return resolveDockerEndpoint(cli.contextStore, cn) | ||||
| } | ||||
|  | ||||
| // setGoDebug is an escape hatch that sets the GODEBUG environment | ||||
| // variable value using docker context metadata. | ||||
| // | ||||
| //	{ | ||||
| //	  "Name": "my-context", | ||||
| //	  "Metadata": { "GODEBUG": "x509negativeserial=1" } | ||||
| //	} | ||||
| // | ||||
| // WARNING: Setting x509negativeserial=1 allows Go's x509 library to accept | ||||
| // X.509 certificates with negative serial numbers. | ||||
| // This behavior is deprecated and non-compliant with current security | ||||
| // standards (RFC 5280). Accepting negative serial numbers can introduce | ||||
| // serious security vulnerabilities, including the risk of certificate | ||||
| // collision or bypass attacks. | ||||
| // This option should only be used for legacy compatibility and never in | ||||
| // production environments. | ||||
| // Use at your own risk. | ||||
| func setGoDebug(meta store.Metadata) { | ||||
| 	fieldName := "GODEBUG" | ||||
| 	godebugEnv := os.Getenv(fieldName) | ||||
| 	// early return if GODEBUG is already set. We don't want to override what | ||||
| 	// the user already sets. | ||||
| 	if godebugEnv != "" { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var cfg any | ||||
| 	var ok bool | ||||
| 	switch m := meta.Metadata.(type) { | ||||
| 	case DockerContext: | ||||
| 		cfg, ok = m.AdditionalFields[fieldName] | ||||
| 		if !ok { | ||||
| 			return | ||||
| 		} | ||||
| 	case map[string]any: | ||||
| 		cfg, ok = m[fieldName] | ||||
| 		if !ok { | ||||
| 			return | ||||
| 		} | ||||
| 	default: | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	v, ok := cfg.(string) | ||||
| 	if !ok { | ||||
| 		return | ||||
| 	} | ||||
| 	// set the GODEBUG environment variable with whatever was in the context | ||||
| 	_ = os.Setenv(fieldName, v) | ||||
| } | ||||
|  | ||||
| func (cli *DockerCli) initialize() error { | ||||
| 	cli.init.Do(func() { | ||||
| 		cli.dockerEndpoint, cli.initErr = cli.getDockerEndPoint() | ||||
|  | ||||
							
								
								
									
										62
									
								
								vendor/github.com/docker/cli/cli/command/registry.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										62
									
								
								vendor/github.com/docker/cli/cli/command/registry.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -31,11 +31,13 @@ const ( | ||||
| // authConfigKey is the key used to store credentials for Docker Hub. It is | ||||
| // a copy of [registry.IndexServer]. | ||||
| // | ||||
| // [registry.IndexServer]: https://pkg.go.dev/github.com/docker/docker/registry#IndexServer | ||||
| // [registry.IndexServer]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/registry#IndexServer | ||||
| const authConfigKey = "https://index.docker.io/v1/" | ||||
|  | ||||
| // RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info | ||||
| // for the given command to prompt the user for username and password. | ||||
| // | ||||
| // Deprecated: this function is no longer used and will be removed in the next release. | ||||
| func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) registrytypes.RequestAuthConfig { | ||||
| 	configKey := getAuthConfigKey(index.Name) | ||||
| 	isDefaultRegistry := configKey == authConfigKey || index.Official | ||||
| @ -66,6 +68,8 @@ func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInf | ||||
| // | ||||
| // It is similar to [registry.ResolveAuthConfig], but uses the credentials- | ||||
| // store, instead of looking up credentials from a map. | ||||
| // | ||||
| // [registry.ResolveAuthConfig]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/registry#ResolveAuthConfig | ||||
| func ResolveAuthConfig(cfg *configfile.ConfigFile, index *registrytypes.IndexInfo) registrytypes.AuthConfig { | ||||
| 	configKey := index.Name | ||||
| 	if index.Official { | ||||
| @ -97,23 +101,6 @@ func GetDefaultAuthConfig(cfg *configfile.ConfigFile, checkCredStore bool, serve | ||||
| 	return registrytypes.AuthConfig(authconfig), nil | ||||
| } | ||||
|  | ||||
| // ConfigureAuth handles prompting of user's username and password if needed. | ||||
| // | ||||
| // Deprecated: use [PromptUserForCredentials] instead. | ||||
| func ConfigureAuth(ctx context.Context, cli Cli, flUser, flPassword string, authConfig *registrytypes.AuthConfig, _ bool) error { | ||||
| 	defaultUsername := authConfig.Username | ||||
| 	serverAddress := authConfig.ServerAddress | ||||
|  | ||||
| 	newAuthConfig, err := PromptUserForCredentials(ctx, cli, flUser, flPassword, defaultUsername, serverAddress) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	authConfig.Username = newAuthConfig.Username | ||||
| 	authConfig.Password = newAuthConfig.Password | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // PromptUserForCredentials handles the CLI prompt for the user to input | ||||
| // credentials. | ||||
| // If argUser is not empty, then the user is only prompted for their password. | ||||
| @ -209,47 +196,38 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete | ||||
| // image. The auth configuration is serialized as a base64url encoded RFC4648, | ||||
| // section 5) JSON string for sending through the X-Registry-Auth header. | ||||
| // RetrieveAuthTokenFromImage retrieves an encoded auth token given a | ||||
| // complete image reference. The auth configuration is serialized as a | ||||
| // base64url encoded ([RFC 4648, Section 5]) JSON string for sending through | ||||
| // the "X-Registry-Auth" header. | ||||
| // | ||||
| // For details on base64url encoding, see: | ||||
| // - RFC4648, section 5:   https://tools.ietf.org/html/rfc4648#section-5 | ||||
| // [RFC 4648, Section 5]: https://tools.ietf.org/html/rfc4648#section-5 | ||||
| func RetrieveAuthTokenFromImage(cfg *configfile.ConfigFile, image string) (string, error) { | ||||
| 	// Retrieve encoded auth token from the image reference | ||||
| 	authConfig, err := resolveAuthConfigFromImage(cfg, image) | ||||
| 	registryRef, err := reference.ParseNormalizedNamed(image) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig) | ||||
| 	configKey := getAuthConfigKey(reference.Domain(registryRef)) | ||||
| 	authConfig, err := cfg.GetAuthConfig(configKey) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	encodedAuth, err := registrytypes.EncodeAuthConfig(registrytypes.AuthConfig(authConfig)) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return encodedAuth, nil | ||||
| } | ||||
|  | ||||
| // resolveAuthConfigFromImage retrieves that AuthConfig using the image string | ||||
| func resolveAuthConfigFromImage(cfg *configfile.ConfigFile, image string) (registrytypes.AuthConfig, error) { | ||||
| 	registryRef, err := reference.ParseNormalizedNamed(image) | ||||
| 	if err != nil { | ||||
| 		return registrytypes.AuthConfig{}, err | ||||
| 	} | ||||
| 	configKey := getAuthConfigKey(reference.Domain(registryRef)) | ||||
| 	a, err := cfg.GetAuthConfig(configKey) | ||||
| 	if err != nil { | ||||
| 		return registrytypes.AuthConfig{}, err | ||||
| 	} | ||||
| 	return registrytypes.AuthConfig(a), nil | ||||
| } | ||||
|  | ||||
| // getAuthConfigKey special-cases using the full index address of the official | ||||
| // index as the AuthConfig key, and uses the (host)name[:port] for private indexes. | ||||
| // | ||||
| // It is similar to [registry.GetAuthConfigKey], but does not require on | ||||
| // [registrytypes.IndexInfo] as intermediate. | ||||
| // | ||||
| // [registry.GetAuthConfigKey]: https://pkg.go.dev/github.com/docker/docker/registry#GetAuthConfigKey | ||||
| // [registrytypes.IndexInfo]:https://pkg.go.dev/github.com/docker/docker/api/types/registry#IndexInfo | ||||
| // [registry.GetAuthConfigKey]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/registry#GetAuthConfigKey | ||||
| // [registrytypes.IndexInfo]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/api/types/registry#IndexInfo | ||||
| func getAuthConfigKey(domainName string) string { | ||||
| 	if domainName == "docker.io" || domainName == "index.docker.io" { | ||||
| 		return authConfigKey | ||||
|  | ||||
							
								
								
									
										12
									
								
								vendor/github.com/docker/cli/cli/command/stack/formatter/formatter.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								vendor/github.com/docker/cli/cli/command/stack/formatter/formatter.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -8,21 +8,31 @@ import ( | ||||
|  | ||||
| const ( | ||||
| 	// SwarmStackTableFormat is the default Swarm stack format | ||||
| 	// | ||||
| 	// Deprecated: this type was for internal use and will be removed in the next release. | ||||
| 	SwarmStackTableFormat formatter.Format = "table {{.Name}}\t{{.Services}}" | ||||
|  | ||||
| 	stackServicesHeader = "SERVICES" | ||||
|  | ||||
| 	// TableFormatKey is an alias for formatter.TableFormatKey | ||||
| 	// | ||||
| 	// Deprecated: this type was for internal use and will be removed in the next release. | ||||
| 	TableFormatKey = formatter.TableFormatKey | ||||
| ) | ||||
|  | ||||
| // Context is an alias for formatter.Context | ||||
| // | ||||
| // Deprecated: this type was for internal use and will be removed in the next release. | ||||
| type Context = formatter.Context | ||||
|  | ||||
| // Format is an alias for formatter.Format | ||||
| // | ||||
| // Deprecated: this type was for internal use and will be removed in the next release. | ||||
| type Format = formatter.Format | ||||
|  | ||||
| // Stack contains deployed stack information. | ||||
| // | ||||
| // Deprecated: this type was for internal use and will be removed in the next release. | ||||
| type Stack struct { | ||||
| 	// Name is the name of the stack | ||||
| 	Name string | ||||
| @ -31,6 +41,8 @@ type Stack struct { | ||||
| } | ||||
|  | ||||
| // StackWrite writes formatted stacks using the Context | ||||
| // | ||||
| // Deprecated: this function was for internal use and will be removed in the next release. | ||||
| func StackWrite(ctx formatter.Context, stacks []*Stack) error { | ||||
| 	render := func(format func(subContext formatter.SubContext) error) error { | ||||
| 		for _, stack := range stacks { | ||||
|  | ||||
							
								
								
									
										15
									
								
								vendor/github.com/docker/cli/cli/command/trust.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								vendor/github.com/docker/cli/cli/command/trust.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,15 +0,0 @@ | ||||
| package command | ||||
|  | ||||
| import ( | ||||
| 	"github.com/spf13/pflag" | ||||
| ) | ||||
|  | ||||
| // AddTrustVerificationFlags adds content trust flags to the provided flagset | ||||
| func AddTrustVerificationFlags(fs *pflag.FlagSet, v *bool, trusted bool) { | ||||
| 	fs.BoolVar(v, "disable-content-trust", !trusted, "Skip image verification") | ||||
| } | ||||
|  | ||||
| // AddTrustSigningFlags adds "signing" flags to the provided flagset | ||||
| func AddTrustSigningFlags(fs *pflag.FlagSet, v *bool, trusted bool) { | ||||
| 	fs.BoolVar(v, "disable-content-trust", !trusted, "Skip image signing") | ||||
| } | ||||
							
								
								
									
										28
									
								
								vendor/github.com/docker/cli/cli/command/utils.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										28
									
								
								vendor/github.com/docker/cli/cli/command/utils.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -14,30 +14,20 @@ import ( | ||||
| 	"github.com/docker/cli/cli/streams" | ||||
| 	"github.com/docker/cli/internal/prompt" | ||||
| 	"github.com/docker/docker/api/types/filters" | ||||
| 	"github.com/moby/sys/atomicwriter" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"github.com/spf13/pflag" | ||||
| ) | ||||
|  | ||||
| // CopyToFile writes the content of the reader to the specified file | ||||
| // ErrPromptTerminated is returned if the user terminated the prompt. | ||||
| // | ||||
| // Deprecated: use [atomicwriter.New]. | ||||
| func CopyToFile(outfile string, r io.Reader) error { | ||||
| 	writer, err := atomicwriter.New(outfile, 0o600) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer writer.Close() | ||||
| 	_, err = io.Copy(writer, r) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // Deprecated: this error is for internal use and will be removed in the next release. | ||||
| const ErrPromptTerminated = prompt.ErrTerminated | ||||
|  | ||||
| // DisableInputEcho disables input echo on the provided streams.In. | ||||
| // This is useful when the user provides sensitive information like passwords. | ||||
| // The function returns a restore function that should be called to restore the | ||||
| // terminal state. | ||||
| // | ||||
| // Deprecated: this function is for internal use and will be removed in the next release. | ||||
| func DisableInputEcho(ins *streams.In) (restore func() error, err error) { | ||||
| 	return prompt.DisableInputEcho(ins) | ||||
| } | ||||
| @ -49,6 +39,8 @@ func DisableInputEcho(ins *streams.In) (restore func() error, err error) { | ||||
| // When the prompt returns an error, the caller should propagate the error up | ||||
| // the stack and close the io.Reader used for the prompt which will prevent the | ||||
| // background goroutine from blocking indefinitely. | ||||
| // | ||||
| // Deprecated: this function is for internal use and will be removed in the next release. | ||||
| func PromptForInput(ctx context.Context, in io.Reader, out io.Writer, message string) (string, error) { | ||||
| 	return prompt.ReadInput(ctx, in, out, message) | ||||
| } | ||||
| @ -63,6 +55,8 @@ func PromptForInput(ctx context.Context, in io.Reader, out io.Writer, message st | ||||
| // When the prompt returns an error, the caller should propagate the error up | ||||
| // the stack and close the io.Reader used for the prompt which will prevent the | ||||
| // background goroutine from blocking indefinitely. | ||||
| // | ||||
| // Deprecated: this function is for internal use and will be removed in the next release. | ||||
| func PromptForConfirmation(ctx context.Context, ins io.Reader, outs io.Writer, message string) (bool, error) { | ||||
| 	return prompt.Confirm(ctx, ins, outs, message) | ||||
| } | ||||
| @ -108,12 +102,6 @@ func PruneFilters(dockerCLI config.Provider, pruneFilters filters.Args) filters. | ||||
| 	return pruneFilters | ||||
| } | ||||
|  | ||||
| // AddPlatformFlag adds `platform` to a set of flags for API version 1.32 and later. | ||||
| func AddPlatformFlag(flags *pflag.FlagSet, target *string) { | ||||
| 	flags.StringVar(target, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable") | ||||
| 	_ = flags.SetAnnotation("platform", "version", []string{"1.32"}) | ||||
| } | ||||
|  | ||||
| // ValidateOutputPath validates the output paths of the "docker cp" command. | ||||
| func ValidateOutputPath(path string) error { | ||||
| 	dir := filepath.Dir(filepath.Clean(path)) | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| package opts | ||||
| package loader | ||||
| 
 | ||||
| import ( | ||||
| 	"os" | ||||
| @ -6,19 +6,21 @@ import ( | ||||
| 	"github.com/docker/cli/pkg/kvfile" | ||||
| ) | ||||
| 
 | ||||
| // ParseEnvFile reads a file with environment variables enumerated by lines | ||||
| // parseEnvFile reads a file with environment variables enumerated by lines | ||||
| // | ||||
| // “Environment variable names used by the utilities in the Shell and | ||||
| // Utilities volume of IEEE Std 1003.1-2001 consist solely of uppercase | ||||
| // Utilities volume of [IEEE Std 1003.1-2001] consist solely of uppercase | ||||
| // letters, digits, and the '_' (underscore) from the characters defined in | ||||
| // Portable Character Set and do not begin with a digit. *But*, other | ||||
| // characters may be permitted by an implementation; applications shall | ||||
| // tolerate the presence of such names.” | ||||
| // -- http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html | ||||
| // | ||||
| // As of #16585, it's up to application inside docker to validate or not | ||||
| // As of [moby-16585], it's up to application inside docker to validate or not | ||||
| // environment variables, that's why we just strip leading whitespace and | ||||
| // nothing more. | ||||
| func ParseEnvFile(filename string) ([]string, error) { | ||||
| // | ||||
| // [IEEE Std 1003.1-2001]: http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html | ||||
| // [moby-16585]: https://github.com/moby/moby/issues/16585 | ||||
| func parseEnvFile(filename string) ([]string, error) { | ||||
| 	return kvfile.Parse(filename, os.LookupEnv) | ||||
| } | ||||
							
								
								
									
										12
									
								
								vendor/github.com/docker/cli/cli/compose/loader/loader.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								vendor/github.com/docker/cli/cli/compose/loader/loader.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -17,6 +17,7 @@ import ( | ||||
| 	"github.com/docker/cli/cli/compose/schema" | ||||
| 	"github.com/docker/cli/cli/compose/template" | ||||
| 	"github.com/docker/cli/cli/compose/types" | ||||
| 	"github.com/docker/cli/internal/volumespec" | ||||
| 	"github.com/docker/cli/opts" | ||||
| 	"github.com/docker/cli/opts/swarmopts" | ||||
| 	"github.com/docker/docker/api/types/versions" | ||||
| @ -41,6 +42,13 @@ type Options struct { | ||||
| 	discardEnvFiles bool | ||||
| } | ||||
|  | ||||
| // ParseVolume parses a volume spec without any knowledge of the target platform. | ||||
| // | ||||
| // This function is unused, but kept for backward-compatibility for external users. | ||||
| func ParseVolume(spec string) (types.ServiceVolumeConfig, error) { | ||||
| 	return volumespec.Parse(spec) | ||||
| } | ||||
|  | ||||
| // WithDiscardEnvFiles sets the Options to discard the `env_file` section after resolving to | ||||
| // the `environment` section | ||||
| func WithDiscardEnvFiles(options *Options) { | ||||
| @ -460,7 +468,7 @@ func resolveEnvironment(serviceConfig *types.ServiceConfig, workingDir string, l | ||||
|  | ||||
| 		for _, file := range serviceConfig.EnvFile { | ||||
| 			filePath := absPath(workingDir, file) | ||||
| 			fileVars, err := opts.ParseEnvFile(filePath) | ||||
| 			fileVars, err := parseEnvFile(filePath) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| @ -756,7 +764,7 @@ var transformBuildConfig TransformerFunc = func(data any) (any, error) { | ||||
| var transformServiceVolumeConfig TransformerFunc = func(data any) (any, error) { | ||||
| 	switch value := data.(type) { | ||||
| 	case string: | ||||
| 		return ParseVolume(value) | ||||
| 		return volumespec.Parse(value) | ||||
| 	case map[string]any: | ||||
| 		return data, nil | ||||
| 	default: | ||||
|  | ||||
							
								
								
									
										34
									
								
								vendor/github.com/docker/cli/cli/compose/types/types.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										34
									
								
								vendor/github.com/docker/cli/cli/compose/types/types.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -8,6 +8,8 @@ import ( | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/docker/cli/internal/volumespec" | ||||
| ) | ||||
|  | ||||
| // UnsupportedProperties not yet supported by this implementation of the compose file | ||||
| @ -390,43 +392,23 @@ type ServicePortConfig struct { | ||||
| } | ||||
|  | ||||
| // ServiceVolumeConfig are references to a volume used by a service | ||||
| type ServiceVolumeConfig struct { | ||||
| 	Type        string                `yaml:",omitempty" json:"type,omitempty"` | ||||
| 	Source      string                `yaml:",omitempty" json:"source,omitempty"` | ||||
| 	Target      string                `yaml:",omitempty" json:"target,omitempty"` | ||||
| 	ReadOnly    bool                  `mapstructure:"read_only" yaml:"read_only,omitempty" json:"read_only,omitempty"` | ||||
| 	Consistency string                `yaml:",omitempty" json:"consistency,omitempty"` | ||||
| 	Bind        *ServiceVolumeBind    `yaml:",omitempty" json:"bind,omitempty"` | ||||
| 	Volume      *ServiceVolumeVolume  `yaml:",omitempty" json:"volume,omitempty"` | ||||
| 	Image       *ServiceVolumeImage   `yaml:",omitempty" json:"image,omitempty"` | ||||
| 	Tmpfs       *ServiceVolumeTmpfs   `yaml:",omitempty" json:"tmpfs,omitempty"` | ||||
| 	Cluster     *ServiceVolumeCluster `yaml:",omitempty" json:"cluster,omitempty"` | ||||
| } | ||||
| type ServiceVolumeConfig = volumespec.VolumeConfig | ||||
|  | ||||
| // ServiceVolumeBind are options for a service volume of type bind | ||||
| type ServiceVolumeBind struct { | ||||
| 	Propagation string `yaml:",omitempty" json:"propagation,omitempty"` | ||||
| } | ||||
| type ServiceVolumeBind = volumespec.BindOpts | ||||
|  | ||||
| // ServiceVolumeVolume are options for a service volume of type volume | ||||
| type ServiceVolumeVolume struct { | ||||
| 	NoCopy  bool   `mapstructure:"nocopy" yaml:"nocopy,omitempty" json:"nocopy,omitempty"` | ||||
| 	Subpath string `mapstructure:"subpath" yaml:"subpath,omitempty" json:"subpath,omitempty"` | ||||
| } | ||||
| type ServiceVolumeVolume = volumespec.VolumeOpts | ||||
|  | ||||
| // ServiceVolumeImage are options for a service volume of type image | ||||
| type ServiceVolumeImage struct { | ||||
| 	Subpath string `mapstructure:"subpath" yaml:"subpath,omitempty" json:"subpath,omitempty"` | ||||
| } | ||||
| type ServiceVolumeImage = volumespec.ImageOpts | ||||
|  | ||||
| // ServiceVolumeTmpfs are options for a service volume of type tmpfs | ||||
| type ServiceVolumeTmpfs struct { | ||||
| 	Size int64 `yaml:",omitempty" json:"size,omitempty"` | ||||
| } | ||||
| type ServiceVolumeTmpfs = volumespec.TmpFsOpts | ||||
|  | ||||
| // ServiceVolumeCluster are options for a service volume of type cluster. | ||||
| // Deliberately left blank for future options, but unused now. | ||||
| type ServiceVolumeCluster struct{} | ||||
| type ServiceVolumeCluster = volumespec.ClusterOpts | ||||
|  | ||||
| // FileReferenceConfig for a reference to a swarm file object | ||||
| type FileReferenceConfig struct { | ||||
|  | ||||
							
								
								
									
										4
									
								
								vendor/github.com/docker/cli/cli/config/types/authconfig.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/docker/cli/cli/config/types/authconfig.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -7,8 +7,8 @@ type AuthConfig struct { | ||||
| 	Auth     string `json:"auth,omitempty"` | ||||
|  | ||||
| 	// Email is an optional value associated with the username. | ||||
| 	// This field is deprecated and will be removed in a later | ||||
| 	// version of docker. | ||||
| 	// | ||||
| 	// Deprecated: This field is deprecated since docker 1.11 (API v1.23) and will be removed in the next release. | ||||
| 	Email string `json:"email,omitempty"` | ||||
|  | ||||
| 	ServerAddress string `json:"serveraddress,omitempty"` | ||||
|  | ||||
							
								
								
									
										6
									
								
								vendor/github.com/docker/cli/cli/context/store/errors.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								vendor/github.com/docker/cli/cli/context/store/errors.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,9 +1,9 @@ | ||||
| package store | ||||
|  | ||||
| import cerrdefs "github.com/containerd/errdefs" | ||||
| import "github.com/containerd/errdefs" | ||||
|  | ||||
| func invalidParameter(err error) error { | ||||
| 	if err == nil || cerrdefs.IsInvalidArgument(err) { | ||||
| 	if err == nil || errdefs.IsInvalidArgument(err) { | ||||
| 		return err | ||||
| 	} | ||||
| 	return invalidParameterErr{err} | ||||
| @ -14,7 +14,7 @@ type invalidParameterErr struct{ error } | ||||
| func (invalidParameterErr) InvalidParameter() {} | ||||
|  | ||||
| func notFound(err error) error { | ||||
| 	if err == nil || cerrdefs.IsNotFound(err) { | ||||
| 	if err == nil || errdefs.IsNotFound(err) { | ||||
| 		return err | ||||
| 	} | ||||
| 	return notFoundErr{err} | ||||
|  | ||||
							
								
								
									
										77
									
								
								vendor/github.com/docker/cli/cli/flags/options.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										77
									
								
								vendor/github.com/docker/cli/cli/flags/options.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,12 +1,12 @@ | ||||
| package flags | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
|  | ||||
| 	"github.com/docker/cli/cli/config" | ||||
| 	"github.com/docker/cli/opts" | ||||
| 	"github.com/docker/docker/client" | ||||
| 	"github.com/docker/go-connections/tlsconfig" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| @ -54,6 +54,39 @@ var ( | ||||
| 	dockerTLS       = os.Getenv(EnvEnableTLS) != "" | ||||
| ) | ||||
|  | ||||
| // hostVar is used for the '--host' / '-H' flag to set [ClientOptions.Hosts]. | ||||
| // The [ClientOptions.Hosts] field is a slice because it was originally shared | ||||
| // with the daemon config. However, the CLI only allows for a single host to | ||||
| // be specified. | ||||
| // | ||||
| // hostVar presents itself as a "string", but stores the value in a string | ||||
| // slice. It produces an error when trying to set multiple values, matching | ||||
| // the check in [getServerHost]. | ||||
| // | ||||
| // [getServerHost]: https://github.com/docker/cli/blob/7eab668982645def1cd46fe1b60894cba6fd17a4/cli/command/cli.go#L542-L551 | ||||
| type hostVar struct { | ||||
| 	dst *[]string | ||||
| 	set bool | ||||
| } | ||||
|  | ||||
| func (h *hostVar) String() string { | ||||
| 	if h.dst == nil || len(*h.dst) == 0 { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return (*h.dst)[0] | ||||
| } | ||||
|  | ||||
| func (h *hostVar) Set(s string) error { | ||||
| 	if h.set { | ||||
| 		return errors.New("specify only one -H") | ||||
| 	} | ||||
| 	*h.dst = []string{s} | ||||
| 	h.set = true | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (*hostVar) Type() string { return "string" } | ||||
|  | ||||
| // ClientOptions are the options used to configure the client cli. | ||||
| type ClientOptions struct { | ||||
| 	Debug      bool | ||||
| @ -90,13 +123,13 @@ func (o *ClientOptions) InstallFlags(flags *pflag.FlagSet) { | ||||
| 		KeyFile:  filepath.Join(dockerCertPath, DefaultKeyFile), | ||||
| 	} | ||||
| 	tlsOptions := o.TLSOptions | ||||
| 	flags.Var(opts.NewQuotedString(&tlsOptions.CAFile), "tlscacert", "Trust certs signed only by this CA") | ||||
| 	flags.Var(opts.NewQuotedString(&tlsOptions.CertFile), "tlscert", "Path to TLS certificate file") | ||||
| 	flags.Var(opts.NewQuotedString(&tlsOptions.KeyFile), "tlskey", "Path to TLS key file") | ||||
| 	flags.Var("edString{&tlsOptions.CAFile}, "tlscacert", "Trust certs signed only by this CA") | ||||
| 	flags.Var("edString{&tlsOptions.CertFile}, "tlscert", "Path to TLS certificate file") | ||||
| 	flags.Var("edString{&tlsOptions.KeyFile}, "tlskey", "Path to TLS key file") | ||||
|  | ||||
| 	// opts.ValidateHost is not used here, so as to allow connection helpers | ||||
| 	hostOpt := opts.NewNamedListOptsRef("hosts", &o.Hosts, nil) | ||||
| 	flags.VarP(hostOpt, "host", "H", "Daemon socket to connect to") | ||||
| 	// TODO(thaJeztah): show the default host. | ||||
| 	// TODO(thaJeztah): this should be a string, not an "array" as we only allow a single host. | ||||
| 	flags.VarP(&hostVar{dst: &o.Hosts}, "host", "H", "Daemon socket to connect to") | ||||
| 	flags.StringVarP(&o.Context, "context", "c", "", | ||||
| 		`Name of the context to use to connect to the daemon (overrides `+client.EnvOverrideHost+` env var and default context set with "docker context use")`) | ||||
| } | ||||
| @ -146,3 +179,33 @@ func SetLogLevel(logLevel string) { | ||||
| 		logrus.SetLevel(logrus.InfoLevel) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type quotedString struct { | ||||
| 	value *string | ||||
| } | ||||
|  | ||||
| func (s *quotedString) Set(val string) error { | ||||
| 	*s.value = trimQuotes(val) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (*quotedString) Type() string { | ||||
| 	return "string" | ||||
| } | ||||
|  | ||||
| func (s *quotedString) String() string { | ||||
| 	return *s.value | ||||
| } | ||||
|  | ||||
| func trimQuotes(value string) string { | ||||
| 	if len(value) < 2 { | ||||
| 		return value | ||||
| 	} | ||||
| 	lastIndex := len(value) - 1 | ||||
| 	for _, char := range []byte{'\'', '"'} { | ||||
| 		if value[0] == char && value[lastIndex] == char { | ||||
| 			return value[1:lastIndex] | ||||
| 		} | ||||
| 	} | ||||
| 	return value | ||||
| } | ||||
|  | ||||
							
								
								
									
										40
									
								
								vendor/github.com/docker/cli/internal/volumespec/types.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								vendor/github.com/docker/cli/internal/volumespec/types.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | ||||
| package volumespec | ||||
|  | ||||
| // VolumeConfig are references to a volume used by a service | ||||
| type VolumeConfig struct { | ||||
| 	Type        string       `yaml:",omitempty" json:"type,omitempty"` | ||||
| 	Source      string       `yaml:",omitempty" json:"source,omitempty"` | ||||
| 	Target      string       `yaml:",omitempty" json:"target,omitempty"` | ||||
| 	ReadOnly    bool         `mapstructure:"read_only" yaml:"read_only,omitempty" json:"read_only,omitempty"` | ||||
| 	Consistency string       `yaml:",omitempty" json:"consistency,omitempty"` | ||||
| 	Bind        *BindOpts    `yaml:",omitempty" json:"bind,omitempty"` | ||||
| 	Volume      *VolumeOpts  `yaml:",omitempty" json:"volume,omitempty"` | ||||
| 	Image       *ImageOpts   `yaml:",omitempty" json:"image,omitempty"` | ||||
| 	Tmpfs       *TmpFsOpts   `yaml:",omitempty" json:"tmpfs,omitempty"` | ||||
| 	Cluster     *ClusterOpts `yaml:",omitempty" json:"cluster,omitempty"` | ||||
| } | ||||
|  | ||||
| // BindOpts are options for a service volume of type bind | ||||
| type BindOpts struct { | ||||
| 	Propagation string `yaml:",omitempty" json:"propagation,omitempty"` | ||||
| } | ||||
|  | ||||
| // VolumeOpts are options for a service volume of type volume | ||||
| type VolumeOpts struct { | ||||
| 	NoCopy  bool   `mapstructure:"nocopy" yaml:"nocopy,omitempty" json:"nocopy,omitempty"` | ||||
| 	Subpath string `mapstructure:"subpath" yaml:"subpath,omitempty" json:"subpath,omitempty"` | ||||
| } | ||||
|  | ||||
| // ImageOpts are options for a service volume of type image | ||||
| type ImageOpts struct { | ||||
| 	Subpath string `mapstructure:"subpath" yaml:"subpath,omitempty" json:"subpath,omitempty"` | ||||
| } | ||||
|  | ||||
| // TmpFsOpts are options for a service volume of type tmpfs | ||||
| type TmpFsOpts struct { | ||||
| 	Size int64 `yaml:",omitempty" json:"size,omitempty"` | ||||
| } | ||||
|  | ||||
| // ClusterOpts are options for a service volume of type cluster. | ||||
| // Deliberately left blank for future options, but unused now. | ||||
| type ClusterOpts struct{} | ||||
| @ -1,20 +1,19 @@ | ||||
| package loader | ||||
| package volumespec | ||||
| 
 | ||||
| import ( | ||||
| 	"strings" | ||||
| 	"unicode" | ||||
| 	"unicode/utf8" | ||||
| 
 | ||||
| 	"github.com/docker/cli/cli/compose/types" | ||||
| 	"github.com/docker/docker/api/types/mount" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
| 
 | ||||
| const endOfSpec = rune(0) | ||||
| 
 | ||||
| // ParseVolume parses a volume spec without any knowledge of the target platform | ||||
| func ParseVolume(spec string) (types.ServiceVolumeConfig, error) { | ||||
| 	volume := types.ServiceVolumeConfig{} | ||||
| // Parse parses a volume spec without any knowledge of the target platform | ||||
| func Parse(spec string) (VolumeConfig, error) { | ||||
| 	volume := VolumeConfig{} | ||||
| 
 | ||||
| 	switch len(spec) { | ||||
| 	case 0: | ||||
| @ -49,7 +48,7 @@ func isWindowsDrive(buffer []rune, char rune) bool { | ||||
| 	return char == ':' && len(buffer) == 1 && unicode.IsLetter(buffer[0]) | ||||
| } | ||||
| 
 | ||||
| func populateFieldFromBuffer(char rune, buffer []rune, volume *types.ServiceVolumeConfig) error { | ||||
| func populateFieldFromBuffer(char rune, buffer []rune, volume *VolumeConfig) error { | ||||
| 	strBuffer := string(buffer) | ||||
| 	switch { | ||||
| 	case len(buffer) == 0: | ||||
| @ -74,10 +73,10 @@ func populateFieldFromBuffer(char rune, buffer []rune, volume *types.ServiceVolu | ||||
| 		case "rw": | ||||
| 			volume.ReadOnly = false | ||||
| 		case "nocopy": | ||||
| 			volume.Volume = &types.ServiceVolumeVolume{NoCopy: true} | ||||
| 			volume.Volume = &VolumeOpts{NoCopy: true} | ||||
| 		default: | ||||
| 			if isBindOption(option) { | ||||
| 				volume.Bind = &types.ServiceVolumeBind{Propagation: option} | ||||
| 				volume.Bind = &BindOpts{Propagation: option} | ||||
| 			} | ||||
| 			// ignore unknown options | ||||
| 		} | ||||
| @ -94,7 +93,7 @@ func isBindOption(option string) bool { | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| func populateType(volume *types.ServiceVolumeConfig) { | ||||
| func populateType(volume *VolumeConfig) { | ||||
| 	switch { | ||||
| 	// Anonymous volume | ||||
| 	case volume.Source == "": | ||||
							
								
								
									
										11
									
								
								vendor/github.com/docker/cli/opts/env.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								vendor/github.com/docker/cli/opts/env.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -7,13 +7,14 @@ import ( | ||||
| ) | ||||
|  | ||||
| // ValidateEnv validates an environment variable and returns it. | ||||
| // If no value is specified, it obtains its value from the current environment | ||||
| // If no value is specified, it obtains its value from the current environment. | ||||
| // | ||||
| // As on ParseEnvFile and related to #16585, environment variable names | ||||
| // are not validated, and it's up to the application inside the container | ||||
| // to validate them or not. | ||||
| // Environment variable names are not validated, and it's up to the application | ||||
| // inside the container to validate them (see [moby-16585]). The only validation | ||||
| // here is to check if name is empty, per [moby-25099]. | ||||
| // | ||||
| // The only validation here is to check if name is empty, per #25099 | ||||
| // [moby-16585]: https://github.com/moby/moby/issues/16585 | ||||
| // [moby-25099]: https://github.com/moby/moby/issues/25099 | ||||
| func ValidateEnv(val string) (string, error) { | ||||
| 	k, _, hasValue := strings.Cut(val, "=") | ||||
| 	if k == "" { | ||||
|  | ||||
							
								
								
									
										14
									
								
								vendor/github.com/docker/cli/opts/envfile_deprecated.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								vendor/github.com/docker/cli/opts/envfile_deprecated.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| package opts | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/docker/cli/pkg/kvfile" | ||||
| ) | ||||
|  | ||||
| // ParseEnvFile reads a file with environment variables enumerated by lines | ||||
| // | ||||
| // Deprecated: use [kvfile.Parse] and pass [os.LookupEnv] to lookup env-vars from the current environment. | ||||
| func ParseEnvFile(filename string) ([]string, error) { | ||||
| 	return kvfile.Parse(filename, os.LookupEnv) | ||||
| } | ||||
							
								
								
									
										2
									
								
								vendor/github.com/docker/cli/opts/hosts.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/docker/cli/opts/hosts.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -34,7 +34,7 @@ const ( | ||||
|  | ||||
| // ValidateHost validates that the specified string is a valid host and returns it. | ||||
| // | ||||
| // TODO(thaJeztah): ValidateHost appears to be unused; deprecate it. | ||||
| // Deprecated: this function is no longer used, and will be removed in the next release. | ||||
| func ValidateHost(val string) (string, error) { | ||||
| 	host := strings.TrimSpace(val) | ||||
| 	// The empty string means default and is not handled by parseDockerDaemonHost | ||||
|  | ||||
							
								
								
									
										14
									
								
								vendor/github.com/docker/cli/opts/opts.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								vendor/github.com/docker/cli/opts/opts.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -134,6 +134,8 @@ func (opts *ListOpts) WithValidator(validator ValidatorFctType) *ListOpts { | ||||
|  | ||||
| // NamedOption is an interface that list and map options | ||||
| // with names implement. | ||||
| // | ||||
| // Deprecated: NamedOption is no longer used and will be removed in the next release. | ||||
| type NamedOption interface { | ||||
| 	Name() string | ||||
| } | ||||
| @ -141,6 +143,8 @@ type NamedOption interface { | ||||
| // NamedListOpts is a ListOpts with a configuration name. | ||||
| // This struct is useful to keep reference to the assigned | ||||
| // field name in the internal configuration struct. | ||||
| // | ||||
| // Deprecated: NamedListOpts is no longer used and will be removed in the next release. | ||||
| type NamedListOpts struct { | ||||
| 	name string | ||||
| 	ListOpts | ||||
| @ -149,6 +153,8 @@ type NamedListOpts struct { | ||||
| var _ NamedOption = &NamedListOpts{} | ||||
|  | ||||
| // NewNamedListOptsRef creates a reference to a new NamedListOpts struct. | ||||
| // | ||||
| // Deprecated: NewNamedListOptsRef is no longer used and will be removed in the next release. | ||||
| func NewNamedListOptsRef(name string, values *[]string, validator ValidatorFctType) *NamedListOpts { | ||||
| 	return &NamedListOpts{ | ||||
| 		name:     name, | ||||
| @ -157,6 +163,8 @@ func NewNamedListOptsRef(name string, values *[]string, validator ValidatorFctTy | ||||
| } | ||||
|  | ||||
| // Name returns the name of the NamedListOpts in the configuration. | ||||
| // | ||||
| // Deprecated: NamedListOpts is no longer used and will be removed in the next release. | ||||
| func (o *NamedListOpts) Name() string { | ||||
| 	return o.name | ||||
| } | ||||
| @ -210,6 +218,8 @@ func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts { | ||||
| // NamedMapOpts is a MapOpts struct with a configuration name. | ||||
| // This struct is useful to keep reference to the assigned | ||||
| // field name in the internal configuration struct. | ||||
| // | ||||
| // Deprecated: NamedMapOpts is no longer used and will be removed in the next release. | ||||
| type NamedMapOpts struct { | ||||
| 	name string | ||||
| 	MapOpts | ||||
| @ -218,6 +228,8 @@ type NamedMapOpts struct { | ||||
| var _ NamedOption = &NamedMapOpts{} | ||||
|  | ||||
| // NewNamedMapOpts creates a reference to a new NamedMapOpts struct. | ||||
| // | ||||
| // Deprecated: NamedMapOpts is no longer used and will be removed in the next release. | ||||
| func NewNamedMapOpts(name string, values map[string]string, validator ValidatorFctType) *NamedMapOpts { | ||||
| 	return &NamedMapOpts{ | ||||
| 		name:    name, | ||||
| @ -226,6 +238,8 @@ func NewNamedMapOpts(name string, values map[string]string, validator ValidatorF | ||||
| } | ||||
|  | ||||
| // Name returns the name of the NamedMapOpts in the configuration. | ||||
| // | ||||
| // Deprecated: NamedMapOpts is no longer used and will be removed in the next release. | ||||
| func (o *NamedMapOpts) Name() string { | ||||
| 	return o.name | ||||
| } | ||||
|  | ||||
							
								
								
									
										4
									
								
								vendor/github.com/docker/cli/opts/quotedstring.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/docker/cli/opts/quotedstring.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -2,6 +2,8 @@ package opts | ||||
|  | ||||
| // QuotedString is a string that may have extra quotes around the value. The | ||||
| // quotes are stripped from the value. | ||||
| // | ||||
| // Deprecated: This option type is no longer used and will be removed in the next release. | ||||
| type QuotedString struct { | ||||
| 	value *string | ||||
| } | ||||
| @ -35,6 +37,8 @@ func trimQuotes(value string) string { | ||||
| } | ||||
|  | ||||
| // NewQuotedString returns a new quoted string option | ||||
| // | ||||
| // Deprecated: This option type is no longer used and will be removed in the next release. | ||||
| func NewQuotedString(value *string) *QuotedString { | ||||
| 	return &QuotedString{value: value} | ||||
| } | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user