Compare commits

...

2 Commits

209 changed files with 20586 additions and 1909 deletions

20
go.mod
View File

@ -7,6 +7,8 @@ require (
git.tcp.direct/kayos/common v0.7.6
git.tcp.direct/tcp.direct/database v0.0.0-20220829103039-b85255196bd1
github.com/amimof/huego v1.2.1
github.com/charmbracelet/ssh v0.0.0-20221117183211-483d43d97103
github.com/charmbracelet/wish v1.0.0
github.com/davecgh/go-spew v1.1.1
github.com/dhamith93/systats v0.2.0
github.com/gliderlabs/ssh v0.3.5
@ -14,18 +16,21 @@ require (
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/manifoldco/promptui v0.9.0
github.com/mazznoer/colorgrad v0.9.0
github.com/muesli/termenv v0.12.0
github.com/muesli/termenv v0.13.0
github.com/rs/zerolog v1.27.0
github.com/spf13/viper v1.12.0
go4.org/netipx v0.0.0-20230125063823-8449b0a6169f
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b
golang.org/x/crypto v0.3.0
golang.org/x/net v0.2.0
)
require (
git.tcp.direct/Mirrors/bitcask-mirror v0.0.0-20220228092422-1ec4297c7e34 // indirect
github.com/abcum/lcp v0.0.0-20201209214815-7a3f3840be81 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aymanbagabas/go-osc52 v1.0.3 // indirect
github.com/caarlos0/sshmarshal v0.1.0 // indirect
github.com/charmbracelet/keygen v0.3.0 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/gofrs/flock v0.8.0 // indirect
@ -34,10 +39,11 @@ require (
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mattn/go-tty v0.0.4 // indirect
github.com/mazznoer/csscolorparser v0.1.2 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
@ -51,8 +57,8 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.3.0 // indirect
golang.org/x/exp v0.0.0-20200228211341-fcea875c7e85 // indirect
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/text v0.4.0 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0 // indirect

40
go.sum
View File

@ -64,12 +64,22 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg=
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/caarlos0/sshmarshal v0.1.0 h1:zTCZrDORFfWh526Tsb7vCm3+Yg/SfW/Ub8aQDeosk0I=
github.com/caarlos0/sshmarshal v0.1.0/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/charmbracelet/keygen v0.3.0 h1:mXpsQcH7DDlST5TddmXNXjS0L7ECk4/kLQYyBcsan2Y=
github.com/charmbracelet/keygen v0.3.0/go.mod h1:1ukgO8806O25lUZ5s0IrNur+RlwTBERlezdgW71F5rM=
github.com/charmbracelet/ssh v0.0.0-20221117183211-483d43d97103 h1:wpHMERIN0pQZE635jWwT1dISgfjbpUcEma+fbPKSMCU=
github.com/charmbracelet/ssh v0.0.0-20221117183211-483d43d97103/go.mod h1:0Vm2/8yBljiLDnGJHU8ehswfawrEybGk33j5ssqKQVM=
github.com/charmbracelet/wish v1.0.0 h1:Ca/Sm8NfbW0/hEtw+voxwgKd5iRq9v7P3X/cDVV8doY=
github.com/charmbracelet/wish v1.0.0/go.mod h1:LatUnJh7kQXK5kvkvuwvddCSeUn8Yss02nDh54yLQas=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
@ -168,7 +178,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -261,11 +271,12 @@ github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-tty v0.0.4 h1:NVikla9X8MN0SQAqCYzpGyXv0jY7MNl3HOWD2dkle7E=
github.com/mattn/go-tty v0.0.4/go.mod h1:u5GGXBtZU6RQoKV8gY5W6UhMudbR5vXnUe7j3pxse28=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@ -276,6 +287,7 @@ github.com/mazznoer/csscolorparser v0.1.2/go.mod h1:Aj22+L/rYN/Y6bj3bYqO3N6g1dtd
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
@ -288,8 +300,8 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/muesli/termenv v0.12.0 h1:KuQRUE3PgxRFWhq4gHvZtPSLCGDqM5q/cYr1pZ39ytc=
github.com/muesli/termenv v0.12.0/go.mod h1:WCCv32tusQ/EEZ5S8oUIIrC/nIuBcxCVqlN4Xfkv+7A=
github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0=
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@ -421,8 +433,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
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=
@ -502,8 +515,9 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
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=
@ -583,15 +597,16 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
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=
@ -600,8 +615,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
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=

View File

@ -3,6 +3,7 @@ package cli
import (
"bufio"
"fmt"
// "io"
"os"
"path/filepath"
"strings"
@ -33,14 +34,21 @@ func executor(cmd string) {
var status = 0
defer func() {
if r := recover(); r != nil {
log.Error().Msgf("PANIC: %s", r)
log.Error().Caller().Msgf("PANIC: %s", r)
}
if _, ok := noHist[cmd]; !ok && status == 0 {
history = append(history, cmd)
go saveHist()
}
}()
// hacky bugfix
cmd = strings.ReplaceAll(cmd, "#", "_POUNDSIGN_")
args, err := shlex.Split(strings.TrimSpace(cmd))
for i, arg := range args {
args[i] = strings.ReplaceAll(arg, "_POUNDSIGN_", "#")
}
if err != nil {
log.Error().Msgf("error parsing command: %s", err)
status = 1
@ -214,6 +222,7 @@ func saveHist() {
_ = os.WriteFile(filepath.Join(pth, ".ziggs_history"), []byte(strings.Join(history, "\n")), 0644)
}
// func StartCLI(r io.Reader, w io.Writer) {
func StartCLI() {
log = config.GetLogger()
processBridges()
@ -223,9 +232,13 @@ func StartCLI() {
processScenes(ziggy.GetSceneMap())
}()
buildTime, _ := common.Version()
// cli.NewStdoutWriter().
prompt = cli.New(
executor,
completer,
// cli.OptionWriter(w),
// cli.Op(r),
// cli.OptionPrefixBackgroundColor(cli.Black),
cli.OptionPrefixTextColor(cli.Yellow),
cli.OptionHistory(getHist()),

View File

@ -45,7 +45,7 @@ func cpuInit(argVal string, bridge *ziggy.Bridge, cpuTarget cmdTarget) error {
cpuOn = false
}()
var lights []*huego.Light
for _, l := range cpuTarget.(*huego.Group).Lights {
for _, l := range cpuTarget.(*ziggy.HueGroup).Lights {
lint, _ := strconv.Atoi(l)
lptr, err := bridge.GetLight(lint)
if err != nil {

View File

@ -129,6 +129,7 @@ func cmdSet(bridge *ziggy.Bridge, args []string) error {
if len(args) == argHead-1 {
return errors.New("not enough arguments")
}
log.Trace().Caller().Msgf("color, args: %v", args)
argHead++
newcolor, err := common.ParseHexColorFast(args[argHead])
if err != nil {

View File

@ -49,6 +49,8 @@ var (
SSHListen string
// SSHHostKey is the path to the SSH host key, if any. If none is specified, one will be generated.
SSHHostKey string
// SSHPublicKeys is a list of public keys that are allowed to connect to ziggs via SSH.
SSHPublicKeys []string
)
var (

View File

@ -1,5 +1,6 @@
package data
/*
import (
"encoding/json"
"reflect"
@ -208,3 +209,4 @@ func TestAddSequence(t *testing.T) {
}
})
}
*/

24
internal/ssh/keys.go Normal file
View File

@ -0,0 +1,24 @@
package ssh
import (
"git.tcp.direct/kayos/common/squish"
"github.com/charmbracelet/ssh"
"git.tcp.direct/kayos/ziggs/internal/config"
)
func (s *Server) GetStaticKeys() map[string]ssh.PublicKey {
if s.keys != nil {
return s.keys
}
pubs := make(map[string]ssh.PublicKey)
for _, key := range config.SSHPublicKeys {
pub, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key))
if err != nil {
panic(err)
}
pubs[squish.B64e(pub.Marshal())] = pub
}
s.keys = pubs
return pubs
}

94
internal/ssh/serve.go Normal file
View File

@ -0,0 +1,94 @@
package ssh
import (
"context"
"log"
"os"
"os/signal"
"syscall"
"time"
"git.tcp.direct/kayos/common/squish"
"github.com/rs/zerolog"
"git.tcp.direct/kayos/ziggs/internal/cli"
"git.tcp.direct/kayos/ziggs/internal/config"
"github.com/charmbracelet/ssh"
"github.com/charmbracelet/wish"
"github.com/charmbracelet/wish/logging"
)
type Server struct {
*ssh.Server
clients map[string]ssh.Session
keys map[string]ssh.PublicKey
log *zerolog.Logger
}
func (s *Server) checkAuth(h ssh.Handler) ssh.Handler {
return func(ss ssh.Session) {
k, ok := s.GetStaticKeys()[squish.B64e(ss.PublicKey().Marshal())]
if !ok {
wish.Println(ss, "huh?")
_ = ss.Close()
return
}
switch {
case ssh.KeysEqual(ss.PublicKey(), k):
h(ss)
default:
wish.Println(ss, "FUBAR")
_ = ss.Close()
return
}
}
}
func (s *Server) ziggssh(h ssh.Handler) ssh.Handler {
return func(ss ssh.Session) {
cli.StartCLI()
h(ss)
}
}
func (s *Server) ListenAndServe() error {
var err error
s.Server, err = wish.NewServer(
wish.WithAddress(config.SSHListen),
wish.WithHostKeyPath(config.SSHHostKey),
wish.WithVersion("SSH-2.0-ziggs"),
wish.WithPublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) bool {
return true
}),
wish.WithMiddleware(
logging.Middleware(),
s.checkAuth,
),
)
if err != nil {
return err
}
done := make(chan os.Signal, 1)
signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
log.Printf("Starting SSH server on %s", config.SSHListen)
go func() {
if err = s.ListenAndServe(); err != nil {
log.Fatalln(err)
}
}()
go func() {
<-done
log.Println("Stopping SSH server")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer func() { cancel() }()
if err := s.Shutdown(ctx); err != nil {
log.Fatalln(err)
}
}()
return nil
}

View File

@ -14,6 +14,7 @@ type Config struct {
ProxyPort uint16 `json:"proxyport,omitempty"`
LinkButton bool `json:"linkbutton,omitempty"`
IPAddress string `json:"ipaddress,omitempty"`
Reboot bool `json:"reboot,omitempty"`
Mac string `json:"mac,omitempty"`
NetMask string `json:"netmask,omitempty"`
Gateway string `json:"gateway,omitempty"`
@ -30,7 +31,6 @@ type Config struct {
DatastoreVersion string `json:"datastoreversion,omitempty"`
StarterKitID string `json:"starterkitid,omitempty"`
InternetService InternetService `json:"internetservices,omitempty"`
Reboot bool `json:"reboot,omitempty"`
}
// SwUpdate contains information related to software updates. Deprecated in 1.20

21
vendor/github.com/aymanbagabas/go-osc52/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Ayman Bagabas
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.

25
vendor/github.com/aymanbagabas/go-osc52/README.md generated vendored Normal file
View File

@ -0,0 +1,25 @@
# go-osc52
<p>
<a href="https://github.com/aymanbagabas/go-osc52/releases"><img src="https://img.shields.io/github/release/aymanbagabas/go-osc52.svg" alt="Latest Release"></a>
<a href="https://pkg.go.dev/github.com/aymanbagabas/go-osc52?tab=doc"><img src="https://godoc.org/github.com/golang/gddo?status.svg" alt="GoDoc"></a>
</p>
A terminal Go library to copy text to clipboard from anywhere. It does so using [ANSI OSC52](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands). The `Copy()` function defaults to copying text from terminals running locally.
To use this over SSH, using [gliderlabs/ssh](https://github.com/gliderlabs/ssh), use `NewOutput(sshSession, sshSession.Environ())` and make sure you pass the `TERM` environment variable in your SSH connection.
```sh
ssh -o SendEnv=TERM <host>
```
Tmux users need to pass an additional environment variable `TMUX`.
```sh
ssh -o SendEnv=TERM -o SendEnv=TMUX <host>
```
# Credits
* [vim-oscyank](https://github.com/ojroques/vim-oscyank) this is heavily inspired by vim-oscyank.

110
vendor/github.com/aymanbagabas/go-osc52/osc52.go generated vendored Normal file
View File

@ -0,0 +1,110 @@
package osc52
import (
"encoding/base64"
"fmt"
"io"
"os"
"strings"
)
// output is the default output for Copy which uses os.Stdout and os.Environ.
var output = NewOutput(os.Stdout, os.Environ())
// envs is a map of environment variables.
type envs map[string]string
// Get returns the value of the environment variable named by the key.
func (e envs) Get(key string) string {
v, ok := e[key]
if !ok {
return ""
}
return v
}
// Output is where the OSC52 string should be written.
type Output struct {
out io.Writer
envs envs
}
// NewOutput returns a new Output.
func NewOutput(out io.Writer, envs []string) *Output {
e := make(map[string]string, 0)
for _, env := range envs {
s := strings.Split(env, "=")
k := s[0]
v := strings.Join(s[1:], "=")
e[k] = v
}
o := &Output{
out: out,
envs: e,
}
return o
}
// Copy copies the OSC52 string to the output. This is the default copy function.
func Copy(str string) {
output.Copy(str)
}
// Copy copies the OSC52 string to the output.
func (o *Output) Copy(str string) {
mode := "default"
term := o.envs.Get("TERM")
switch {
case o.envs.Get("TMUX") != "", strings.HasPrefix(term, "tmux"):
mode = "tmux"
case strings.HasPrefix(term, "screen"):
mode = "screen"
case strings.Contains(term, "kitty"):
mode = "kitty"
}
switch mode {
case "default":
o.copyDefault(str)
case "tmux":
o.copyTmux(str)
case "screen":
o.copyDCS(str)
case "kitty":
o.copyKitty(str)
}
}
// copyDefault copies the OSC52 string to the output.
func (o *Output) copyDefault(str string) {
b64 := base64.StdEncoding.EncodeToString([]byte(str))
o.out.Write([]byte("\x1b]52;c;" + b64 + "\x07"))
}
// copyTmux copies the OSC52 string escaped for Tmux.
func (o *Output) copyTmux(str string) {
b64 := base64.StdEncoding.EncodeToString([]byte(str))
o.out.Write([]byte("\x1bPtmux;\x1b\x1b]52;c;" + b64 + "\x07\x1b\\"))
}
// copyDCS copies the OSC52 string wrapped in a DCS sequence which is
// appropriate when using screen.
//
// Screen doesn't support OSC52 but will pass the contents of a DCS sequence to
// the outer terminal unchanged.
func (o *Output) copyDCS(str string) {
// Here, we split the encoded string into 76 bytes chunks and then join the
// chunks with <end-dsc><start-dsc> sequences. Finally, wrap the whole thing in
// <start-dsc><start-osc52><joined-chunks><end-osc52><end-dsc>.
b64 := base64.StdEncoding.EncodeToString([]byte(str))
s := strings.SplitN(b64, "", 76)
q := fmt.Sprintf("\x1bP\x1b]52;c;%s\x07\x1b\x5c", strings.Join(s, "\x1b\\\x1bP"))
o.out.Write([]byte(q))
}
// copyKitty copies the OSC52 string to Kitty. First, it flushes the keyboard
// before copying, this is required for Kitty < 0.22.0.
func (o *Output) copyKitty(str string) {
o.out.Write([]byte("\x1b]52;c;!\x07"))
o.copyDefault(str)
}

2
vendor/github.com/caarlos0/sshmarshal/.gitignore generated vendored Normal file
View File

@ -0,0 +1,2 @@
dist/

View File

@ -0,0 +1,3 @@
includes:
- from_url:
url: https://raw.githubusercontent.com/caarlos0/.goreleaserfiles/main/lib.yml

7
vendor/github.com/caarlos0/sshmarshal/README.md generated vendored Normal file
View File

@ -0,0 +1,7 @@
# sshmarshal
Library containing code copied from [x/crypto][crypto] and [this patch][patch] so we can
more easily marshal SSH private keys.
[patch]: https://go-review.googlesource.com/c/crypto/+/218620/
[crypto]: https://github.com/golang/crypto

View File

@ -0,0 +1,93 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package bcrypt_pbkdf implements bcrypt_pbkdf(3) from OpenBSD.
//
// See https://flak.tedunangst.com/post/bcrypt-pbkdf and
// https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/lib/libutil/bcrypt_pbkdf.c.
package bcrypt_pbkdf
import (
"crypto/sha512"
"errors"
"golang.org/x/crypto/blowfish"
)
const blockSize = 32
// Key derives a key from the password, salt and rounds count, returning a
// []byte of length keyLen that can be used as cryptographic key.
func Key(password, salt []byte, rounds, keyLen int) ([]byte, error) {
if rounds < 1 {
return nil, errors.New("bcrypt_pbkdf: number of rounds is too small")
}
if len(password) == 0 {
return nil, errors.New("bcrypt_pbkdf: empty password")
}
if len(salt) == 0 || len(salt) > 1<<20 {
return nil, errors.New("bcrypt_pbkdf: bad salt length")
}
if keyLen > 1024 {
return nil, errors.New("bcrypt_pbkdf: keyLen is too large")
}
numBlocks := (keyLen + blockSize - 1) / blockSize
key := make([]byte, numBlocks*blockSize)
h := sha512.New()
h.Write(password)
shapass := h.Sum(nil)
shasalt := make([]byte, 0, sha512.Size)
cnt, tmp := make([]byte, 4), make([]byte, blockSize)
for block := 1; block <= numBlocks; block++ {
h.Reset()
h.Write(salt)
cnt[0] = byte(block >> 24)
cnt[1] = byte(block >> 16)
cnt[2] = byte(block >> 8)
cnt[3] = byte(block)
h.Write(cnt)
bcryptHash(tmp, shapass, h.Sum(shasalt))
out := make([]byte, blockSize)
copy(out, tmp)
for i := 2; i <= rounds; i++ {
h.Reset()
h.Write(tmp)
bcryptHash(tmp, shapass, h.Sum(shasalt))
for j := 0; j < len(out); j++ {
out[j] ^= tmp[j]
}
}
for i, v := range out {
key[i*numBlocks+(block-1)] = v
}
}
return key[:keyLen], nil
}
var magic = []byte("OxychromaticBlowfishSwatDynamite")
func bcryptHash(out, shapass, shasalt []byte) {
c, err := blowfish.NewSaltedCipher(shapass, shasalt)
if err != nil {
panic(err)
}
for i := 0; i < 64; i++ {
blowfish.ExpandKey(shasalt, c)
blowfish.ExpandKey(shapass, c)
}
copy(out, magic)
for i := 0; i < 32; i += 8 {
for j := 0; j < 64; j++ {
c.Encrypt(out[i:i+8], out[i:i+8])
}
}
// Swap bytes due to different endianness.
for i := 0; i < 32; i += 4 {
out[i+3], out[i+2], out[i+1], out[i] = out[i], out[i+1], out[i+2], out[i+3]
}
}

236
vendor/github.com/caarlos0/sshmarshal/marshal.go generated vendored Normal file
View File

@ -0,0 +1,236 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package sshmarshal
import (
"crypto"
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"encoding/binary"
"encoding/pem"
"errors"
"fmt"
"io"
"math/big"
"github.com/caarlos0/sshmarshal/internal/bcrypt_pbkdf"
. "golang.org/x/crypto/ssh"
)
// MarshalPrivateKey returns a PEM block with the private key serialized in the
// OpenSSH format.
func MarshalPrivateKey(key crypto.PrivateKey, comment string) (*pem.Block, error) {
return marshalOpenSSHPrivateKey(key, comment, unencryptedOpenSSHMarshaler)
}
// MarshalPrivateKeyWithPassphrase returns a PEM block holding the encrypted
// private key serialized in the OpenSSH format.
func MarshalPrivateKeyWithPassphrase(key crypto.PrivateKey, comment string, passphrase []byte) (*pem.Block, error) {
return marshalOpenSSHPrivateKey(key, comment, passphraseProtectedOpenSSHMarshaler(passphrase))
}
func unencryptedOpenSSHMarshaler(privKeyBlock []byte) ([]byte, string, string, string, error) {
key := generateOpenSSHPadding(privKeyBlock, 8)
return key, "none", "none", "", nil
}
func passphraseProtectedOpenSSHMarshaler(passphrase []byte) openSSHEncryptFunc {
return func(privKeyBlock []byte) ([]byte, string, string, string, error) {
salt := make([]byte, 16)
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
return nil, "", "", "", err
}
opts := struct {
Salt []byte
Rounds uint32
}{salt, 16}
// Derive key to encrypt the private key block.
k, err := bcrypt_pbkdf.Key(passphrase, salt, int(opts.Rounds), 32+aes.BlockSize)
if err != nil {
return nil, "", "", "", err
}
// Add padding matching the block size of AES.
keyBlock := generateOpenSSHPadding(privKeyBlock, aes.BlockSize)
// Encrypt the private key using the derived secret.
dst := make([]byte, len(keyBlock))
iv := k[32 : 32+aes.BlockSize]
block, err := aes.NewCipher(k[:32])
if err != nil {
return nil, "", "", "", err
}
stream := cipher.NewCTR(block, iv)
stream.XORKeyStream(dst, keyBlock)
return dst, "aes256-ctr", "bcrypt", string(Marshal(opts)), nil
}
}
const magic = "openssh-key-v1\x00"
type openSSHEncryptFunc func(privKeyBlock []byte) (protectedKeyBlock []byte, cipherName, kdfName, kdfOptions string, err error)
func marshalOpenSSHPrivateKey(key crypto.PrivateKey, comment string, encrypt openSSHEncryptFunc) (*pem.Block, error) {
var w struct {
CipherName string
KdfName string
KdfOpts string
NumKeys uint32
PubKey []byte
PrivKeyBlock []byte
}
var pk1 struct {
Check1 uint32
Check2 uint32
Keytype string
Rest []byte `ssh:"rest"`
}
// Random check bytes.
var check uint32
if err := binary.Read(rand.Reader, binary.BigEndian, &check); err != nil {
return nil, err
}
pk1.Check1 = check
pk1.Check2 = check
w.NumKeys = 1
// Use a []byte directly on ed25519 keys.
if k, ok := key.(*ed25519.PrivateKey); ok {
key = *k
}
switch k := key.(type) {
case *rsa.PrivateKey:
E := new(big.Int).SetInt64(int64(k.PublicKey.E))
// Marshal public key:
// E and N are in reversed order in the public and private key.
pubKey := struct {
KeyType string
E *big.Int
N *big.Int
}{
KeyAlgoRSA,
E, k.PublicKey.N,
}
w.PubKey = Marshal(pubKey)
// Marshal private key.
key := struct {
N *big.Int
E *big.Int
D *big.Int
Iqmp *big.Int
P *big.Int
Q *big.Int
Comment string
}{
k.PublicKey.N, E,
k.D, k.Precomputed.Qinv, k.Primes[0], k.Primes[1],
comment,
}
pk1.Keytype = KeyAlgoRSA
pk1.Rest = Marshal(key)
case ed25519.PrivateKey:
pub := make([]byte, ed25519.PublicKeySize)
priv := make([]byte, ed25519.PrivateKeySize)
copy(pub, k[ed25519.PublicKeySize:])
copy(priv, k)
// Marshal public key.
pubKey := struct {
KeyType string
Pub []byte
}{
KeyAlgoED25519, pub,
}
w.PubKey = Marshal(pubKey)
// Marshal private key.
key := struct {
Pub []byte
Priv []byte
Comment string
}{
pub, priv,
comment,
}
pk1.Keytype = KeyAlgoED25519
pk1.Rest = Marshal(key)
case *ecdsa.PrivateKey:
var curve, keyType string
switch name := k.Curve.Params().Name; name {
case "P-256":
curve = "nistp256"
keyType = KeyAlgoECDSA256
case "P-384":
curve = "nistp384"
keyType = KeyAlgoECDSA384
case "P-521":
curve = "nistp521"
keyType = KeyAlgoECDSA521
default:
return nil, errors.New("ssh: unhandled elliptic curve " + name)
}
pub := elliptic.Marshal(k.Curve, k.PublicKey.X, k.PublicKey.Y)
// Marshal public key.
pubKey := struct {
KeyType string
Curve string
Pub []byte
}{
keyType, curve, pub,
}
w.PubKey = Marshal(pubKey)
// Marshal private key.
key := struct {
Curve string
Pub []byte
D *big.Int
Comment string
}{
curve, pub, k.D,
comment,
}
pk1.Keytype = keyType
pk1.Rest = Marshal(key)
default:
return nil, fmt.Errorf("ssh: unsupported key type %T", k)
}
var err error
// Add padding and encrypt the key if necessary.
w.PrivKeyBlock, w.CipherName, w.KdfName, w.KdfOpts, err = encrypt(Marshal(pk1))
if err != nil {
return nil, err
}
b := Marshal(w)
block := &pem.Block{
Type: "OPENSSH PRIVATE KEY",
Bytes: append([]byte(magic), b...),
}
return block, nil
}
func generateOpenSSHPadding(block []byte, blockSize int) []byte {
for i, l := 0, len(block); (l+i)%blockSize != 0; i++ {
block = append(block, byte(i+1))
}
return block
}

34
vendor/github.com/charmbracelet/keygen/.golangci.yml generated vendored Normal file
View File

@ -0,0 +1,34 @@
run:
tests: false
issues:
include:
- EXC0001
- EXC0005
- EXC0011
- EXC0012
- EXC0013
max-issues-per-linter: 0
max-same-issues: 0
linters:
enable:
- bodyclose
- dupl
- exportloopref
- goconst
- godot
- godox
- goimports
- goprintffuncname
- gosec
- ifshort
- misspell
- prealloc
- revive
- rowserrcheck
- sqlclosecheck
- unconvert
- unparam
- whitespace

21
vendor/github.com/charmbracelet/keygen/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 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.

32
vendor/github.com/charmbracelet/keygen/README.md generated vendored Normal file
View File

@ -0,0 +1,32 @@
# Keygen
[![Latest Release](https://img.shields.io/github/release/charmbracelet/keygen.svg)](https://github.com/charmbracelet/keygen/releases)
[![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://pkg.go.dev/github.com/charmbracelet/keygen?tab=doc)
[![Build Status](https://github.com/charmbracelet/keygen/workflows/build/badge.svg)](https://github.com/charmbracelet/keygen/actions)
[![Go ReportCard](https://goreportcard.com/badge/charmbracelet/keygen)](https://goreportcard.com/report/charmbracelet/keygen)
An SSH key pair generator with password protected keys support. Supports generating RSA, ECDSA, and Ed25519 keys.
## Example
```go
filepath := filepath.Join(".ssh", "my_awesome_key")
passphrase := []byte("awesome_secret")
k, err := NewWithWrite(filepath, passphrase, key.Ed25519)
if err != nil {
fmt.Printf("error creating SSH key pair: %v", err)
os.Exit(1)
}
```
## License
[MIT](https://github.com/charmbracelet/keygen/raw/master/LICENSE)
***
Part of [Charm](https://charm.sh).
<a href="https://charm.sh/"><img alt="the Charm logo" src="https://stuff.charm.sh/charm-badge-unrounded.jpg" width="400"></a>
Charm热爱开源 • Charm loves open source

387
vendor/github.com/charmbracelet/keygen/keygen.go generated vendored Normal file
View File

@ -0,0 +1,387 @@
// Package keygen handles the creation of new SSH key pairs.
package keygen
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"os"
"os/user"
"path/filepath"
"github.com/caarlos0/sshmarshal"
"github.com/mitchellh/go-homedir"
"golang.org/x/crypto/ssh"
)
// KeyType represents a type of SSH key.
type KeyType string
// Supported key types.
const (
RSA KeyType = "rsa"
Ed25519 KeyType = "ed25519"
ECDSA KeyType = "ecdsa"
)
const rsaDefaultBits = 4096
// ErrMissingSSHKeys indicates we're missing some keys that we expected to
// have after generating. This should be an extreme edge case.
var ErrMissingSSHKeys = errors.New("missing one or more keys; did something happen to them after they were generated?")
// ErrUnsupportedKeyType indicates an unsupported key type.
type ErrUnsupportedKeyType struct {
keyType string
}
// Error implements the error interface for ErrUnsupportedKeyType
func (e ErrUnsupportedKeyType) Error() string {
err := "unsupported key type"
if e.keyType != "" {
err += fmt.Sprintf(": %s", e.keyType)
}
return err
}
// FilesystemErr is used to signal there was a problem creating keys at the
// filesystem-level. For example, when we're unable to create a directory to
// store new SSH keys in.
type FilesystemErr struct {
Err error
}
// Error returns a human-readable string for the error. It implements the error
// interface.
func (e FilesystemErr) Error() string {
return e.Err.Error()
}
// Unwrap returns the underlying error.
func (e FilesystemErr) Unwrap() error {
return e.Err
}
// SSHKeysAlreadyExistErr indicates that files already exist at the location at
// which we're attempting to create SSH keys.
type SSHKeysAlreadyExistErr struct {
Path string
}
// SSHKeyPair holds a pair of SSH keys and associated methods.
type SSHKeyPair struct {
path string // private key filename path; public key will have .pub appended
passphrase []byte
keyType KeyType
privateKey crypto.PrivateKey
}
func (s SSHKeyPair) privateKeyPath() string {
p := fmt.Sprintf("%s_%s", s.path, s.keyType)
return p
}
func (s SSHKeyPair) publicKeyPath() string {
return s.privateKeyPath() + ".pub"
}
// New generates an SSHKeyPair, which contains a pair of SSH keys.
func New(path string, passphrase []byte, keyType KeyType) (*SSHKeyPair, error) {
var err error
s := &SSHKeyPair{
path: path,
keyType: keyType,
passphrase: passphrase,
}
if s.KeyPairExists() {
privData, err := ioutil.ReadFile(s.privateKeyPath())
if err != nil {
return nil, err
}
var k interface{}
if len(passphrase) > 0 {
k, err = ssh.ParseRawPrivateKeyWithPassphrase(privData, passphrase)
} else {
k, err = ssh.ParseRawPrivateKey(privData)
}
if err != nil {
return nil, err
}
switch k := k.(type) {
case *rsa.PrivateKey, *ecdsa.PrivateKey, *ed25519.PrivateKey:
s.privateKey = k
default:
return nil, ErrUnsupportedKeyType{fmt.Sprintf("%T", k)}
}
return s, nil
}
switch keyType {
case Ed25519:
err = s.generateEd25519Keys()
case RSA:
err = s.generateRSAKeys(rsaDefaultBits)
case ECDSA:
err = s.generateECDSAKeys(elliptic.P384())
default:
return nil, ErrUnsupportedKeyType{string(keyType)}
}
if err != nil {
return nil, err
}
return s, nil
}
// NewWithWrite generates an SSHKeyPair and writes it to disk if not exist.
func NewWithWrite(path string, passphrase []byte, keyType KeyType) (*SSHKeyPair, error) {
s, err := New(path, passphrase, keyType)
if err != nil {
return nil, err
}
if !s.KeyPairExists() {
if err = s.WriteKeys(); err != nil {
return nil, err
}
}
return s, nil
}
// PrivateKey returns the unencrypted private key.
func (s *SSHKeyPair) PrivateKey() crypto.PrivateKey {
switch s.keyType {
case RSA, Ed25519, ECDSA:
return s.privateKey
default:
return nil
}
}
// PrivateKeyPEM returns the unencrypted private key in OPENSSH PEM format.
func (s *SSHKeyPair) PrivateKeyPEM() []byte {
block, err := s.pemBlock(nil)
if err != nil {
return nil
}
return pem.EncodeToMemory(block)
}
// PublicKey returns the SSH public key (RFC 4253). Ready to be used in an
// OpenSSH authorized_keys file.
func (s *SSHKeyPair) PublicKey() []byte {
var pk crypto.PublicKey
// Prepare public key
switch s.keyType {
case RSA:
key, ok := s.privateKey.(*rsa.PrivateKey)
if !ok {
return nil
}
pk = key.Public()
case Ed25519:
key, ok := s.privateKey.(*ed25519.PrivateKey)
if !ok {
return nil
}
pk = key.Public()
case ECDSA:
key, ok := s.privateKey.(*ecdsa.PrivateKey)
if !ok {
return nil
}
pk = key.Public()
default:
return nil
}
p, err := ssh.NewPublicKey(pk)
if err != nil {
return nil
}
// serialize public key
ak := ssh.MarshalAuthorizedKey(p)
return pubKeyWithMemo(ak)
}
func (s *SSHKeyPair) pemBlock(passphrase []byte) (*pem.Block, error) {
key := s.PrivateKey()
if key == nil {
return nil, ErrMissingSSHKeys
}
switch s.keyType {
case RSA, Ed25519, ECDSA:
if len(passphrase) > 0 {
return sshmarshal.MarshalPrivateKeyWithPassphrase(key, "", passphrase)
}
return sshmarshal.MarshalPrivateKey(key, "")
default:
return nil, ErrUnsupportedKeyType{string(s.keyType)}
}
}
// generateEd25519Keys creates a pair of EdD25519 keys for SSH auth.
func (s *SSHKeyPair) generateEd25519Keys() error {
// Generate keys
_, privateKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return err
}
s.privateKey = &privateKey
return nil
}
// generateEd25519Keys creates a pair of EdD25519 keys for SSH auth.
func (s *SSHKeyPair) generateECDSAKeys(curve elliptic.Curve) error {
// Generate keys
privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return err
}
s.privateKey = privateKey
return nil
}
// generateRSAKeys creates a pair for RSA keys for SSH auth.
func (s *SSHKeyPair) generateRSAKeys(bitSize int) error {
// Generate private key
privateKey, err := rsa.GenerateKey(rand.Reader, bitSize)
if err != nil {
return err
}
// Validate private key
err = privateKey.Validate()
if err != nil {
return err
}
s.privateKey = privateKey
return nil
}
// prepFilesystem makes sure the state of the filesystem is as it needs to be
// in order to write our keys to disk. It will create and/or set permissions on
// the SSH directory we're going to write our keys to (for example, ~/.ssh) as
// well as make sure that no files exist at the location in which we're going
// to write out keys.
func (s *SSHKeyPair) prepFilesystem() error {
var err error
keyDir := filepath.Dir(s.path)
if keyDir != "" {
keyDir, err = homedir.Expand(keyDir)
if err != nil {
return err
}
info, err := os.Stat(keyDir)
if os.IsNotExist(err) {
// Directory doesn't exist: create it
return os.MkdirAll(keyDir, 0700)
}
if err != nil {
// There was another error statting the directory; something is awry
return FilesystemErr{Err: err}
}
if !info.IsDir() {
// It exists but it's not a directory
return FilesystemErr{Err: fmt.Errorf("%s is not a directory", keyDir)}
}
if info.Mode().Perm() != 0700 {
// Permissions are wrong: fix 'em
if err := os.Chmod(keyDir, 0700); err != nil {
return FilesystemErr{Err: err}
}
}
}
// Make sure the files we're going to write to don't already exist
if fileExists(s.privateKeyPath()) {
return SSHKeysAlreadyExistErr{Path: s.privateKeyPath()}
}
if fileExists(s.publicKeyPath()) {
return SSHKeysAlreadyExistErr{Path: s.publicKeyPath()}
}
// The directory looks good as-is
return nil
}
// WriteKeys writes the SSH key pair to disk.
func (s *SSHKeyPair) WriteKeys() error {
var err error
priv := s.PrivateKeyPEM()
pub := s.PublicKey()
if priv == nil || pub == nil {
return ErrMissingSSHKeys
}
// Encrypt private key with passphrase
if len(s.passphrase) > 0 {
block, err := s.pemBlock(s.passphrase)
if err != nil {
return err
}
priv = pem.EncodeToMemory(block)
}
if err = s.prepFilesystem(); err != nil {
return err
}
if err := writeKeyToFile(priv, s.privateKeyPath()); err != nil {
return err
}
if err := writeKeyToFile(pub, s.publicKeyPath()); err != nil {
return err
}
return nil
}
// KeyPairExists checks if the SSH key pair exists on disk.
func (s *SSHKeyPair) KeyPairExists() bool {
return fileExists(s.privateKeyPath()) && fileExists(s.publicKeyPath())
}
func writeKeyToFile(keyBytes []byte, path string) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return ioutil.WriteFile(path, keyBytes, 0600)
}
return FilesystemErr{Err: fmt.Errorf("file %s already exists", path)}
}
func fileExists(path string) bool {
_, err := os.Stat(path)
if os.IsNotExist(err) {
return false
}
if err != nil {
return false
}
return true
}
// attaches a user@host suffix to a serialized public key. returns the original
// pubkey if we can't get the username or host.
func pubKeyWithMemo(pubKey []byte) []byte {
u, err := user.Current()
if err != nil {
return pubKey
}
hostname, err := os.Hostname()
if err != nil {
return pubKey
}
return append(bytes.TrimRight(pubKey, "\n"), []byte(fmt.Sprintf(" %s@%s\n", u.Username, hostname))...)
}
// Error returns the a human-readable error message for SSHKeysAlreadyExistErr.
// It satisfies the error interface.
func (e SSHKeysAlreadyExistErr) Error() string {
return fmt.Sprintf("ssh key %s already exists", e.Path)
}

27
vendor/github.com/charmbracelet/ssh/LICENSE generated vendored Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2016 Glider Labs. 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 Glider Labs 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.

96
vendor/github.com/charmbracelet/ssh/README.md generated vendored Normal file
View File

@ -0,0 +1,96 @@
# gliderlabs/ssh
[![GoDoc](https://godoc.org/github.com/gliderlabs/ssh?status.svg)](https://godoc.org/github.com/gliderlabs/ssh)
[![CircleCI](https://img.shields.io/circleci/project/github/gliderlabs/ssh.svg)](https://circleci.com/gh/gliderlabs/ssh)
[![Go Report Card](https://goreportcard.com/badge/github.com/gliderlabs/ssh)](https://goreportcard.com/report/github.com/gliderlabs/ssh)
[![OpenCollective](https://opencollective.com/ssh/sponsors/badge.svg)](#sponsors)
[![Slack](http://slack.gliderlabs.com/badge.svg)](http://slack.gliderlabs.com)
[![Email Updates](https://img.shields.io/badge/updates-subscribe-yellow.svg)](https://app.convertkit.com/landing_pages/243312)
> The Glider Labs SSH server package is dope. &mdash;[@bradfitz](https://twitter.com/bradfitz), Go team member
This Go package wraps the [crypto/ssh
package](https://godoc.org/golang.org/x/crypto/ssh) with a higher-level API for
building SSH servers. The goal of the API was to make it as simple as using
[net/http](https://golang.org/pkg/net/http/), so the API is very similar:
```go
package main
import (
"github.com/gliderlabs/ssh"
"io"
"log"
)
func main() {
ssh.Handle(func(s ssh.Session) {
io.WriteString(s, "Hello world\n")
})
log.Fatal(ssh.ListenAndServe(":2222", nil))
}
```
This package was built by [@progrium](https://twitter.com/progrium) after working on nearly a dozen projects at Glider Labs using SSH and collaborating with [@shazow](https://twitter.com/shazow) (known for [ssh-chat](https://github.com/shazow/ssh-chat)).
## Examples
A bunch of great examples are in the `_examples` directory.
## Usage
[See GoDoc reference.](https://godoc.org/github.com/gliderlabs/ssh)
## Contributing
Pull requests are welcome! However, since this project is very much about API
design, please submit API changes as issues to discuss before submitting PRs.
Also, you can [join our Slack](http://slack.gliderlabs.com) to discuss as well.
## Roadmap
* Non-session channel handlers
* Cleanup callback API
* 1.0 release
* High-level client?
## Sponsors
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/ssh#sponsor)]
<a href="https://opencollective.com/ssh/sponsor/0/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/1/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/2/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/3/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/4/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/5/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/6/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/7/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/8/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/9/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/9/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/10/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/10/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/11/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/11/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/12/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/12/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/13/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/13/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/14/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/14/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/15/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/15/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/16/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/16/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/17/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/17/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/18/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/18/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/19/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/19/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/20/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/20/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/21/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/21/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/22/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/22/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/23/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/23/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/24/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/24/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/25/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/25/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/26/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/26/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/27/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/27/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/28/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/28/avatar.svg"></a>
<a href="https://opencollective.com/ssh/sponsor/29/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/29/avatar.svg"></a>
## License
[BSD](LICENSE)

83
vendor/github.com/charmbracelet/ssh/agent.go generated vendored Normal file
View File

@ -0,0 +1,83 @@
package ssh
import (
"io"
"io/ioutil"
"net"
"path"
"sync"
gossh "golang.org/x/crypto/ssh"
)
const (
agentRequestType = "auth-agent-req@openssh.com"
agentChannelType = "auth-agent@openssh.com"
agentTempDir = "auth-agent"
agentListenFile = "listener.sock"
)
// contextKeyAgentRequest is an internal context key for storing if the
// client requested agent forwarding
var contextKeyAgentRequest = &contextKey{"auth-agent-req"}
// SetAgentRequested sets up the session context so that AgentRequested
// returns true.
func SetAgentRequested(ctx Context) {
ctx.SetValue(contextKeyAgentRequest, true)
}
// AgentRequested returns true if the client requested agent forwarding.
func AgentRequested(sess Session) bool {
return sess.Context().Value(contextKeyAgentRequest) == true
}
// NewAgentListener sets up a temporary Unix socket that can be communicated
// to the session environment and used for forwarding connections.
func NewAgentListener() (net.Listener, error) {
dir, err := ioutil.TempDir("", agentTempDir)
if err != nil {
return nil, err
}
l, err := net.Listen("unix", path.Join(dir, agentListenFile))
if err != nil {
return nil, err
}
return l, nil
}
// ForwardAgentConnections takes connections from a listener to proxy into the
// session on the OpenSSH channel for agent connections. It blocks and services
// connections until the listener stop accepting.
func ForwardAgentConnections(l net.Listener, s Session) {
sshConn := s.Context().Value(ContextKeyConn).(gossh.Conn)
for {
conn, err := l.Accept()
if err != nil {
return
}
go func(conn net.Conn) {
defer conn.Close()
channel, reqs, err := sshConn.OpenChannel(agentChannelType, nil)
if err != nil {
return
}
defer channel.Close()
go gossh.DiscardRequests(reqs)
var wg sync.WaitGroup
wg.Add(2)
go func() {
io.Copy(conn, channel)
conn.(*net.UnixConn).CloseWrite()
wg.Done()
}()
go func() {
io.Copy(channel, conn)
channel.CloseWrite()
wg.Done()
}()
wg.Wait()
}(conn)
}
}

26
vendor/github.com/charmbracelet/ssh/circle.yml generated vendored Normal file
View File

@ -0,0 +1,26 @@
version: 2
jobs:
build-go-latest:
docker:
- image: golang:latest
working_directory: /go/src/github.com/gliderlabs/ssh
steps:
- checkout
- run: go get
- run: go test -v -race
build-go-1.13:
docker:
- image: golang:1.13
working_directory: /go/src/github.com/gliderlabs/ssh
steps:
- checkout
- run: go get
- run: go test -v -race
workflows:
version: 2
build:
jobs:
- build-go-latest
- build-go-1.13

55
vendor/github.com/charmbracelet/ssh/conn.go generated vendored Normal file
View File

@ -0,0 +1,55 @@
package ssh
import (
"context"
"net"
"time"
)
type serverConn struct {
net.Conn
idleTimeout time.Duration
maxDeadline time.Time
closeCanceler context.CancelFunc
}
func (c *serverConn) Write(p []byte) (n int, err error) {
c.updateDeadline()
n, err = c.Conn.Write(p)
if _, isNetErr := err.(net.Error); isNetErr && c.closeCanceler != nil {
c.closeCanceler()
}
return
}
func (c *serverConn) Read(b []byte) (n int, err error) {
c.updateDeadline()
n, err = c.Conn.Read(b)
if _, isNetErr := err.(net.Error); isNetErr && c.closeCanceler != nil {
c.closeCanceler()
}
return
}
func (c *serverConn) Close() (err error) {
err = c.Conn.Close()
if c.closeCanceler != nil {
c.closeCanceler()
}
return
}
func (c *serverConn) updateDeadline() {
switch {
case c.idleTimeout > 0:
idleDeadline := time.Now().Add(c.idleTimeout)
if idleDeadline.Unix() < c.maxDeadline.Unix() || c.maxDeadline.IsZero() {
c.Conn.SetDeadline(idleDeadline)
return
}
fallthrough
default:
c.Conn.SetDeadline(c.maxDeadline)
}
}

155
vendor/github.com/charmbracelet/ssh/context.go generated vendored Normal file
View File

@ -0,0 +1,155 @@
package ssh
import (
"context"
"encoding/hex"
"net"
"sync"
gossh "golang.org/x/crypto/ssh"
)
// contextKey is a value for use with context.WithValue. It's used as
// a pointer so it fits in an interface{} without allocation.
type contextKey struct {
name string
}
var (
// ContextKeyUser is a context key for use with Contexts in this package.
// The associated value will be of type string.
ContextKeyUser = &contextKey{"user"}
// ContextKeySessionID is a context key for use with Contexts in this package.
// The associated value will be of type string.
ContextKeySessionID = &contextKey{"session-id"}
// ContextKeyPermissions is a context key for use with Contexts in this package.
// The associated value will be of type *Permissions.
ContextKeyPermissions = &contextKey{"permissions"}
// ContextKeyClientVersion is a context key for use with Contexts in this package.
// The associated value will be of type string.
ContextKeyClientVersion = &contextKey{"client-version"}
// ContextKeyServerVersion is a context key for use with Contexts in this package.
// The associated value will be of type string.
ContextKeyServerVersion = &contextKey{"server-version"}
// ContextKeyLocalAddr is a context key for use with Contexts in this package.
// The associated value will be of type net.Addr.
ContextKeyLocalAddr = &contextKey{"local-addr"}
// ContextKeyRemoteAddr is a context key for use with Contexts in this package.
// The associated value will be of type net.Addr.
ContextKeyRemoteAddr = &contextKey{"remote-addr"}
// ContextKeyServer is a context key for use with Contexts in this package.
// The associated value will be of type *Server.
ContextKeyServer = &contextKey{"ssh-server"}
// ContextKeyConn is a context key for use with Contexts in this package.
// The associated value will be of type gossh.ServerConn.
ContextKeyConn = &contextKey{"ssh-conn"}
// ContextKeyPublicKey is a context key for use with Contexts in this package.
// The associated value will be of type PublicKey.
ContextKeyPublicKey = &contextKey{"public-key"}
)
// Context is a package specific context interface. It exposes connection
// metadata and allows new values to be easily written to it. It's used in
// authentication handlers and callbacks, and its underlying context.Context is
// exposed on Session in the session Handler. A connection-scoped lock is also
// embedded in the context to make it easier to limit operations per-connection.
type Context interface {
context.Context
sync.Locker
// User returns the username used when establishing the SSH connection.
User() string
// SessionID returns the session hash.
SessionID() string
// ClientVersion returns the version reported by the client.
ClientVersion() string
// ServerVersion returns the version reported by the server.
ServerVersion() string
// RemoteAddr returns the remote address for this connection.
RemoteAddr() net.Addr
// LocalAddr returns the local address for this connection.
LocalAddr() net.Addr
// Permissions returns the Permissions object used for this connection.
Permissions() *Permissions
// SetValue allows you to easily write new values into the underlying context.
SetValue(key, value interface{})
}
type sshContext struct {
context.Context
*sync.Mutex
}
func newContext(srv *Server) (*sshContext, context.CancelFunc) {
innerCtx, cancel := context.WithCancel(context.Background())
ctx := &sshContext{innerCtx, &sync.Mutex{}}
ctx.SetValue(ContextKeyServer, srv)
perms := &Permissions{&gossh.Permissions{}}
ctx.SetValue(ContextKeyPermissions, perms)
return ctx, cancel
}
// this is separate from newContext because we will get ConnMetadata
// at different points so it needs to be applied separately
func applyConnMetadata(ctx Context, conn gossh.ConnMetadata) {
if ctx.Value(ContextKeySessionID) != nil {
return
}
ctx.SetValue(ContextKeySessionID, hex.EncodeToString(conn.SessionID()))
ctx.SetValue(ContextKeyClientVersion, string(conn.ClientVersion()))
ctx.SetValue(ContextKeyServerVersion, string(conn.ServerVersion()))
ctx.SetValue(ContextKeyUser, conn.User())
ctx.SetValue(ContextKeyLocalAddr, conn.LocalAddr())
ctx.SetValue(ContextKeyRemoteAddr, conn.RemoteAddr())
}
func (ctx *sshContext) SetValue(key, value interface{}) {
ctx.Context = context.WithValue(ctx.Context, key, value)
}
func (ctx *sshContext) User() string {
return ctx.Value(ContextKeyUser).(string)
}
func (ctx *sshContext) SessionID() string {
return ctx.Value(ContextKeySessionID).(string)
}
func (ctx *sshContext) ClientVersion() string {
return ctx.Value(ContextKeyClientVersion).(string)
}
func (ctx *sshContext) ServerVersion() string {
return ctx.Value(ContextKeyServerVersion).(string)
}
func (ctx *sshContext) RemoteAddr() net.Addr {
if addr, ok := ctx.Value(ContextKeyRemoteAddr).(net.Addr); ok {
return addr
}
return nil
}
func (ctx *sshContext) LocalAddr() net.Addr {
return ctx.Value(ContextKeyLocalAddr).(net.Addr)
}
func (ctx *sshContext) Permissions() *Permissions {
return ctx.Value(ContextKeyPermissions).(*Permissions)
}

45
vendor/github.com/charmbracelet/ssh/doc.go generated vendored Normal file
View File

@ -0,0 +1,45 @@
/*
Package ssh wraps the crypto/ssh package with a higher-level API for building
SSH servers. The goal of the API was to make it as simple as using net/http, so
the API is very similar.
You should be able to build any SSH server using only this package, which wraps
relevant types and some functions from crypto/ssh. However, you still need to
use crypto/ssh for building SSH clients.
ListenAndServe starts an SSH server with a given address, handler, and options. The
handler is usually nil, which means to use DefaultHandler. Handle sets DefaultHandler:
ssh.Handle(func(s ssh.Session) {
io.WriteString(s, "Hello world\n")
})
log.Fatal(ssh.ListenAndServe(":2222", nil))
If you don't specify a host key, it will generate one every time. This is convenient
except you'll have to deal with clients being confused that the host key is different.
It's a better idea to generate or point to an existing key on your system:
log.Fatal(ssh.ListenAndServe(":2222", nil, ssh.HostKeyFile("/Users/progrium/.ssh/id_rsa")))
Although all options have functional option helpers, another way to control the
server's behavior is by creating a custom Server:
s := &ssh.Server{
Addr: ":2222",
Handler: sessionHandler,
PublicKeyHandler: authHandler,
}
s.AddHostKey(hostKeySigner)
log.Fatal(s.ListenAndServe())
This package automatically handles basic SSH requests like setting environment
variables, requesting PTY, and changing window size. These requests are
processed, responded to, and any relevant state is updated. This state is then
exposed to you via the Session interface.
The one big feature missing from the Session abstraction is signals. This was
started, but not completed. Pull Requests welcome!
*/
package ssh

84
vendor/github.com/charmbracelet/ssh/options.go generated vendored Normal file
View File

@ -0,0 +1,84 @@
package ssh
import (
"io/ioutil"
gossh "golang.org/x/crypto/ssh"
)
// PasswordAuth returns a functional option that sets PasswordHandler on the server.
func PasswordAuth(fn PasswordHandler) Option {
return func(srv *Server) error {
srv.PasswordHandler = fn
return nil
}
}
// PublicKeyAuth returns a functional option that sets PublicKeyHandler on the server.
func PublicKeyAuth(fn PublicKeyHandler) Option {
return func(srv *Server) error {
srv.PublicKeyHandler = fn
return nil
}
}
// HostKeyFile returns a functional option that adds HostSigners to the server
// from a PEM file at filepath.
func HostKeyFile(filepath string) Option {
return func(srv *Server) error {
pemBytes, err := ioutil.ReadFile(filepath)
if err != nil {
return err
}
signer, err := gossh.ParsePrivateKey(pemBytes)
if err != nil {
return err
}
srv.AddHostKey(signer)
return nil
}
}
func KeyboardInteractiveAuth(fn KeyboardInteractiveHandler) Option {
return func(srv *Server) error {
srv.KeyboardInteractiveHandler = fn
return nil
}
}
// HostKeyPEM returns a functional option that adds HostSigners to the server
// from a PEM file as bytes.
func HostKeyPEM(bytes []byte) Option {
return func(srv *Server) error {
signer, err := gossh.ParsePrivateKey(bytes)
if err != nil {
return err
}
srv.AddHostKey(signer)
return nil
}
}
// NoPty returns a functional option that sets PtyCallback to return false,
// denying PTY requests.
func NoPty() Option {
return func(srv *Server) error {
srv.PtyCallback = func(ctx Context, pty Pty) bool {
return false
}
return nil
}
}
// WrapConn returns a functional option that sets ConnCallback on the server.
func WrapConn(fn ConnCallback) Option {
return func(srv *Server) error {
srv.ConnCallback = fn
return nil
}
}

57
vendor/github.com/charmbracelet/ssh/pty.go generated vendored Normal file
View File

@ -0,0 +1,57 @@
package ssh
import (
"bytes"
"io"
)
// NewPtyWriter creates a writer that handles when the session has a active
// PTY, replacing the \n with \r\n.
func NewPtyWriter(w io.Writer) io.Writer {
return ptyWriter{
w: w,
}
}
var _ io.Writer = ptyWriter{}
type ptyWriter struct {
w io.Writer
}
func (w ptyWriter) Write(p []byte) (int, error) {
m := len(p)
// normalize \n to \r\n when pty is accepted.
// this is a hardcoded shortcut since we don't support terminal modes.
p = bytes.Replace(p, []byte{'\n'}, []byte{'\r', '\n'}, -1)
p = bytes.Replace(p, []byte{'\r', '\r', '\n'}, []byte{'\r', '\n'}, -1)
n, err := w.w.Write(p)
if n > m {
n = m
}
return n, err
}
// NewPtyReadWriter return an io.ReadWriter that delegates the read to the
// given io.ReadWriter, and the writes to a ptyWriter.
func NewPtyReadWriter(rw io.ReadWriter) io.ReadWriter {
return readWriterDelegate{
w: NewPtyWriter(rw),
r: rw,
}
}
var _ io.ReadWriter = readWriterDelegate{}
type readWriterDelegate struct {
w io.Writer
r io.Reader
}
func (rw readWriterDelegate) Read(p []byte) (n int, err error) {
return rw.r.Read(p)
}
func (rw readWriterDelegate) Write(p []byte) (n int, err error) {
return rw.w.Write(p)
}

449
vendor/github.com/charmbracelet/ssh/server.go generated vendored Normal file
View File

@ -0,0 +1,449 @@
package ssh
import (
"context"
"errors"
"fmt"
"net"
"sync"
"time"
gossh "golang.org/x/crypto/ssh"
)
// ErrServerClosed is returned by the Server's Serve, ListenAndServe,
// and ListenAndServeTLS methods after a call to Shutdown or Close.
var ErrServerClosed = errors.New("ssh: Server closed")
type SubsystemHandler func(s Session)
var DefaultSubsystemHandlers = map[string]SubsystemHandler{}
type RequestHandler func(ctx Context, srv *Server, req *gossh.Request) (ok bool, payload []byte)
var DefaultRequestHandlers = map[string]RequestHandler{}
type ChannelHandler func(srv *Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx Context)
var DefaultChannelHandlers = map[string]ChannelHandler{
"session": DefaultSessionHandler,
}
// Server defines parameters for running an SSH server. The zero value for
// Server is a valid configuration. When both PasswordHandler and
// PublicKeyHandler are nil, no client authentication is performed.
type Server struct {
Addr string // TCP address to listen on, ":22" if empty
Handler Handler // handler to invoke, ssh.DefaultHandler if nil
HostSigners []Signer // private keys for the host key, must have at least one
Version string // server version to be sent before the initial handshake
KeyboardInteractiveHandler KeyboardInteractiveHandler // keyboard-interactive authentication handler
PasswordHandler PasswordHandler // password authentication handler
PublicKeyHandler PublicKeyHandler // public key authentication handler
PtyCallback PtyCallback // callback for allowing PTY sessions, allows all if nil
ConnCallback ConnCallback // optional callback for wrapping net.Conn before handling
LocalPortForwardingCallback LocalPortForwardingCallback // callback for allowing local port forwarding, denies all if nil
ReversePortForwardingCallback ReversePortForwardingCallback // callback for allowing reverse port forwarding, denies all if nil
ServerConfigCallback ServerConfigCallback // callback for configuring detailed SSH options
SessionRequestCallback SessionRequestCallback // callback for allowing or denying SSH sessions
ConnectionFailedCallback ConnectionFailedCallback // callback to report connection failures
IdleTimeout time.Duration // connection timeout when no activity, none if empty
MaxTimeout time.Duration // absolute connection timeout, none if empty
// ChannelHandlers allow overriding the built-in session handlers or provide
// extensions to the protocol, such as tcpip forwarding. By default only the
// "session" handler is enabled.
ChannelHandlers map[string]ChannelHandler
// RequestHandlers allow overriding the server-level request handlers or
// provide extensions to the protocol, such as tcpip forwarding. By default
// no handlers are enabled.
RequestHandlers map[string]RequestHandler
// SubsystemHandlers are handlers which are similar to the usual SSH command
// handlers, but handle named subsystems.
SubsystemHandlers map[string]SubsystemHandler
listenerWg sync.WaitGroup
mu sync.RWMutex
listeners map[net.Listener]struct{}
conns map[*gossh.ServerConn]struct{}
connWg sync.WaitGroup
doneChan chan struct{}
}
func (srv *Server) ensureHostSigner() error {
srv.mu.Lock()
defer srv.mu.Unlock()
if len(srv.HostSigners) == 0 {
signer, err := generateSigner()
if err != nil {
return err
}
srv.HostSigners = append(srv.HostSigners, signer)
}
return nil
}
func (srv *Server) ensureHandlers() {
srv.mu.Lock()
defer srv.mu.Unlock()
if srv.RequestHandlers == nil {
srv.RequestHandlers = map[string]RequestHandler{}
for k, v := range DefaultRequestHandlers {
srv.RequestHandlers[k] = v
}
}
if srv.ChannelHandlers == nil {
srv.ChannelHandlers = map[string]ChannelHandler{}
for k, v := range DefaultChannelHandlers {
srv.ChannelHandlers[k] = v
}
}
if srv.SubsystemHandlers == nil {
srv.SubsystemHandlers = map[string]SubsystemHandler{}
for k, v := range DefaultSubsystemHandlers {
srv.SubsystemHandlers[k] = v
}
}
}
func (srv *Server) config(ctx Context) *gossh.ServerConfig {
srv.mu.RLock()
defer srv.mu.RUnlock()
var config *gossh.ServerConfig
if srv.ServerConfigCallback == nil {
config = &gossh.ServerConfig{}
} else {
config = srv.ServerConfigCallback(ctx)
}
for _, signer := range srv.HostSigners {
config.AddHostKey(signer)
}
if srv.PasswordHandler == nil && srv.PublicKeyHandler == nil && srv.KeyboardInteractiveHandler == nil {
config.NoClientAuth = true
}
if srv.Version != "" {
config.ServerVersion = "SSH-2.0-" + srv.Version
}
if srv.PasswordHandler != nil {
config.PasswordCallback = func(conn gossh.ConnMetadata, password []byte) (*gossh.Permissions, error) {
applyConnMetadata(ctx, conn)
if ok := srv.PasswordHandler(ctx, string(password)); !ok {
return ctx.Permissions().Permissions, fmt.Errorf("permission denied")
}
return ctx.Permissions().Permissions, nil
}
}
if srv.PublicKeyHandler != nil {
config.PublicKeyCallback = func(conn gossh.ConnMetadata, key gossh.PublicKey) (*gossh.Permissions, error) {
applyConnMetadata(ctx, conn)
if ok := srv.PublicKeyHandler(ctx, key); !ok {
return ctx.Permissions().Permissions, fmt.Errorf("permission denied")
}
ctx.SetValue(ContextKeyPublicKey, key)
return ctx.Permissions().Permissions, nil
}
}
if srv.KeyboardInteractiveHandler != nil {
config.KeyboardInteractiveCallback = func(conn gossh.ConnMetadata, challenger gossh.KeyboardInteractiveChallenge) (*gossh.Permissions, error) {
applyConnMetadata(ctx, conn)
if ok := srv.KeyboardInteractiveHandler(ctx, challenger); !ok {
return ctx.Permissions().Permissions, fmt.Errorf("permission denied")
}
return ctx.Permissions().Permissions, nil
}
}
return config
}
// Handle sets the Handler for the server.
func (srv *Server) Handle(fn Handler) {
srv.mu.Lock()
defer srv.mu.Unlock()
srv.Handler = fn
}
// Close immediately closes all active listeners and all active
// connections.
//
// Close returns any error returned from closing the Server's
// underlying Listener(s).
func (srv *Server) Close() error {
srv.mu.Lock()
defer srv.mu.Unlock()
srv.closeDoneChanLocked()
err := srv.closeListenersLocked()
for c := range srv.conns {
c.Close()
delete(srv.conns, c)
}
return err
}
// Shutdown gracefully shuts down the server without interrupting any
// active connections. Shutdown works by first closing all open
// listeners, and then waiting indefinitely for connections to close.
// If the provided context expires before the shutdown is complete,
// then the context's error is returned.
func (srv *Server) Shutdown(ctx context.Context) error {
srv.mu.Lock()
lnerr := srv.closeListenersLocked()
srv.closeDoneChanLocked()
srv.mu.Unlock()
finished := make(chan struct{}, 1)
go func() {
srv.listenerWg.Wait()
srv.connWg.Wait()
finished <- struct{}{}
}()
select {
case <-ctx.Done():
return ctx.Err()
case <-finished:
return lnerr
}
}
// Serve accepts incoming connections on the Listener l, creating a new
// connection goroutine for each. The connection goroutines read requests and then
// calls srv.Handler to handle sessions.
//
// Serve always returns a non-nil error.
func (srv *Server) Serve(l net.Listener) error {
srv.ensureHandlers()
defer l.Close()
if err := srv.ensureHostSigner(); err != nil {
return err
}
if srv.Handler == nil {
srv.Handler = DefaultHandler
}
var tempDelay time.Duration
srv.trackListener(l, true)
defer srv.trackListener(l, false)
for {
conn, e := l.Accept()
if e != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
if ne, ok := e.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
time.Sleep(tempDelay)
continue
}
return e
}
go srv.HandleConn(conn)
}
}
func (srv *Server) HandleConn(newConn net.Conn) {
ctx, cancel := newContext(srv)
if srv.ConnCallback != nil {
cbConn := srv.ConnCallback(ctx, newConn)
if cbConn == nil {
newConn.Close()
return
}
newConn = cbConn
}
conn := &serverConn{
Conn: newConn,
idleTimeout: srv.IdleTimeout,
closeCanceler: cancel,
}
if srv.MaxTimeout > 0 {
conn.maxDeadline = time.Now().Add(srv.MaxTimeout)
}
defer conn.Close()
sshConn, chans, reqs, err := gossh.NewServerConn(conn, srv.config(ctx))
if err != nil {
if srv.ConnectionFailedCallback != nil {
srv.ConnectionFailedCallback(conn, err)
}
return
}
srv.trackConn(sshConn, true)
defer srv.trackConn(sshConn, false)
ctx.SetValue(ContextKeyConn, sshConn)
applyConnMetadata(ctx, sshConn)
//go gossh.DiscardRequests(reqs)
go srv.handleRequests(ctx, reqs)
for ch := range chans {
handler := srv.ChannelHandlers[ch.ChannelType()]
if handler == nil {
handler = srv.ChannelHandlers["default"]
}
if handler == nil {
ch.Reject(gossh.UnknownChannelType, "unsupported channel type")
continue
}
go handler(srv, sshConn, ch, ctx)
}
}
func (srv *Server) handleRequests(ctx Context, in <-chan *gossh.Request) {
for req := range in {
handler := srv.RequestHandlers[req.Type]
if handler == nil {
handler = srv.RequestHandlers["default"]
}
if handler == nil {
req.Reply(false, nil)
continue
}
/*reqCtx, cancel := context.WithCancel(ctx)
defer cancel() */
ret, payload := handler(ctx, srv, req)
req.Reply(ret, payload)
}
}
// ListenAndServe listens on the TCP network address srv.Addr and then calls
// Serve to handle incoming connections. If srv.Addr is blank, ":22" is used.
// ListenAndServe always returns a non-nil error.
func (srv *Server) ListenAndServe() error {
addr := srv.Addr
if addr == "" {
addr = ":22"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(ln)
}
// AddHostKey adds a private key as a host key. If an existing host key exists
// with the same algorithm, it is overwritten. Each server config must have at
// least one host key.
func (srv *Server) AddHostKey(key Signer) {
srv.mu.Lock()
defer srv.mu.Unlock()
// these are later added via AddHostKey on ServerConfig, which performs the
// check for one of every algorithm.
// This check is based on the AddHostKey method from the x/crypto/ssh
// library. This allows us to only keep one active key for each type on a
// server at once. So, if you're dynamically updating keys at runtime, this
// list will not keep growing.
for i, k := range srv.HostSigners {
if k.PublicKey().Type() == key.PublicKey().Type() {
srv.HostSigners[i] = key
return
}
}
srv.HostSigners = append(srv.HostSigners, key)
}
// SetOption runs a functional option against the server.
func (srv *Server) SetOption(option Option) error {
// NOTE: there is a potential race here for any option that doesn't call an
// internal method. We can't actually lock here because if something calls
// (as an example) AddHostKey, it will deadlock.
//srv.mu.Lock()
//defer srv.mu.Unlock()
return option(srv)
}
func (srv *Server) getDoneChan() <-chan struct{} {
srv.mu.Lock()
defer srv.mu.Unlock()
return srv.getDoneChanLocked()
}
func (srv *Server) getDoneChanLocked() chan struct{} {
if srv.doneChan == nil {
srv.doneChan = make(chan struct{})
}
return srv.doneChan
}
func (srv *Server) closeDoneChanLocked() {
ch := srv.getDoneChanLocked()
select {
case <-ch:
// Already closed. Don't close again.
default:
// Safe to close here. We're the only closer, guarded
// by srv.mu.
close(ch)
}
}
func (srv *Server) closeListenersLocked() error {
var err error
for ln := range srv.listeners {
if cerr := ln.Close(); cerr != nil && err == nil {
err = cerr
}
delete(srv.listeners, ln)
}
return err
}
func (srv *Server) trackListener(ln net.Listener, add bool) {
srv.mu.Lock()
defer srv.mu.Unlock()
if srv.listeners == nil {
srv.listeners = make(map[net.Listener]struct{})
}
if add {
// If the *Server is being reused after a previous
// Close or Shutdown, reset its doneChan:
if len(srv.listeners) == 0 && len(srv.conns) == 0 {
srv.doneChan = nil
}
srv.listeners[ln] = struct{}{}
srv.listenerWg.Add(1)
} else {
delete(srv.listeners, ln)
srv.listenerWg.Done()
}
}
func (srv *Server) trackConn(c *gossh.ServerConn, add bool) {
srv.mu.Lock()
defer srv.mu.Unlock()
if srv.conns == nil {
srv.conns = make(map[*gossh.ServerConn]struct{})
}
if add {
srv.conns[c] = struct{}{}
srv.connWg.Add(1)
} else {
delete(srv.conns, c)
srv.connWg.Done()
}
}

371
vendor/github.com/charmbracelet/ssh/session.go generated vendored Normal file
View File

@ -0,0 +1,371 @@
package ssh
import (
"errors"
"fmt"
"io"
"net"
"sync"
"github.com/anmitsu/go-shlex"
gossh "golang.org/x/crypto/ssh"
)
// Session provides access to information about an SSH session and methods
// to read and write to the SSH channel with an embedded Channel interface from
// crypto/ssh.
//
// When Command() returns an empty slice, the user requested a shell. Otherwise
// the user is performing an exec with those command arguments.
//
// TODO: Signals
type Session interface {
gossh.Channel
// User returns the username used when establishing the SSH connection.
User() string
// RemoteAddr returns the net.Addr of the client side of the connection.
RemoteAddr() net.Addr
// LocalAddr returns the net.Addr of the server side of the connection.
LocalAddr() net.Addr
// Environ returns a copy of strings representing the environment set by the
// user for this session, in the form "key=value".
Environ() []string
// Exit sends an exit status and then closes the session.
Exit(code int) error
// Command returns a shell parsed slice of arguments that were provided by the
// user. Shell parsing splits the command string according to POSIX shell rules,
// which considers quoting not just whitespace.
Command() []string
// RawCommand returns the exact command that was provided by the user.
RawCommand() string
// Subsystem returns the subsystem requested by the user.
Subsystem() string
// PublicKey returns the PublicKey used to authenticate. If a public key was not
// used it will return nil.
PublicKey() PublicKey
// Context returns the connection's context. The returned context is always
// non-nil and holds the same data as the Context passed into auth
// handlers and callbacks.
//
// The context is canceled when the client's connection closes or I/O
// operation fails.
Context() Context
// Permissions returns a copy of the Permissions object that was available for
// setup in the auth handlers via the Context.
Permissions() Permissions
// Pty returns PTY information, a channel of window size changes, and a boolean
// of whether or not a PTY was accepted for this session.
Pty() (Pty, <-chan Window, bool)
// Signals registers a channel to receive signals sent from the client. The
// channel must handle signal sends or it will block the SSH request loop.
// Registering nil will unregister the channel from signal sends. During the
// time no channel is registered signals are buffered up to a reasonable amount.
// If there are buffered signals when a channel is registered, they will be
// sent in order on the channel immediately after registering.
Signals(c chan<- Signal)
// Break regisers a channel to receive notifications of break requests sent
// from the client. The channel must handle break requests, or it will block
// the request handling loop. Registering nil will unregister the channel.
// During the time that no channel is registered, breaks are ignored.
Break(c chan<- bool)
}
// maxSigBufSize is how many signals will be buffered
// when there is no signal channel specified
const maxSigBufSize = 128
func DefaultSessionHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx Context) {
ch, reqs, err := newChan.Accept()
if err != nil {
// TODO: trigger event callback
return
}
sess := &session{
Channel: ch,
conn: conn,
handler: srv.Handler,
ptyCb: srv.PtyCallback,
sessReqCb: srv.SessionRequestCallback,
subsystemHandlers: srv.SubsystemHandlers,
ctx: ctx,
}
sess.handleRequests(reqs)
}
type session struct {
sync.Mutex
gossh.Channel
conn *gossh.ServerConn
handler Handler
subsystemHandlers map[string]SubsystemHandler
handled bool
exited bool
pty *Pty
winch chan Window
env []string
ptyCb PtyCallback
sessReqCb SessionRequestCallback
rawCmd string
subsystem string
ctx Context
sigCh chan<- Signal
sigBuf []Signal
breakCh chan<- bool
}
func (sess *session) Stderr() io.ReadWriter {
if sess.pty != nil {
return NewPtyReadWriter(sess.Channel.Stderr())
}
return sess.Channel.Stderr()
}
func (sess *session) Write(p []byte) (int, error) {
if sess.pty != nil {
return NewPtyWriter(sess.Channel).Write(p)
}
return sess.Channel.Write(p)
}
func (sess *session) PublicKey() PublicKey {
sessionkey := sess.ctx.Value(ContextKeyPublicKey)
if sessionkey == nil {
return nil
}
return sessionkey.(PublicKey)
}
func (sess *session) Permissions() Permissions {
// use context permissions because its properly
// wrapped and easier to dereference
perms := sess.ctx.Value(ContextKeyPermissions).(*Permissions)
return *perms
}
func (sess *session) Context() Context {
return sess.ctx
}
func (sess *session) Exit(code int) error {
sess.Lock()
defer sess.Unlock()
if sess.exited {
return errors.New("Session.Exit called multiple times")
}
sess.exited = true
status := struct{ Status uint32 }{uint32(code)}
_, err := sess.SendRequest("exit-status", false, gossh.Marshal(&status))
if err != nil {
return err
}
return sess.Close()
}
func (sess *session) User() string {
return sess.conn.User()
}
func (sess *session) RemoteAddr() net.Addr {
return sess.conn.RemoteAddr()
}
func (sess *session) LocalAddr() net.Addr {
return sess.conn.LocalAddr()
}
func (sess *session) Environ() []string {
return append([]string(nil), sess.env...)
}
func (sess *session) RawCommand() string {
return sess.rawCmd
}
func (sess *session) Command() []string {
cmd, _ := shlex.Split(sess.rawCmd, true)
return append([]string(nil), cmd...)
}
func (sess *session) Subsystem() string {
return sess.subsystem
}
func (sess *session) Pty() (Pty, <-chan Window, bool) {
if sess.pty != nil {
return *sess.pty, sess.winch, true
}
return Pty{}, sess.winch, false
}
func (sess *session) Signals(c chan<- Signal) {
sess.Lock()
defer sess.Unlock()
sess.sigCh = c
if len(sess.sigBuf) > 0 {
go func() {
for _, sig := range sess.sigBuf {
sess.sigCh <- sig
}
}()
}
}
func (sess *session) Break(c chan<- bool) {
sess.Lock()
defer sess.Unlock()
sess.breakCh = c
}
func (sess *session) handleRequests(reqs <-chan *gossh.Request) {
for req := range reqs {
switch req.Type {
case "shell", "exec":
if sess.handled {
req.Reply(false, nil)
continue
}
payload := struct{ Value string }{}
gossh.Unmarshal(req.Payload, &payload)
sess.rawCmd = payload.Value
// If there's a session policy callback, we need to confirm before
// accepting the session.
if sess.sessReqCb != nil && !sess.sessReqCb(sess, req.Type) {
sess.rawCmd = ""
req.Reply(false, nil)
continue
}
sess.handled = true
req.Reply(true, nil)
go func() {
sess.handler(sess)
sess.Exit(0)
}()
case "subsystem":
if sess.handled {
req.Reply(false, nil)
continue
}
payload := struct{ Value string }{}
gossh.Unmarshal(req.Payload, &payload)
sess.subsystem = payload.Value
// If there's a session policy callback, we need to confirm before
// accepting the session.
if sess.sessReqCb != nil && !sess.sessReqCb(sess, req.Type) {
sess.rawCmd = ""
req.Reply(false, nil)
continue
}
handler := sess.subsystemHandlers[payload.Value]
if handler == nil {
handler = sess.subsystemHandlers["default"]
}
if handler == nil {
req.Reply(false, nil)
continue
}
sess.handled = true
req.Reply(true, nil)
go func() {
handler(sess)
sess.Exit(0)
}()
case "env":
if sess.handled {
req.Reply(false, nil)
continue
}
var kv struct{ Key, Value string }
gossh.Unmarshal(req.Payload, &kv)
sess.env = append(sess.env, fmt.Sprintf("%s=%s", kv.Key, kv.Value))
req.Reply(true, nil)
case "signal":
var payload struct{ Signal string }
gossh.Unmarshal(req.Payload, &payload)
sess.Lock()
if sess.sigCh != nil {
sess.sigCh <- Signal(payload.Signal)
} else {
if len(sess.sigBuf) < maxSigBufSize {
sess.sigBuf = append(sess.sigBuf, Signal(payload.Signal))
}
}
sess.Unlock()
case "pty-req":
if sess.handled || sess.pty != nil {
req.Reply(false, nil)
continue
}
ptyReq, ok := parsePtyRequest(req.Payload)
if !ok {
req.Reply(false, nil)
continue
}
if sess.ptyCb != nil {
ok := sess.ptyCb(sess.ctx, ptyReq)
if !ok {
req.Reply(false, nil)
continue
}
}
sess.pty = &ptyReq
sess.winch = make(chan Window, 1)
sess.winch <- ptyReq.Window
defer func() {
// when reqs is closed
close(sess.winch)
}()
req.Reply(ok, nil)
case "window-change":
if sess.pty == nil {
req.Reply(false, nil)
continue
}
win, ok := parseWinchRequest(req.Payload)
if ok {
sess.pty.Window = win
sess.winch <- win
}
req.Reply(ok, nil)
case agentRequestType:
// TODO: option/callback to allow agent forwarding
SetAgentRequested(sess.ctx)
req.Reply(true, nil)
case "break":
ok := false
sess.Lock()
if sess.breakCh != nil {
sess.breakCh <- true
ok = true
}
req.Reply(ok, nil)
sess.Unlock()
default:
// TODO: debug log
req.Reply(false, nil)
}
}
}

127
vendor/github.com/charmbracelet/ssh/ssh.go generated vendored Normal file
View File

@ -0,0 +1,127 @@
package ssh
import (
"crypto/subtle"
"net"
gossh "golang.org/x/crypto/ssh"
)
type Signal string
// POSIX signals as listed in RFC 4254 Section 6.10.
const (
SIGABRT Signal = "ABRT"
SIGALRM Signal = "ALRM"
SIGFPE Signal = "FPE"
SIGHUP Signal = "HUP"
SIGILL Signal = "ILL"
SIGINT Signal = "INT"
SIGKILL Signal = "KILL"
SIGPIPE Signal = "PIPE"
SIGQUIT Signal = "QUIT"
SIGSEGV Signal = "SEGV"
SIGTERM Signal = "TERM"
SIGUSR1 Signal = "USR1"
SIGUSR2 Signal = "USR2"
)
// DefaultHandler is the default Handler used by Serve.
var DefaultHandler Handler
// Option is a functional option handler for Server.
type Option func(*Server) error
// Handler is a callback for handling established SSH sessions.
type Handler func(Session)
// PublicKeyHandler is a callback for performing public key authentication.
type PublicKeyHandler func(ctx Context, key PublicKey) bool
// PasswordHandler is a callback for performing password authentication.
type PasswordHandler func(ctx Context, password string) bool
// KeyboardInteractiveHandler is a callback for performing keyboard-interactive authentication.
type KeyboardInteractiveHandler func(ctx Context, challenger gossh.KeyboardInteractiveChallenge) bool
// PtyCallback is a hook for allowing PTY sessions.
type PtyCallback func(ctx Context, pty Pty) bool
// SessionRequestCallback is a callback for allowing or denying SSH sessions.
type SessionRequestCallback func(sess Session, requestType string) bool
// ConnCallback is a hook for new connections before handling.
// It allows wrapping for timeouts and limiting by returning
// the net.Conn that will be used as the underlying connection.
type ConnCallback func(ctx Context, conn net.Conn) net.Conn
// LocalPortForwardingCallback is a hook for allowing port forwarding
type LocalPortForwardingCallback func(ctx Context, destinationHost string, destinationPort uint32) bool
// ReversePortForwardingCallback is a hook for allowing reverse port forwarding
type ReversePortForwardingCallback func(ctx Context, bindHost string, bindPort uint32) bool
// ServerConfigCallback is a hook for creating custom default server configs
type ServerConfigCallback func(ctx Context) *gossh.ServerConfig
// ConnectionFailedCallback is a hook for reporting failed connections
// Please note: the net.Conn is likely to be closed at this point
type ConnectionFailedCallback func(conn net.Conn, err error)
// Window represents the size of a PTY window.
type Window struct {
Width int
Height int
}
// Pty represents a PTY request and configuration.
type Pty struct {
Term string
Window Window
// HELP WANTED: terminal modes!
}
// Serve accepts incoming SSH connections on the listener l, creating a new
// connection goroutine for each. The connection goroutines read requests and
// then calls handler to handle sessions. Handler is typically nil, in which
// case the DefaultHandler is used.
func Serve(l net.Listener, handler Handler, options ...Option) error {
srv := &Server{Handler: handler}
for _, option := range options {
if err := srv.SetOption(option); err != nil {
return err
}
}
return srv.Serve(l)
}
// ListenAndServe listens on the TCP network address addr and then calls Serve
// with handler to handle sessions on incoming connections. Handler is typically
// nil, in which case the DefaultHandler is used.
func ListenAndServe(addr string, handler Handler, options ...Option) error {
srv := &Server{Addr: addr, Handler: handler}
for _, option := range options {
if err := srv.SetOption(option); err != nil {
return err
}
}
return srv.ListenAndServe()
}
// Handle registers the handler as the DefaultHandler.
func Handle(handler Handler) {
DefaultHandler = handler
}
// KeysEqual is constant time compare of the keys to avoid timing attacks.
func KeysEqual(ak, bk PublicKey) bool {
//avoid panic if one of the keys is nil, return false instead
if ak == nil || bk == nil {
return false
}
a := ak.Marshal()
b := bk.Marshal()
return (len(a) == len(b) && subtle.ConstantTimeCompare(a, b) == 1)
}

193
vendor/github.com/charmbracelet/ssh/tcpip.go generated vendored Normal file
View File

@ -0,0 +1,193 @@
package ssh
import (
"io"
"log"
"net"
"strconv"
"sync"
gossh "golang.org/x/crypto/ssh"
)
const (
forwardedTCPChannelType = "forwarded-tcpip"
)
// direct-tcpip data struct as specified in RFC4254, Section 7.2
type localForwardChannelData struct {
DestAddr string
DestPort uint32
OriginAddr string
OriginPort uint32
}
// DirectTCPIPHandler can be enabled by adding it to the server's
// ChannelHandlers under direct-tcpip.
func DirectTCPIPHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx Context) {
d := localForwardChannelData{}
if err := gossh.Unmarshal(newChan.ExtraData(), &d); err != nil {
newChan.Reject(gossh.ConnectionFailed, "error parsing forward data: "+err.Error())
return
}
if srv.LocalPortForwardingCallback == nil || !srv.LocalPortForwardingCallback(ctx, d.DestAddr, d.DestPort) {
newChan.Reject(gossh.Prohibited, "port forwarding is disabled")
return
}
dest := net.JoinHostPort(d.DestAddr, strconv.FormatInt(int64(d.DestPort), 10))
var dialer net.Dialer
dconn, err := dialer.DialContext(ctx, "tcp", dest)
if err != nil {
newChan.Reject(gossh.ConnectionFailed, err.Error())
return
}
ch, reqs, err := newChan.Accept()
if err != nil {
dconn.Close()
return
}
go gossh.DiscardRequests(reqs)
go func() {
defer ch.Close()
defer dconn.Close()
io.Copy(ch, dconn)
}()
go func() {
defer ch.Close()
defer dconn.Close()
io.Copy(dconn, ch)
}()
}
type remoteForwardRequest struct {
BindAddr string
BindPort uint32
}
type remoteForwardSuccess struct {
BindPort uint32
}
type remoteForwardCancelRequest struct {
BindAddr string
BindPort uint32
}
type remoteForwardChannelData struct {
DestAddr string
DestPort uint32
OriginAddr string
OriginPort uint32
}
// ForwardedTCPHandler can be enabled by creating a ForwardedTCPHandler and
// adding the HandleSSHRequest callback to the server's RequestHandlers under
// tcpip-forward and cancel-tcpip-forward.
type ForwardedTCPHandler struct {
forwards map[string]net.Listener
sync.Mutex
}
func (h *ForwardedTCPHandler) HandleSSHRequest(ctx Context, srv *Server, req *gossh.Request) (bool, []byte) {
h.Lock()
if h.forwards == nil {
h.forwards = make(map[string]net.Listener)
}
h.Unlock()
conn := ctx.Value(ContextKeyConn).(*gossh.ServerConn)
switch req.Type {
case "tcpip-forward":
var reqPayload remoteForwardRequest
if err := gossh.Unmarshal(req.Payload, &reqPayload); err != nil {
// TODO: log parse failure
return false, []byte{}
}
if srv.ReversePortForwardingCallback == nil || !srv.ReversePortForwardingCallback(ctx, reqPayload.BindAddr, reqPayload.BindPort) {
return false, []byte("port forwarding is disabled")
}
addr := net.JoinHostPort(reqPayload.BindAddr, strconv.Itoa(int(reqPayload.BindPort)))
ln, err := net.Listen("tcp", addr)
if err != nil {
// TODO: log listen failure
return false, []byte{}
}
_, destPortStr, _ := net.SplitHostPort(ln.Addr().String())
destPort, _ := strconv.Atoi(destPortStr)
h.Lock()
h.forwards[addr] = ln
h.Unlock()
go func() {
<-ctx.Done()
h.Lock()
ln, ok := h.forwards[addr]
h.Unlock()
if ok {
ln.Close()
}
}()
go func() {
for {
c, err := ln.Accept()
if err != nil {
// TODO: log accept failure
break
}
originAddr, orignPortStr, _ := net.SplitHostPort(c.RemoteAddr().String())
originPort, _ := strconv.Atoi(orignPortStr)
payload := gossh.Marshal(&remoteForwardChannelData{
DestAddr: reqPayload.BindAddr,
DestPort: uint32(destPort),
OriginAddr: originAddr,
OriginPort: uint32(originPort),
})
go func() {
ch, reqs, err := conn.OpenChannel(forwardedTCPChannelType, payload)
if err != nil {
// TODO: log failure to open channel
log.Println(err)
c.Close()
return
}
go gossh.DiscardRequests(reqs)
go func() {
defer ch.Close()
defer c.Close()
io.Copy(ch, c)
}()
go func() {
defer ch.Close()
defer c.Close()
io.Copy(c, ch)
}()
}()
}
h.Lock()
delete(h.forwards, addr)
h.Unlock()
}()
return true, gossh.Marshal(&remoteForwardSuccess{uint32(destPort)})
case "cancel-tcpip-forward":
var reqPayload remoteForwardCancelRequest
if err := gossh.Unmarshal(req.Payload, &reqPayload); err != nil {
// TODO: log parse failure
return false, []byte{}
}
addr := net.JoinHostPort(reqPayload.BindAddr, strconv.Itoa(int(reqPayload.BindPort)))
h.Lock()
ln, ok := h.forwards[addr]
h.Unlock()
if ok {
ln.Close()
}
return true, nil
default:
return false, nil
}
}

83
vendor/github.com/charmbracelet/ssh/util.go generated vendored Normal file
View File

@ -0,0 +1,83 @@
package ssh
import (
"crypto/rand"
"crypto/rsa"
"encoding/binary"
"golang.org/x/crypto/ssh"
)
func generateSigner() (ssh.Signer, error) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
return ssh.NewSignerFromKey(key)
}
func parsePtyRequest(s []byte) (pty Pty, ok bool) {
term, s, ok := parseString(s)
if !ok {
return
}
width32, s, ok := parseUint32(s)
if !ok {
return
}
height32, _, ok := parseUint32(s)
if !ok {
return
}
pty = Pty{
Term: term,
Window: Window{
Width: int(width32),
Height: int(height32),
},
}
return
}
func parseWinchRequest(s []byte) (win Window, ok bool) {
width32, s, ok := parseUint32(s)
if width32 < 1 {
ok = false
}
if !ok {
return
}
height32, _, ok := parseUint32(s)
if height32 < 1 {
ok = false
}
if !ok {
return
}
win = Window{
Width: int(width32),
Height: int(height32),
}
return
}
func parseString(in []byte) (out string, rest []byte, ok bool) {
if len(in) < 4 {
return
}
length := binary.BigEndian.Uint32(in)
if uint32(len(in)) < 4+length {
return
}
out = string(in[4 : 4+length])
rest = in[4+length:]
ok = true
return
}
func parseUint32(in []byte) (uint32, []byte, bool) {
if len(in) < 4 {
return 0, nil, false
}
return binary.BigEndian.Uint32(in), in[4:], true
}

33
vendor/github.com/charmbracelet/ssh/wrap.go generated vendored Normal file
View File

@ -0,0 +1,33 @@
package ssh
import gossh "golang.org/x/crypto/ssh"
// PublicKey is an abstraction of different types of public keys.
type PublicKey interface {
gossh.PublicKey
}
// The Permissions type holds fine-grained permissions that are specific to a
// user or a specific authentication method for a user. Permissions, except for
// "source-address", must be enforced in the server application layer, after
// successful authentication.
type Permissions struct {
*gossh.Permissions
}
// A Signer can create signatures that verify against a public key.
type Signer interface {
gossh.Signer
}
// ParseAuthorizedKey parses a public key from an authorized_keys file used in
// OpenSSH according to the sshd(8) manual page.
func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []string, rest []byte, err error) {
return gossh.ParseAuthorizedKey(in)
}
// ParsePublicKey parses an SSH public key formatted for use in
// the SSH wire protocol according to RFC 4253, section 6.6.
func ParsePublicKey(in []byte) (out PublicKey, err error) {
return gossh.ParsePublicKey(in)
}

11
vendor/github.com/charmbracelet/wish/.gitignore generated vendored Normal file
View File

@ -0,0 +1,11 @@
examples/bubbletea/bubbletea
examples/bubbletea/.ssh
examples/git/git
examples/git/.ssh
examples/git/.repos
.repos
.ssh
coverage.txt
# MacOS specific
.DS_Store

34
vendor/github.com/charmbracelet/wish/.golangci.yml generated vendored Normal file
View File

@ -0,0 +1,34 @@
run:
tests: false
issues:
include:
- EXC0001
- EXC0005
- EXC0011
- EXC0012
- EXC0013
max-issues-per-linter: 0
max-same-issues: 0
linters:
enable:
- bodyclose
- dupl
- exportloopref
- goconst
- godot
- godox
- goimports
- goprintffuncname
- gosec
- ifshort
- misspell
- prealloc
- revive
- rowserrcheck
- sqlclosecheck
- unconvert
- unparam
- whitespace

8
vendor/github.com/charmbracelet/wish/.goreleaser.yml generated vendored Normal file
View File

@ -0,0 +1,8 @@
includes:
- from_url:
url: charmbracelet/meta/main/goreleaser-lib.yaml
- from_url:
url: charmbracelet/meta/main/goreleaser-announce.yaml
# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json

21
vendor/github.com/charmbracelet/wish/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019-2021 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.

142
vendor/github.com/charmbracelet/wish/README.md generated vendored Normal file
View File

@ -0,0 +1,142 @@
# Wish
<p>
<img style="width: 247px" src="https://stuff.charm.sh/wish/wish-header.png" alt="A nice rendering of a star, anthropomorphized somewhat by means of a smile, with the words Charm Wish next to it">
<br>
<a href="https://github.com/charmbracelet/wish/releases"><img src="https://img.shields.io/github/release/charmbracelet/wish.svg" alt="Latest Release"></a>
<a href="https://pkg.go.dev/github.com/charmbracelet/wish?tab=doc"><img src="https://godoc.org/github.com/golang/gddo?status.svg" alt="GoDoc"></a>
<a href="https://github.com/charmbracelet/wish/actions"><img src="https://github.com/charmbracelet/wish/workflows/Build/badge.svg" alt="Build Status"></a>
<a href="https://codecov.io/gh/charmbracelet/wish"><img alt="Codecov branch" src="https://img.shields.io/codecov/c/github/charmbracelet/wish/main.svg"></a>
<a href="https://goreportcard.com/report/github.com/charmbracelet/wish"><img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/charmbracelet/wish"></a>
</p>
Make SSH apps, just like that! 💫
SSH is an excellent platform to build remotely accessible applications on. It
offers secure communication without the hassle of HTTPS certificates, it has
user identification with SSH keys and it's accessible from anywhere with a
terminal. Powerful protocols like Git work over SSH and you can even render
TUIs directly over an SSH connection.
Wish is an SSH server with sensible defaults and a collection of middleware that
makes building SSH apps easy. Wish is built on [gliderlabs/ssh][gliderlabs/ssh]
and should be easy to integrate into any existing projects.
## What are SSH Apps?
Usually, when we think about SSH, we think about remote shell access into servers,
most commonly through `openssh-server`.
That's a perfectly valid and probably the most common use of SSH, but it can do so much more than that.
Just like HTTP, SMTP, FTP and others, SSH is a protocol!
It is a cryptographic network protocol for operating network services securely over an unsecured network. [^1]
[^1]: https://en.wikipedia.org/wiki/Secure_Shell
That means, among other things, that we can write custom SSH servers, without touching `openssh-server`,
so we can securely do more things than just providing a shell.
Wish is a library that helps writing these kind of apps using Go.
## Middleware
Wish middlewares are analogous to those in several HTTP frameworks.
They are essentially SSH handlers that you can use to do specific tasks,
and then call the next middleware.
Notice that middlewares are composed from first to last,
which means the last one is executed first.
### Bubble Tea
The [`bubbletea`](bubbletea) middleware makes it easy to serve any
[Bubble Tea][bubbletea] application over SSH. Each SSH session will get their own
`tea.Program` with the SSH pty input and output connected. Client window
dimension and resize messages are also natively handled by the `tea.Program`.
You can see a demo of the Wish middleware in action at: `ssh git.charm.sh`
### Git
The [`git`](git) middleware adds `git` server functionality to any ssh server.
It supports repo creation on initial push and custom public key based auth.
This middleware requires that `git` is installed on the server.
### Logging
The [`logging`](logging) middleware provides basic connection logging. Connects
are logged with the remote address, invoked command, TERM setting, window
dimensions and if the auth was public key based. Disconnect will log the remote
address and connection duration.
### Access Control
Not all applications will support general SSH connections. To restrict access
to supported methods, you can use the [`activeterm`](activeterm) middleware to
only allow connections with active terminals connected and the
[`accesscontrol`](accesscontrol) middleware that lets you specify allowed
commands.
## Default Server
Wish includes the ability to easily create an always authenticating default SSH
server with automatic server key generation.
## Examples
There are examples for a standalone [Bubble Tea application](examples/bubbletea)
and [Git server](examples/git) in the [examples](examples) folder.
## Apps Built With Wish
* [Soft Serve](https://github.com/charmbracelet/soft-serve)
* [Wishlist](https://github.com/charmbracelet/wishlist)
* [SSHWordle](https://github.com/davidcroda/sshwordle)
* [clidle](https://github.com/ajeetdsouza/clidle)
* [ssh-warm-welcome](https://git.vvvvvvaria.org/decentral1se/ssh-warm-welcome)
[bubbletea]: https://github.com/charmbracelet/bubbletea
[gliderlabs/ssh]: https://github.com/gliderlabs/ssh
## Pro Tip
When building various Wish applications locally you can add the following to
your `~/.ssh/config` to avoid having to clear out `localhost` entries in your
`~/.ssh/known_hosts` file:
```
Host localhost
UserKnownHostsFile /dev/null
```
## How it works?
Wish uses [gliderlabs/ssh][gliderlabs/ssh] to implement its SSH server, and
the OpenSSH is never used nor needed — you can even uninstall it if you want to.
Incidentally, there's no risk of accidentally sharing a shell because there's no
default behavior that does that on Wish.
###
## Feedback
Wed love to hear your thoughts on this project. Feel free to drop us a note!
* [Twitter](https://twitter.com/charmcli)
* [The Fediverse](https://mastodon.social/@charmcli)
* [Discord](https://charm.sh/chat)
## License
[MIT](https://github.com/charmbracelet/wish/raw/main/LICENSE)
***
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>
Charm热爱开源 • Charm loves open source

View File

@ -0,0 +1,41 @@
package logging
import (
"log"
"time"
"github.com/charmbracelet/ssh"
"github.com/charmbracelet/wish"
)
// Middleware provides basic connection logging. Connects are logged with the
// remote address, invoked command, TERM setting, window dimensions and if the
// auth was public key based. Disconnect will log the remote address and
// connection duration.
//
// The logger is set to the std default logger.
func Middleware() wish.Middleware {
return MiddlewareWithLogger(log.Default())
}
// Logger is the interface that wraps the basic Log method.
type Logger interface {
Printf(format string, v ...interface{})
}
// MiddlewareWithLogger provides basic connection logging. Connects are logged with the
// remote address, invoked command, TERM setting, window dimensions and if the
// auth was public key based. Disconnect will log the remote address and
// connection duration.
func MiddlewareWithLogger(l Logger) wish.Middleware {
return func(sh ssh.Handler) ssh.Handler {
return func(s ssh.Session) {
ct := time.Now()
hpk := s.PublicKey() != nil
pty, _, _ := s.Pty()
l.Printf("%s connect %s %v %v %s %v %v\n", s.User(), s.RemoteAddr().String(), hpk, s.Command(), pty.Term, pty.Window.Width, pty.Window.Height)
sh(s)
l.Printf("%s disconnect %s\n", s.RemoteAddr().String(), time.Since(ct))
}
}
}

188
vendor/github.com/charmbracelet/wish/options.go generated vendored Normal file
View File

@ -0,0 +1,188 @@
package wish
import (
"bufio"
"bytes"
"errors"
"io"
"log"
"os"
"path/filepath"
"strings"
"time"
"github.com/charmbracelet/keygen"
"github.com/charmbracelet/ssh"
gossh "golang.org/x/crypto/ssh"
)
// WithAddress returns an ssh.Option that sets the address to listen on.
func WithAddress(addr string) ssh.Option {
return func(s *ssh.Server) error {
s.Addr = addr
return nil
}
}
// WithVersion returns an ssh.Option that sets the server version.
func WithVersion(version string) ssh.Option {
return func(s *ssh.Server) error {
s.Version = version
return nil
}
}
// WithMiddleware composes the provided Middleware and return a ssh.Option.
// This useful if you manually create an ssh.Server and want to set the
// Server.Handler.
//
// Notice that middlewares are composed from first to last, which means the last one is executed first.
func WithMiddleware(mw ...Middleware) ssh.Option {
return func(s *ssh.Server) error {
h := func(s ssh.Session) {}
for _, m := range mw {
h = m(h)
}
s.Handler = h
return nil
}
}
// WithHostKeyFile returns an ssh.Option that sets the path to the private.
func WithHostKeyPath(path string) ssh.Option {
if _, err := os.Stat(path); os.IsNotExist(err) {
dir, f := filepath.Split(path)
n := strings.TrimSuffix(f, "_ed25519")
_, err := keygen.NewWithWrite(filepath.Join(dir, n), nil, keygen.Ed25519)
if err != nil {
return func(*ssh.Server) error {
return err
}
}
path = filepath.Join(dir, n+"_ed25519")
}
return ssh.HostKeyFile(path)
}
// WithHostKeyPEM returns an ssh.Option that sets the host key from a PEM block.
func WithHostKeyPEM(pem []byte) ssh.Option {
return ssh.HostKeyPEM(pem)
}
// WithAuthorizedKeys allows to use a SSH authorized_keys file to allowlist users.
func WithAuthorizedKeys(path string) ssh.Option {
return func(s *ssh.Server) error {
if _, err := os.Stat(path); err != nil {
return err
}
return WithPublicKeyAuth(func(_ ssh.Context, key ssh.PublicKey) bool {
return isAuthorized(path, func(k ssh.PublicKey) bool {
return ssh.KeysEqual(key, k)
})
})(s)
}
}
// WithTrustedUserCAKeys authorize certificates that are signed with the given
// Certificate Authority public key, and are valid.
// Analogous to the TrustedUserCAKeys OpenSSH option.
func WithTrustedUserCAKeys(path string) ssh.Option {
return func(s *ssh.Server) error {
if _, err := os.Stat(path); err != nil {
return err
}
return WithPublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) bool {
cert, ok := key.(*gossh.Certificate)
if !ok {
// not a certificate...
return false
}
return isAuthorized(path, func(k ssh.PublicKey) bool {
checker := &gossh.CertChecker{
IsUserAuthority: func(auth gossh.PublicKey) bool {
// its a cert signed by one of the CAs
return bytes.Equal(auth.Marshal(), k.Marshal())
},
}
if !checker.IsUserAuthority(cert.SignatureKey) {
return false
}
if err := checker.CheckCert(ctx.User(), cert); err != nil {
return false
}
return true
})
})(s)
}
}
func isAuthorized(path string, checker func(k ssh.PublicKey) bool) bool {
f, err := os.Open(path)
if err != nil {
log.Printf("failed to parse %q: %s", path, err)
return false
}
defer f.Close() // nolint: errcheck
rd := bufio.NewReader(f)
for {
line, _, err := rd.ReadLine()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
log.Printf("failed to parse %q: %s", path, err)
return false
}
if strings.TrimSpace(string(line)) == "" {
continue
}
if bytes.HasPrefix(line, []byte{'#'}) {
continue
}
upk, _, _, _, err := ssh.ParseAuthorizedKey(line)
if err != nil {
log.Printf("failed to parse %q: %s", path, err)
return false
}
if checker(upk) {
return true
}
}
return false
}
// WithPublicKeyAuth returns an ssh.Option that sets the public key auth handler.
func WithPublicKeyAuth(h ssh.PublicKeyHandler) ssh.Option {
return ssh.PublicKeyAuth(h)
}
// WithPasswordAuth returns an ssh.Option that sets the password auth handler.
func WithPasswordAuth(p ssh.PasswordHandler) ssh.Option {
return ssh.PasswordAuth(p)
}
// WithKeyboardInteractiveAuth returns an ssh.Option that sets the keyboard interactive auth handler.
func WithKeyboardInteractiveAuth(h ssh.KeyboardInteractiveHandler) ssh.Option {
return ssh.KeyboardInteractiveAuth(h)
}
// WithIdleTimeout returns an ssh.Option that sets the connection's idle timeout.
func WithIdleTimeout(d time.Duration) ssh.Option {
return func(s *ssh.Server) error {
s.IdleTimeout = d
return nil
}
}
// WithMaxTimeout returns an ssh.Option that sets the connection's absolute timeout.
func WithMaxTimeout(d time.Duration) ssh.Option {
return func(s *ssh.Server) error {
s.MaxTimeout = d
return nil
}
}

100
vendor/github.com/charmbracelet/wish/wish.go generated vendored Normal file
View File

@ -0,0 +1,100 @@
package wish
import (
"fmt"
"io"
"github.com/charmbracelet/keygen"
"github.com/charmbracelet/ssh"
)
// Middleware is a function that takes an ssh.Handler and returns an
// ssh.Handler. Implementations should call the provided handler argument.
type Middleware func(ssh.Handler) ssh.Handler
// NewServer is returns a default SSH server with the provided Middleware. A
// new SSH key pair of type ed25519 will be created if one does not exist. By
// default this server will accept all incoming connections, password and
// public key.
func NewServer(ops ...ssh.Option) (*ssh.Server, error) {
s := &ssh.Server{}
// Some sensible defaults
s.Version = "OpenSSH_7.6p1"
for _, op := range ops {
if err := s.SetOption(op); err != nil {
return nil, err
}
}
if len(s.HostSigners) == 0 {
k, err := keygen.New("", nil, keygen.Ed25519)
if err != nil {
return nil, err
}
err = s.SetOption(WithHostKeyPEM(k.PrivateKeyPEM()))
if err != nil {
return nil, err
}
}
return s, nil
}
// Fatal prints to the given session's STDERR and exits 1.
func Fatal(s ssh.Session, v ...interface{}) {
Error(s, v...)
_ = s.Exit(1)
_ = s.Close()
}
// Fatalf formats according to the given format, prints to the session's STDERR
// followed by an exit 1.
//
// Notice that this might cause formatting issues if you don't add a \r\n in the end of your string.
func Fatalf(s ssh.Session, f string, v ...interface{}) {
Errorf(s, f, v...)
_ = s.Exit(1)
_ = s.Close()
}
// Fatalln formats according to the default format, prints to the session's
// STDERR, followed by a new line and an exit 1.
func Fatalln(s ssh.Session, v ...interface{}) {
Errorln(s, v...)
Errorf(s, "\r")
_ = s.Exit(1)
_ = s.Close()
}
// Error prints the given error the the session's STDERR.
func Error(s ssh.Session, v ...interface{}) {
_, _ = fmt.Fprint(s.Stderr(), v...)
}
// Errorf formats according to the given format and prints to the session's STDERR.
func Errorf(s ssh.Session, f string, v ...interface{}) {
_, _ = fmt.Fprintf(s.Stderr(), f, v...)
}
// Errorf formats according to the default format and prints to the session's STDERR.
func Errorln(s ssh.Session, v ...interface{}) {
_, _ = fmt.Fprintln(s.Stderr(), v...)
}
// Print writes to the session's STDOUT followed.
func Print(s ssh.Session, v ...interface{}) {
_, _ = fmt.Fprint(s, v...)
}
// Printf formats according to the given format and writes to the session's STDOUT.
func Printf(s ssh.Session, f string, v ...interface{}) {
_, _ = fmt.Fprintf(s, f, v...)
}
// Println formats according to the default format and writes to the session's STDOUT.
func Println(s ssh.Session, v ...interface{}) {
_, _ = fmt.Fprintln(s, v...)
}
// WriteString writes the given string to the session's STDOUT.
func WriteString(s ssh.Session, v string) (int, error) {
return io.WriteString(s, v)
}

202
vendor/github.com/google/shlex/COPYING generated vendored Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

2
vendor/github.com/google/shlex/README generated vendored Normal file
View File

@ -0,0 +1,2 @@
go-shlex is a simple lexer for go that supports shell-style quoting,
commenting, and escaping.

416
vendor/github.com/google/shlex/shlex.go generated vendored Normal file
View File

@ -0,0 +1,416 @@
/*
Copyright 2012 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
Package shlex implements a simple lexer which splits input in to tokens using
shell-style rules for quoting and commenting.
The basic use case uses the default ASCII lexer to split a string into sub-strings:
shlex.Split("one \"two three\" four") -> []string{"one", "two three", "four"}
To process a stream of strings:
l := NewLexer(os.Stdin)
for ; token, err := l.Next(); err != nil {
// process token
}
To access the raw token stream (which includes tokens for comments):
t := NewTokenizer(os.Stdin)
for ; token, err := t.Next(); err != nil {
// process token
}
*/
package shlex
import (
"bufio"
"fmt"
"io"
"strings"
)
// TokenType is a top-level token classification: A word, space, comment, unknown.
type TokenType int
// runeTokenClass is the type of a UTF-8 character classification: A quote, space, escape.
type runeTokenClass int
// the internal state used by the lexer state machine
type lexerState int
// Token is a (type, value) pair representing a lexographical token.
type Token struct {
tokenType TokenType
value string
}
// Equal reports whether tokens a, and b, are equal.
// Two tokens are equal if both their types and values are equal. A nil token can
// never be equal to another token.
func (a *Token) Equal(b *Token) bool {
if a == nil || b == nil {
return false
}
if a.tokenType != b.tokenType {
return false
}
return a.value == b.value
}
// Named classes of UTF-8 runes
const (
spaceRunes = " \t\r\n"
escapingQuoteRunes = `"`
nonEscapingQuoteRunes = "'"
escapeRunes = `\`
commentRunes = "#"
)
// Classes of rune token
const (
unknownRuneClass runeTokenClass = iota
spaceRuneClass
escapingQuoteRuneClass
nonEscapingQuoteRuneClass
escapeRuneClass
commentRuneClass
eofRuneClass
)
// Classes of lexographic token
const (
UnknownToken TokenType = iota
WordToken
SpaceToken
CommentToken
)
// Lexer state machine states
const (
startState lexerState = iota // no runes have been seen
inWordState // processing regular runes in a word
escapingState // we have just consumed an escape rune; the next rune is literal
escapingQuotedState // we have just consumed an escape rune within a quoted string
quotingEscapingState // we are within a quoted string that supports escaping ("...")
quotingState // we are within a string that does not support escaping ('...')
commentState // we are within a comment (everything following an unquoted or unescaped #
)
// tokenClassifier is used for classifying rune characters.
type tokenClassifier map[rune]runeTokenClass
func (typeMap tokenClassifier) addRuneClass(runes string, tokenType runeTokenClass) {
for _, runeChar := range runes {
typeMap[runeChar] = tokenType
}
}
// newDefaultClassifier creates a new classifier for ASCII characters.
func newDefaultClassifier() tokenClassifier {
t := tokenClassifier{}
t.addRuneClass(spaceRunes, spaceRuneClass)
t.addRuneClass(escapingQuoteRunes, escapingQuoteRuneClass)
t.addRuneClass(nonEscapingQuoteRunes, nonEscapingQuoteRuneClass)
t.addRuneClass(escapeRunes, escapeRuneClass)
t.addRuneClass(commentRunes, commentRuneClass)
return t
}
// ClassifyRune classifiees a rune
func (t tokenClassifier) ClassifyRune(runeVal rune) runeTokenClass {
return t[runeVal]
}
// Lexer turns an input stream into a sequence of tokens. Whitespace and comments are skipped.
type Lexer Tokenizer
// NewLexer creates a new lexer from an input stream.
func NewLexer(r io.Reader) *Lexer {
return (*Lexer)(NewTokenizer(r))
}
// Next returns the next word, or an error. If there are no more words,
// the error will be io.EOF.
func (l *Lexer) Next() (string, error) {
for {
token, err := (*Tokenizer)(l).Next()
if err != nil {
return "", err
}
switch token.tokenType {
case WordToken:
return token.value, nil
case CommentToken:
// skip comments
default:
return "", fmt.Errorf("Unknown token type: %v", token.tokenType)
}
}
}
// Tokenizer turns an input stream into a sequence of typed tokens
type Tokenizer struct {
input bufio.Reader
classifier tokenClassifier
}
// NewTokenizer creates a new tokenizer from an input stream.
func NewTokenizer(r io.Reader) *Tokenizer {
input := bufio.NewReader(r)
classifier := newDefaultClassifier()
return &Tokenizer{
input: *input,
classifier: classifier}
}
// scanStream scans the stream for the next token using the internal state machine.
// It will panic if it encounters a rune which it does not know how to handle.
func (t *Tokenizer) scanStream() (*Token, error) {
state := startState
var tokenType TokenType
var value []rune
var nextRune rune
var nextRuneType runeTokenClass
var err error
for {
nextRune, _, err = t.input.ReadRune()
nextRuneType = t.classifier.ClassifyRune(nextRune)
if err == io.EOF {
nextRuneType = eofRuneClass
err = nil
} else if err != nil {
return nil, err
}
switch state {
case startState: // no runes read yet
{
switch nextRuneType {
case eofRuneClass:
{
return nil, io.EOF
}
case spaceRuneClass:
{
}
case escapingQuoteRuneClass:
{
tokenType = WordToken
state = quotingEscapingState
}
case nonEscapingQuoteRuneClass:
{
tokenType = WordToken
state = quotingState
}
case escapeRuneClass:
{
tokenType = WordToken
state = escapingState
}
case commentRuneClass:
{
tokenType = CommentToken
state = commentState
}
default:
{
tokenType = WordToken
value = append(value, nextRune)
state = inWordState
}
}
}
case inWordState: // in a regular word
{
switch nextRuneType {
case eofRuneClass:
{
token := &Token{
tokenType: tokenType,
value: string(value)}
return token, err
}
case spaceRuneClass:
{
token := &Token{
tokenType: tokenType,
value: string(value)}
return token, err
}
case escapingQuoteRuneClass:
{
state = quotingEscapingState
}
case nonEscapingQuoteRuneClass:
{
state = quotingState
}
case escapeRuneClass:
{
state = escapingState
}
default:
{
value = append(value, nextRune)
}
}
}
case escapingState: // the rune after an escape character
{
switch nextRuneType {
case eofRuneClass:
{
err = fmt.Errorf("EOF found after escape character")
token := &Token{
tokenType: tokenType,
value: string(value)}
return token, err
}
default:
{
state = inWordState
value = append(value, nextRune)
}
}
}
case escapingQuotedState: // the next rune after an escape character, in double quotes
{
switch nextRuneType {
case eofRuneClass:
{
err = fmt.Errorf("EOF found after escape character")
token := &Token{
tokenType: tokenType,
value: string(value)}
return token, err
}
default:
{
state = quotingEscapingState
value = append(value, nextRune)
}
}
}
case quotingEscapingState: // in escaping double quotes
{
switch nextRuneType {
case eofRuneClass:
{
err = fmt.Errorf("EOF found when expecting closing quote")
token := &Token{
tokenType: tokenType,
value: string(value)}
return token, err
}
case escapingQuoteRuneClass:
{
state = inWordState
}
case escapeRuneClass:
{
state = escapingQuotedState
}
default:
{
value = append(value, nextRune)
}
}
}
case quotingState: // in non-escaping single quotes
{
switch nextRuneType {
case eofRuneClass:
{
err = fmt.Errorf("EOF found when expecting closing quote")
token := &Token{
tokenType: tokenType,
value: string(value)}
return token, err
}
case nonEscapingQuoteRuneClass:
{
state = inWordState
}
default:
{
value = append(value, nextRune)
}
}
}
case commentState: // in a comment
{
switch nextRuneType {
case eofRuneClass:
{
token := &Token{
tokenType: tokenType,
value: string(value)}
return token, err
}
case spaceRuneClass:
{
if nextRune == '\n' {
state = startState
token := &Token{
tokenType: tokenType,
value: string(value)}
return token, err
} else {
value = append(value, nextRune)
}
}
default:
{
value = append(value, nextRune)
}
}
}
default:
{
return nil, fmt.Errorf("Unexpected state: %v", state)
}
}
}
}
// Next returns the next token in the stream.
func (t *Tokenizer) Next() (*Token, error) {
return t.scanStream()
}
// Split partitions a string into a slice of strings.
func Split(s string) ([]string, error) {
l := NewLexer(strings.NewReader(s))
subStrings := make([]string, 0)
for {
word, err := l.Next()
if err != nil {
if err == io.EOF {
return subStrings, nil
}
return subStrings, err
}
subStrings = append(subStrings, word)
}
}

View File

@ -1,16 +0,0 @@
language: go
sudo: false
go:
- 1.13.x
- tip
before_install:
- go get -t -v ./...
script:
- go generate
- git diff --cached --exit-code
- ./go.test.sh
after_success:
- bash <(curl -s https://codecov.io/bash)

View File

@ -1,7 +1,7 @@
go-runewidth
============
[![Build Status](https://travis-ci.org/mattn/go-runewidth.png?branch=master)](https://travis-ci.org/mattn/go-runewidth)
[![Build Status](https://github.com/mattn/go-runewidth/workflows/test/badge.svg?branch=master)](https://github.com/mattn/go-runewidth/actions?query=workflow%3Atest)
[![Codecov](https://codecov.io/gh/mattn/go-runewidth/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-runewidth)
[![GoDoc](https://godoc.org/github.com/mattn/go-runewidth?status.svg)](http://godoc.org/github.com/mattn/go-runewidth)
[![Go Report Card](https://goreportcard.com/badge/github.com/mattn/go-runewidth)](https://goreportcard.com/report/github.com/mattn/go-runewidth)

View File

@ -1,12 +0,0 @@
#!/usr/bin/env bash
set -e
echo "" > coverage.txt
for d in $(go list ./... | grep -v vendor); do
go test -race -coverprofile=profile.out -covermode=atomic "$d"
if [ -f profile.out ]; then
cat profile.out >> coverage.txt
rm profile.out
fi
done

View File

@ -2,6 +2,7 @@ package runewidth
import (
"os"
"strings"
"github.com/rivo/uniseg"
)
@ -34,7 +35,13 @@ func handleEnv() {
EastAsianWidth = env == "1"
}
// update DefaultCondition
DefaultCondition.EastAsianWidth = EastAsianWidth
if DefaultCondition.EastAsianWidth != EastAsianWidth {
DefaultCondition.EastAsianWidth = EastAsianWidth
if len(DefaultCondition.combinedLut) > 0 {
DefaultCondition.combinedLut = DefaultCondition.combinedLut[:0]
CreateLUT()
}
}
}
type interval struct {
@ -89,6 +96,7 @@ var nonprint = table{
// Condition have flag EastAsianWidth whether the current locale is CJK or not.
type Condition struct {
combinedLut []byte
EastAsianWidth bool
StrictEmojiNeutral bool
}
@ -104,10 +112,16 @@ func NewCondition() *Condition {
// RuneWidth returns the number of cells in r.
// See http://www.unicode.org/reports/tr11/
func (c *Condition) RuneWidth(r rune) int {
if r < 0 || r > 0x10FFFF {
return 0
}
if len(c.combinedLut) > 0 {
return int(c.combinedLut[r>>1]>>(uint(r&1)*4)) & 3
}
// optimized version, verified by TestRuneWidthChecksums()
if !c.EastAsianWidth {
switch {
case r < 0x20 || r > 0x10FFFF:
case r < 0x20:
return 0
case (r >= 0x7F && r <= 0x9F) || r == 0xAD: // nonprint
return 0
@ -124,7 +138,7 @@ func (c *Condition) RuneWidth(r rune) int {
}
} else {
switch {
case r < 0 || r > 0x10FFFF || inTables(r, nonprint, combining):
case inTables(r, nonprint, combining):
return 0
case inTable(r, narrow):
return 1
@ -138,6 +152,27 @@ func (c *Condition) RuneWidth(r rune) int {
}
}
// CreateLUT will create an in-memory lookup table of 557056 bytes for faster operation.
// This should not be called concurrently with other operations on c.
// If options in c is changed, CreateLUT should be called again.
func (c *Condition) CreateLUT() {
const max = 0x110000
lut := c.combinedLut
if len(c.combinedLut) != 0 {
// Remove so we don't use it.
c.combinedLut = nil
} else {
lut = make([]byte, max/2)
}
for i := range lut {
i32 := int32(i * 2)
x0 := c.RuneWidth(i32)
x1 := c.RuneWidth(i32 + 1)
lut[i] = uint8(x0) | uint8(x1)<<4
}
c.combinedLut = lut
}
// StringWidth return width as you can see
func (c *Condition) StringWidth(s string) (width int) {
g := uniseg.NewGraphemes(s)
@ -180,11 +215,47 @@ func (c *Condition) Truncate(s string, w int, tail string) string {
return s[:pos] + tail
}
// TruncateLeft cuts w cells from the beginning of the `s`.
func (c *Condition) TruncateLeft(s string, w int, prefix string) string {
if c.StringWidth(s) <= w {
return prefix
}
var width int
pos := len(s)
g := uniseg.NewGraphemes(s)
for g.Next() {
var chWidth int
for _, r := range g.Runes() {
chWidth = c.RuneWidth(r)
if chWidth > 0 {
break // See StringWidth() for details.
}
}
if width+chWidth > w {
if width < w {
_, pos = g.Positions()
prefix += strings.Repeat(" ", width+chWidth-w)
} else {
pos, _ = g.Positions()
}
break
}
width += chWidth
}
return prefix + s[pos:]
}
// Wrap return string wrapped with w cells
func (c *Condition) Wrap(s string, w int) string {
width := 0
out := ""
for _, r := range []rune(s) {
for _, r := range s {
cw := c.RuneWidth(r)
if r == '\n' {
out += string(r)
@ -257,6 +328,11 @@ func Truncate(s string, w int, tail string) string {
return DefaultCondition.Truncate(s, w, tail)
}
// TruncateLeft cuts w cells from the beginning of the `s`.
func TruncateLeft(s string, w int, prefix string) string {
return DefaultCondition.TruncateLeft(s, w, prefix)
}
// Wrap return string wrapped with w cells
func Wrap(s string, w int) string {
return DefaultCondition.Wrap(s, w)
@ -271,3 +347,12 @@ func FillLeft(s string, w int) string {
func FillRight(s string, w int) string {
return DefaultCondition.FillRight(s, w)
}
// CreateLUT will create an in-memory lookup table of 557055 bytes for faster operation.
// This should not be called concurrently with other operations.
func CreateLUT() {
if len(DefaultCondition.combinedLut) > 0 {
return
}
DefaultCondition.CreateLUT()
}

View File

@ -1,3 +1,4 @@
//go:build appengine
// +build appengine
package runewidth

View File

@ -1,5 +1,5 @@
// +build js
// +build !appengine
//go:build js && !appengine
// +build js,!appengine
package runewidth

View File

@ -1,6 +1,5 @@
// +build !windows
// +build !js
// +build !appengine
//go:build !windows && !js && !appengine
// +build !windows,!js,!appengine
package runewidth

View File

@ -1,5 +1,5 @@
// +build windows
// +build !appengine
//go:build windows && !appengine
// +build windows,!appengine
package runewidth

21
vendor/github.com/mitchellh/go-homedir/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013 Mitchell Hashimoto
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.

14
vendor/github.com/mitchellh/go-homedir/README.md generated vendored Normal file
View File

@ -0,0 +1,14 @@
# go-homedir
This is a Go library for detecting the user's home directory without
the use of cgo, so the library can be used in cross-compilation environments.
Usage is incredibly simple, just call `homedir.Dir()` to get the home directory
for a user, and `homedir.Expand()` to expand the `~` in a path to the home
directory.
**Why not just use `os/user`?** The built-in `os/user` package requires
cgo on Darwin systems. This means that any Go code that uses that package
cannot cross compile. But 99% of the time the use for `os/user` is just to
retrieve the home directory, which we can do for the current user without
cgo. This library does that, enabling cross-compilation.

167
vendor/github.com/mitchellh/go-homedir/homedir.go generated vendored Normal file
View File

@ -0,0 +1,167 @@
package homedir
import (
"bytes"
"errors"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
)
// DisableCache will disable caching of the home directory. Caching is enabled
// by default.
var DisableCache bool
var homedirCache string
var cacheLock sync.RWMutex
// Dir returns the home directory for the executing user.
//
// This uses an OS-specific method for discovering the home directory.
// An error is returned if a home directory cannot be detected.
func Dir() (string, error) {
if !DisableCache {
cacheLock.RLock()
cached := homedirCache
cacheLock.RUnlock()
if cached != "" {
return cached, nil
}
}
cacheLock.Lock()
defer cacheLock.Unlock()
var result string
var err error
if runtime.GOOS == "windows" {
result, err = dirWindows()
} else {
// Unix-like system, so just assume Unix
result, err = dirUnix()
}
if err != nil {
return "", err
}
homedirCache = result
return result, nil
}
// Expand expands the path to include the home directory if the path
// is prefixed with `~`. If it isn't prefixed with `~`, the path is
// returned as-is.
func Expand(path string) (string, error) {
if len(path) == 0 {
return path, nil
}
if path[0] != '~' {
return path, nil
}
if len(path) > 1 && path[1] != '/' && path[1] != '\\' {
return "", errors.New("cannot expand user-specific home dir")
}
dir, err := Dir()
if err != nil {
return "", err
}
return filepath.Join(dir, path[1:]), nil
}
// Reset clears the cache, forcing the next call to Dir to re-detect
// the home directory. This generally never has to be called, but can be
// useful in tests if you're modifying the home directory via the HOME
// env var or something.
func Reset() {
cacheLock.Lock()
defer cacheLock.Unlock()
homedirCache = ""
}
func dirUnix() (string, error) {
homeEnv := "HOME"
if runtime.GOOS == "plan9" {
// On plan9, env vars are lowercase.
homeEnv = "home"
}
// First prefer the HOME environmental variable
if home := os.Getenv(homeEnv); home != "" {
return home, nil
}
var stdout bytes.Buffer
// If that fails, try OS specific commands
if runtime.GOOS == "darwin" {
cmd := exec.Command("sh", "-c", `dscl -q . -read /Users/"$(whoami)" NFSHomeDirectory | sed 's/^[^ ]*: //'`)
cmd.Stdout = &stdout
if err := cmd.Run(); err == nil {
result := strings.TrimSpace(stdout.String())
if result != "" {
return result, nil
}
}
} else {
cmd := exec.Command("getent", "passwd", strconv.Itoa(os.Getuid()))
cmd.Stdout = &stdout
if err := cmd.Run(); err != nil {
// If the error is ErrNotFound, we ignore it. Otherwise, return it.
if err != exec.ErrNotFound {
return "", err
}
} else {
if passwd := strings.TrimSpace(stdout.String()); passwd != "" {
// username:password:uid:gid:gecos:home:shell
passwdParts := strings.SplitN(passwd, ":", 7)
if len(passwdParts) > 5 {
return passwdParts[5], nil
}
}
}
}
// If all else fails, try the shell
stdout.Reset()
cmd := exec.Command("sh", "-c", "cd && pwd")
cmd.Stdout = &stdout
if err := cmd.Run(); err != nil {
return "", err
}
result := strings.TrimSpace(stdout.String())
if result == "" {
return "", errors.New("blank output when reading home directory")
}
return result, nil
}
func dirWindows() (string, error) {
// First prefer the HOME environmental variable
if home := os.Getenv("HOME"); home != "" {
return home, nil
}
// Prefer standard environment variable USERPROFILE
if home := os.Getenv("USERPROFILE"); home != "" {
return home, nil
}
drive := os.Getenv("HOMEDRIVE")
path := os.Getenv("HOMEPATH")
home := drive + path
if drive == "" || path == "" {
return "", errors.New("HOMEDRIVE, HOMEPATH, or USERPROFILE are blank")
}
return home, nil
}

View File

@ -31,17 +31,15 @@ color conversions.
go get github.com/muesli/termenv
```
## Query Terminal Support
`termenv` can query the terminal it is running in, so you can safely use
advanced features, like RGB colors. `ColorProfile` returns the color profile
supported by the terminal:
## Usage
```go
profile := termenv.ColorProfile()
output := termenv.NewOutput(os.Stdout)
```
This returns one of the supported color profiles:
`termenv` queries the terminal's capabilities it is running in, so you can
safely use advanced features, like RGB colors or ANSI styles. `output.Profile`
returns the supported profile:
- `termenv.Ascii` - no ANSI support detected, ASCII only
- `termenv.ANSI` - 16 color ANSI support
@ -57,39 +55,46 @@ app is running in a light- or dark-themed environment:
```go
// Returns terminal's foreground color
color := termenv.ForegroundColor()
color := output.ForegroundColor()
// Returns terminal's background color
color := termenv.BackgroundColor()
color := output.BackgroundColor()
// Returns whether terminal uses a dark-ish background
darkTheme := termenv.HasDarkBackground()
darkTheme := output.HasDarkBackground()
```
### Manual Profile Selection
If you don't want to rely on the automatic detection, you can manually select
the profile you want to use:
```go
output := termenv.NewOutput(os.Stdout, termenv.WithProfile(termenv.TrueColor))
```
## Colors
`termenv` supports multiple color profiles: ANSI (16 colors), ANSI Extended
(256 colors), and TrueColor (24-bit RGB). Colors will automatically be degraded
to the best matching available color in the desired profile:
`termenv` supports multiple color profiles: Ascii (black & white only),
ANSI (16 colors), ANSI Extended (256 colors), and TrueColor (24-bit RGB). Colors
will automatically be degraded to the best matching available color in the
desired profile:
`TrueColor` => `ANSI 256 Colors` => `ANSI 16 Colors` => `Ascii`
```go
s := termenv.String("Hello World")
// Retrieve color profile supported by terminal
p := termenv.ColorProfile()
s := output.String("Hello World")
// Supports hex values
// Will automatically degrade colors on terminals not supporting RGB
s.Foreground(p.Color("#abcdef"))
s.Foreground(output.Color("#abcdef"))
// but also supports ANSI colors (0-255)
s.Background(p.Color("69"))
s.Background(output.Color("69"))
// ...or the color.Color interface
s.Foreground(p.FromColor(color.RGBA{255, 128, 0, 255}))
s.Foreground(output.FromColor(color.RGBA{255, 128, 0, 255}))
// Combine fore- & background colors
s.Foreground(p.Color("#ffffff")).Background(p.Color("#0000ff"))
s.Foreground(output.Color("#ffffff")).Background(output.Color("#0000ff"))
// Supports the fmt.Stringer interface
fmt.Println(s)
@ -100,7 +105,7 @@ fmt.Println(s)
You can use a chainable syntax to compose your own styles:
```go
s := termenv.String("foobar")
s := output.String("foobar")
// Text styles
s.Bold()
@ -122,9 +127,11 @@ s.Bold().Underline()
## Template Helpers
`termenv` provides a set of helper functions to style your Go templates:
```go
// load template helpers
f := termenv.TemplateFuncs(termenv.ColorProfile())
f := output.TemplateFuncs()
tpl := template.New("tpl").Funcs(f)
// apply bold style in a template
@ -153,153 +160,170 @@ Other available helper functions are: `Faint`, `Italic`, `CrossOut`,
```go
// Move the cursor to a given position
termenv.MoveCursor(row, column)
output.MoveCursor(row, column)
// Save the cursor position
termenv.SaveCursorPosition()
output.SaveCursorPosition()
// Restore a saved cursor position
termenv.RestoreCursorPosition()
output.RestoreCursorPosition()
// Move the cursor up a given number of lines
termenv.CursorUp(n)
output.CursorUp(n)
// Move the cursor down a given number of lines
termenv.CursorDown(n)
output.CursorDown(n)
// Move the cursor up a given number of lines
termenv.CursorForward(n)
output.CursorForward(n)
// Move the cursor backwards a given number of cells
termenv.CursorBack(n)
output.CursorBack(n)
// Move the cursor down a given number of lines and place it at the beginning
// of the line
termenv.CursorNextLine(n)
output.CursorNextLine(n)
// Move the cursor up a given number of lines and place it at the beginning of
// the line
termenv.CursorPrevLine(n)
output.CursorPrevLine(n)
```
## Screen
```go
// Reset the terminal to its default style, removing any active styles
termenv.Reset()
output.Reset()
// RestoreScreen restores a previously saved screen state
termenv.RestoreScreen()
output.RestoreScreen()
// SaveScreen saves the screen state
termenv.SaveScreen()
output.SaveScreen()
// Switch to the altscreen. The former view can be restored with ExitAltScreen()
termenv.AltScreen()
output.AltScreen()
// Exit the altscreen and return to the former terminal view
termenv.ExitAltScreen()
output.ExitAltScreen()
// Clear the visible portion of the terminal
termenv.ClearScreen()
output.ClearScreen()
// Clear the current line
termenv.ClearLine()
output.ClearLine()
// Clear a given number of lines
termenv.ClearLines(n)
output.ClearLines(n)
// Set the scrolling region of the terminal
termenv.ChangeScrollingRegion(top, bottom)
output.ChangeScrollingRegion(top, bottom)
// Insert the given number of lines at the top of the scrollable region, pushing
// lines below down
termenv.InsertLines(n)
output.InsertLines(n)
// Delete the given number of lines, pulling any lines in the scrollable region
// below up
termenv.DeleteLines(n)
output.DeleteLines(n)
```
## Session
```go
// SetWindowTitle sets the terminal window title
termenv.SetWindowTitle(title)
output.SetWindowTitle(title)
// SetForegroundColor sets the default foreground color
termenv.SetForegroundColor(color)
output.SetForegroundColor(color)
// SetBackgroundColor sets the default background color
termenv.SetBackgroundColor(color)
output.SetBackgroundColor(color)
// SetCursorColor sets the cursor color
termenv.SetCursorColor(color)
output.SetCursorColor(color)
// Hide the cursor
termenv.HideCursor()
output.HideCursor()
// Show the cursor
termenv.ShowCursor()
output.ShowCursor()
```
## Mouse
```go
// Enable X10 mouse mode, only button press events are sent
termenv.EnableMousePress()
output.EnableMousePress()
// Disable X10 mouse mode
termenv.DisableMousePress()
output.DisableMousePress()
// Enable Mouse Tracking mode
termenv.EnableMouse()
output.EnableMouse()
// Disable Mouse Tracking mode
termenv.DisableMouse()
output.DisableMouse()
// Enable Hilite Mouse Tracking mode
termenv.EnableMouseHilite()
output.EnableMouseHilite()
// Disable Hilite Mouse Tracking mode
termenv.DisableMouseHilite()
output.DisableMouseHilite()
// Enable Cell Motion Mouse Tracking mode
termenv.EnableMouseCellMotion()
output.EnableMouseCellMotion()
// Disable Cell Motion Mouse Tracking mode
termenv.DisableMouseCellMotion()
output.DisableMouseCellMotion()
// Enable All Motion Mouse mode
termenv.EnableMouseAllMotion()
output.EnableMouseAllMotion()
// Disable All Motion Mouse mode
termenv.DisableMouseAllMotion()
output.DisableMouseAllMotion()
```
## Bracketed Paste
```go
// Enables bracketed paste mode
termenv.EnableBracketedPaste()
// Disables bracketed paste mode
termenv.DisableBracketedPaste()
```
## Optional Feature Support
| Terminal | Alt Screen | Query Color Scheme | Query Cursor Position | Set Window Title | Change Cursor Color | Change Default Foreground Setting | Change Default Background Setting |
| ---------------- | :--------: | :----------------: | :-------------------: | :--------------: | :-----------------: | :-------------------------------: | :-------------------------------: |
| alacritty | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| foot | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| kitty | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Konsole | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
| rxvt | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
| screen | ✅ | ⛔[^mux] | ✅ | ✅ | ❌ | ❌ | ✅ |
| st | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| tmux | ✅ | ⛔[^mux] | ✅ | ✅ | ✅ | ✅ | ✅ |
| vte-based[^vte] | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
| wezterm | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| xterm | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
| Linux Console | ✅ | ❌ | ✅ | ⛔ | ❌ | ❌ | ❌ |
| Apple Terminal | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
| iTerm | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
| Windows cmd | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Windows Terminal | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Terminal | Alt Screen | Query Color Scheme | Query Cursor Position | Set Window Title | Change Cursor Color | Change Default Foreground Setting | Change Default Background Setting | Copy (OSC52) | Hyperlinks (OSC8) | Bracketed Paste |
| ---------------- | :--------: | :----------------: | :-------------------: | :--------------: | :-----------------: | :-------------------------------: | :-------------------------------: | :----------: | :---------------: | :-------------: |
| alacritty | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌[^alacritty] | ✅ |
| foot | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| kitty | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Konsole | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌[^konsole] | ✅ | ✅ |
| rxvt | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |
| urxvt | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅[^urxvt] | ❌ | ✅ |
| screen | ✅ | ⛔[^mux] | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌[^screen] | ❌ |
| st | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
| tmux | ✅ | ⛔[^mux] | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌[^tmux] | ✅ |
| vte-based[^vte] | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌[^vte] | ✅ | ✅ |
| wezterm | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| xterm | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ |
| Linux Console | ✅ | ❌ | ✅ | ⛔ | ❌ | ❌ | ❌ | ⛔ | ⛔ | ❌ |
| Apple Terminal | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅[^apple] | ❌ | ✅ |
| iTerm | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
| Windows cmd | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
| Windows Terminal | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
[^vte]: This covers all vte-based terminals, including Gnome Terminal, guake, Pantheon Terminal, Terminator, Tilix, XFCE Terminal.
[^vte]: This covers all vte-based terminals, including Gnome Terminal, guake, Pantheon Terminal, Terminator, Tilix, XFCE Terminal. OSC52 is not supported, see [issue#2495](https://gitlab.gnome.org/GNOME/vte/-/issues/2495).
[^mux]: Unavailable as multiplexers (like tmux or screen) can be connected to multiple terminals (with different color settings) at the same time.
[^urxvt]: Workaround for urxvt not supporting OSC52. See [this](https://unix.stackexchange.com/a/629485) for more information.
[^konsole]: OSC52 is not supported, for more info see [bug#372116](https://bugs.kde.org/show_bug.cgi?id=372116).
[^apple]: OSC52 works with a [workaround](https://github.com/roy2220/osc52pty).
[^tmux]: OSC8 is not supported, for more info see [issue#911](https://github.com/tmux/tmux/issues/911).
[^screen]: OSC8 is not supported, for more info see [bug#50952](https://savannah.gnu.org/bugs/index.php?50952).
[^alacritty]: OSC8 is not supported, for more info see [issue#922](https://github.com/alacritty/alacritty/issues/922).
You can help improve this list! Check out [how to](ansi_compat.md) and open an issue or pull request.
@ -316,13 +340,17 @@ terminal applications on Unix support ANSI styling out-of-the-box, on Windows
you need to enable ANSI processing in your application first:
```go
mode, err := termenv.EnableWindowsANSIConsole()
restoreConsole, err := termenv.EnableVirtualTerminalProcessing(termenv.DefaultOutput())
if err != nil {
panic(err)
}
defer termenv.RestoreWindowsConsole(mode)
defer restoreConsole()
```
The above code is safe to include on non-Windows systems or when os.Stdout does
not refer to a terminal (e.g. in tests).
## Color Chart
![ANSI color chart](https://github.com/muesli/termenv/raw/master/examples/color-chart/color-chart.png)
@ -351,8 +379,8 @@ out these projects:
Got some feedback or suggestions? Please open an issue or drop me a note!
* [Twitter](https://twitter.com/mueslix)
* [The Fediverse](https://mastodon.social/@fribbledom)
- [Twitter](https://twitter.com/mueslix)
- [The Fediverse](https://mastodon.social/@fribbledom)
## License

View File

@ -46,3 +46,12 @@ This command should set the window title to "Test":
```bash
echo -ne "\033]2;Test\007" && sleep 10
```
## Bracketed paste
Enter this command, then paste a word from the clipboard. The text
displayed on the terminal should contain the codes `200~` and `201~`:
```bash
echo -ne "\033[?2004h" && sleep 10
```

View File

@ -3,9 +3,7 @@ package termenv
import (
"errors"
"fmt"
"image/color"
"math"
"strconv"
"strings"
"github.com/lucasb-eyer/go-colorful"
@ -69,72 +67,6 @@ func ConvertToRGB(c Color) colorful.Color {
return ch
}
// Convert transforms a given Color to a Color supported within the Profile.
func (p Profile) Convert(c Color) Color {
if p == Ascii {
return NoColor{}
}
switch v := c.(type) {
case ANSIColor:
return v
case ANSI256Color:
if p == ANSI {
return ansi256ToANSIColor(v)
}
return v
case RGBColor:
h, err := colorful.Hex(string(v))
if err != nil {
return nil
}
if p < TrueColor {
ac := hexToANSI256Color(h)
if p == ANSI {
return ansi256ToANSIColor(ac)
}
return ac
}
return v
}
return c
}
// Color creates a Color from a string. Valid inputs are hex colors, as well as
// ANSI color codes (0-15, 16-255).
func (p Profile) Color(s string) Color {
if len(s) == 0 {
return nil
}
var c Color
if strings.HasPrefix(s, "#") {
c = RGBColor(s)
} else {
i, err := strconv.Atoi(s)
if err != nil {
return nil
}
if i < 16 {
c = ANSIColor(i)
} else {
c = ANSI256Color(i)
}
}
return p.Convert(c)
}
// FromColor creates a Color from a color.Color.
func (p Profile) FromColor(c color.Color) Color {
col, _ := colorful.MakeColor(c)
return p.Color(col.Hex())
}
// Sequence returns the ANSI Sequence for the color.
func (c NoColor) Sequence(bg bool) string {
return ""

16
vendor/github.com/muesli/termenv/copy.go generated vendored Normal file
View File

@ -0,0 +1,16 @@
package termenv
import (
"github.com/aymanbagabas/go-osc52"
)
// Copy copies text to clipboard using OSC 52 escape sequence.
func (o Output) Copy(str string) {
out := osc52.NewOutput(o.tty, o.environ.Environ())
out.Copy(str)
}
// Copy copies text to clipboard using OSC 52 escape sequence.
func Copy(str string) {
output.Copy(str)
}

15
vendor/github.com/muesli/termenv/hyperlink.go generated vendored Normal file
View File

@ -0,0 +1,15 @@
package termenv
import (
"fmt"
)
// Hyperlink creates a hyperlink using OSC8.
func Hyperlink(link, name string) string {
return output.Hyperlink(link, name)
}
// Hyperlink creates a hyperlink using OSC8.
func (o *Output) Hyperlink(link, name string) string {
return fmt.Sprintf("\x1b]8;;%s\x1b\\%s\x1b]8;;\x1b\\", link, name)
}

163
vendor/github.com/muesli/termenv/output.go generated vendored Normal file
View File

@ -0,0 +1,163 @@
package termenv
import (
"io"
"os"
"sync"
)
var (
// output is the default global output.
output = NewOutput(os.Stdout)
)
// File represents a file descriptor.
type File interface {
io.ReadWriter
Fd() uintptr
}
// Output is a terminal output.
type Output struct {
Profile
tty io.Writer
environ Environ
cache bool
fgSync *sync.Once
fgColor Color
bgSync *sync.Once
bgColor Color
}
// Environ is an interface for getting environment variables.
type Environ interface {
Environ() []string
Getenv(string) string
}
type osEnviron struct{}
func (oe *osEnviron) Environ() []string {
return os.Environ()
}
func (oe *osEnviron) Getenv(key string) string {
return os.Getenv(key)
}
// DefaultOutput returns the default global output.
func DefaultOutput() *Output {
return output
}
// NewOutput returns a new Output for the given file descriptor.
func NewOutput(tty io.Writer, opts ...func(*Output)) *Output {
o := &Output{
tty: tty,
environ: &osEnviron{},
Profile: -1,
fgSync: &sync.Once{},
fgColor: NoColor{},
bgSync: &sync.Once{},
bgColor: NoColor{},
}
for _, opt := range opts {
opt(o)
}
if o.Profile < 0 {
o.Profile = o.EnvColorProfile()
}
return o
}
// WithEnvironment returns a new Output for the given environment.
func WithEnvironment(environ Environ) func(*Output) {
return func(o *Output) {
o.environ = environ
}
}
// WithProfile returns a new Output for the given profile.
func WithProfile(profile Profile) func(*Output) {
return func(o *Output) {
o.Profile = profile
}
}
// WithColorCache returns a new Output with fore- and background color values
// pre-fetched and cached.
func WithColorCache(v bool) func(*Output) {
return func(o *Output) {
o.cache = v
// cache the values now
_ = o.ForegroundColor()
_ = o.BackgroundColor()
}
}
// ForegroundColor returns the terminal's default foreground color.
func (o *Output) ForegroundColor() Color {
f := func() {
if !o.isTTY() {
return
}
o.fgColor = o.foregroundColor()
}
if o.cache {
o.fgSync.Do(f)
} else {
f()
}
return o.fgColor
}
// BackgroundColor returns the terminal's default background color.
func (o *Output) BackgroundColor() Color {
f := func() {
if !o.isTTY() {
return
}
o.bgColor = o.backgroundColor()
}
if o.cache {
o.bgSync.Do(f)
} else {
f()
}
return o.bgColor
}
// HasDarkBackground returns whether terminal uses a dark-ish background.
func (o *Output) HasDarkBackground() bool {
c := ConvertToRGB(o.BackgroundColor())
_, _, l := c.Hsl()
return l < 0.5
}
// TTY returns the terminal's file descriptor. This may be nil if the output is
// not a terminal.
func (o Output) TTY() File {
if f, ok := o.tty.(File); ok {
return f
}
return nil
}
func (o Output) Write(p []byte) (int, error) {
return o.tty.Write(p)
}
// WriteString writes the given string to the output.
func (o Output) WriteString(s string) (int, error) {
return o.Write([]byte(s))
}

97
vendor/github.com/muesli/termenv/profile.go generated vendored Normal file
View File

@ -0,0 +1,97 @@
package termenv
import (
"image/color"
"strconv"
"strings"
"github.com/lucasb-eyer/go-colorful"
)
// Profile is a color profile: Ascii, ANSI, ANSI256, or TrueColor.
type Profile int
const (
// TrueColor, 24-bit color profile
TrueColor = Profile(iota)
// ANSI256, 8-bit color profile
ANSI256
// ANSI, 4-bit color profile
ANSI
// Ascii, uncolored profile
Ascii //nolint:revive
)
// String returns a new Style.
func (p Profile) String(s ...string) Style {
return Style{
profile: p,
string: strings.Join(s, " "),
}
}
// Convert transforms a given Color to a Color supported within the Profile.
func (p Profile) Convert(c Color) Color {
if p == Ascii {
return NoColor{}
}
switch v := c.(type) {
case ANSIColor:
return v
case ANSI256Color:
if p == ANSI {
return ansi256ToANSIColor(v)
}
return v
case RGBColor:
h, err := colorful.Hex(string(v))
if err != nil {
return nil
}
if p != TrueColor {
ac := hexToANSI256Color(h)
if p == ANSI {
return ansi256ToANSIColor(ac)
}
return ac
}
return v
}
return c
}
// Color creates a Color from a string. Valid inputs are hex colors, as well as
// ANSI color codes (0-15, 16-255).
func (p Profile) Color(s string) Color {
if len(s) == 0 {
return nil
}
var c Color
if strings.HasPrefix(s, "#") {
c = RGBColor(s)
} else {
i, err := strconv.Atoi(s)
if err != nil {
return nil
}
if i < 16 {
c = ANSIColor(i)
} else {
c = ANSI256Color(i)
}
}
return p.Convert(c)
}
// FromColor creates a Color from a color.Color.
func (p Profile) FromColor(c color.Color) Color {
col, _ := colorful.MakeColor(c)
return p.Color(col.Hex())
}

View File

@ -49,6 +49,13 @@ const (
AltScreenSeq = "?1049h"
ExitAltScreenSeq = "?1049l"
// Bracketed paste.
// https://en.wikipedia.org/wiki/Bracketed-paste
EnableBracketedPasteSeq = "?2004h"
DisableBracketedPasteSeq = "?2004l"
StartBracketedPasteSeq = "200~"
EndBracketedPasteSeq = "201~"
// Session.
SetWindowTitleSeq = "2;%s\007"
SetForegroundColorSeq = "10;%s\007"
@ -59,200 +66,498 @@ const (
)
// Reset the terminal to its default style, removing any active styles.
func Reset() {
fmt.Print(CSI + ResetSeq + "m")
func (o Output) Reset() {
fmt.Fprint(o.tty, CSI+ResetSeq+"m")
}
// SetForegroundColor sets the default foreground color.
func SetForegroundColor(color Color) {
fmt.Printf(OSC+SetForegroundColorSeq, color)
func (o Output) SetForegroundColor(color Color) {
fmt.Fprintf(o.tty, OSC+SetForegroundColorSeq, color)
}
// SetBackgroundColor sets the default background color.
func SetBackgroundColor(color Color) {
fmt.Printf(OSC+SetBackgroundColorSeq, color)
func (o Output) SetBackgroundColor(color Color) {
fmt.Fprintf(o.tty, OSC+SetBackgroundColorSeq, color)
}
// SetCursorColor sets the cursor color.
func SetCursorColor(color Color) {
fmt.Printf(OSC+SetCursorColorSeq, color)
func (o Output) SetCursorColor(color Color) {
fmt.Fprintf(o.tty, OSC+SetCursorColorSeq, color)
}
// RestoreScreen restores a previously saved screen state.
func RestoreScreen() {
fmt.Print(CSI + RestoreScreenSeq)
func (o Output) RestoreScreen() {
fmt.Fprint(o.tty, CSI+RestoreScreenSeq)
}
// SaveScreen saves the screen state.
func SaveScreen() {
fmt.Print(CSI + SaveScreenSeq)
func (o Output) SaveScreen() {
fmt.Fprint(o.tty, CSI+SaveScreenSeq)
}
// AltScreen switches to the alternate screen buffer. The former view can be
// restored with ExitAltScreen().
func AltScreen() {
fmt.Print(CSI + AltScreenSeq)
func (o Output) AltScreen() {
fmt.Fprint(o.tty, CSI+AltScreenSeq)
}
// ExitAltScreen exits the alternate screen buffer and returns to the former
// terminal view.
func ExitAltScreen() {
fmt.Print(CSI + ExitAltScreenSeq)
func (o Output) ExitAltScreen() {
fmt.Fprint(o.tty, CSI+ExitAltScreenSeq)
}
// ClearScreen clears the visible portion of the terminal.
func ClearScreen() {
fmt.Printf(CSI+EraseDisplaySeq, 2)
MoveCursor(1, 1)
func (o Output) ClearScreen() {
fmt.Fprintf(o.tty, CSI+EraseDisplaySeq, 2)
o.MoveCursor(1, 1)
}
// MoveCursor moves the cursor to a given position.
func MoveCursor(row int, column int) {
fmt.Printf(CSI+CursorPositionSeq, row, column)
func (o Output) MoveCursor(row int, column int) {
fmt.Fprintf(o.tty, CSI+CursorPositionSeq, row, column)
}
// HideCursor hides the cursor.
func HideCursor() {
fmt.Printf(CSI + HideCursorSeq)
func (o Output) HideCursor() {
fmt.Fprint(o.tty, CSI+HideCursorSeq)
}
// ShowCursor shows the cursor.
func ShowCursor() {
fmt.Printf(CSI + ShowCursorSeq)
func (o Output) ShowCursor() {
fmt.Fprint(o.tty, CSI+ShowCursorSeq)
}
// SaveCursorPosition saves the cursor position.
func SaveCursorPosition() {
fmt.Print(CSI + SaveCursorPositionSeq)
func (o Output) SaveCursorPosition() {
fmt.Fprint(o.tty, CSI+SaveCursorPositionSeq)
}
// RestoreCursorPosition restores a saved cursor position.
func RestoreCursorPosition() {
fmt.Print(CSI + RestoreCursorPositionSeq)
func (o Output) RestoreCursorPosition() {
fmt.Fprint(o.tty, CSI+RestoreCursorPositionSeq)
}
// CursorUp moves the cursor up a given number of lines.
func CursorUp(n int) {
fmt.Printf(CSI+CursorUpSeq, n)
func (o Output) CursorUp(n int) {
fmt.Fprintf(o.tty, CSI+CursorUpSeq, n)
}
// CursorDown moves the cursor down a given number of lines.
func CursorDown(n int) {
fmt.Printf(CSI+CursorDownSeq, n)
func (o Output) CursorDown(n int) {
fmt.Fprintf(o.tty, CSI+CursorDownSeq, n)
}
// CursorForward moves the cursor up a given number of lines.
func CursorForward(n int) {
fmt.Printf(CSI+CursorForwardSeq, n)
func (o Output) CursorForward(n int) {
fmt.Fprintf(o.tty, CSI+CursorForwardSeq, n)
}
// CursorBack moves the cursor backwards a given number of cells.
func CursorBack(n int) {
fmt.Printf(CSI+CursorBackSeq, n)
func (o Output) CursorBack(n int) {
fmt.Fprintf(o.tty, CSI+CursorBackSeq, n)
}
// CursorNextLine moves the cursor down a given number of lines and places it at
// the beginning of the line.
func CursorNextLine(n int) {
fmt.Printf(CSI+CursorNextLineSeq, n)
func (o Output) CursorNextLine(n int) {
fmt.Fprintf(o.tty, CSI+CursorNextLineSeq, n)
}
// CursorPrevLine moves the cursor up a given number of lines and places it at
// the beginning of the line.
func CursorPrevLine(n int) {
fmt.Printf(CSI+CursorPreviousLineSeq, n)
func (o Output) CursorPrevLine(n int) {
fmt.Fprintf(o.tty, CSI+CursorPreviousLineSeq, n)
}
// ClearLine clears the current line.
func ClearLine() {
fmt.Print(CSI + EraseEntireLineSeq)
func (o Output) ClearLine() {
fmt.Fprint(o.tty, CSI+EraseEntireLineSeq)
}
// ClearLineLeft clears the line to the left of the cursor.
func ClearLineLeft() {
fmt.Print(CSI + EraseLineLeftSeq)
func (o Output) ClearLineLeft() {
fmt.Fprint(o.tty, CSI+EraseLineLeftSeq)
}
// ClearLineRight clears the line to the right of the cursor.
func ClearLineRight() {
fmt.Print(CSI + EraseLineRightSeq)
func (o Output) ClearLineRight() {
fmt.Fprint(o.tty, CSI+EraseLineRightSeq)
}
// ClearLines clears a given number of lines.
func ClearLines(n int) {
func (o Output) ClearLines(n int) {
clearLine := fmt.Sprintf(CSI+EraseLineSeq, 2)
cursorUp := fmt.Sprintf(CSI+CursorUpSeq, 1)
fmt.Print(clearLine + strings.Repeat(cursorUp+clearLine, n))
fmt.Fprint(o.tty, clearLine+strings.Repeat(cursorUp+clearLine, n))
}
// ChangeScrollingRegion sets the scrolling region of the terminal.
func ChangeScrollingRegion(top, bottom int) {
fmt.Printf(CSI+ChangeScrollingRegionSeq, top, bottom)
func (o Output) ChangeScrollingRegion(top, bottom int) {
fmt.Fprintf(o.tty, CSI+ChangeScrollingRegionSeq, top, bottom)
}
// InsertLines inserts the given number of lines at the top of the scrollable
// region, pushing lines below down.
func InsertLines(n int) {
fmt.Printf(CSI+InsertLineSeq, n)
func (o Output) InsertLines(n int) {
fmt.Fprintf(o.tty, CSI+InsertLineSeq, n)
}
// DeleteLines deletes the given number of lines, pulling any lines in
// the scrollable region below up.
func DeleteLines(n int) {
fmt.Printf(CSI+DeleteLineSeq, n)
func (o Output) DeleteLines(n int) {
fmt.Fprintf(o.tty, CSI+DeleteLineSeq, n)
}
// EnableMousePress enables X10 mouse mode. Button press events are sent only.
func EnableMousePress() {
fmt.Print(CSI + EnableMousePressSeq)
func (o Output) EnableMousePress() {
fmt.Fprint(o.tty, CSI+EnableMousePressSeq)
}
// DisableMousePress disables X10 mouse mode.
func DisableMousePress() {
fmt.Print(CSI + DisableMousePressSeq)
func (o Output) DisableMousePress() {
fmt.Fprint(o.tty, CSI+DisableMousePressSeq)
}
// EnableMouse enables Mouse Tracking mode.
func EnableMouse() {
fmt.Print(CSI + EnableMouseSeq)
func (o Output) EnableMouse() {
fmt.Fprint(o.tty, CSI+EnableMouseSeq)
}
// DisableMouse disables Mouse Tracking mode.
func DisableMouse() {
fmt.Print(CSI + DisableMouseSeq)
func (o Output) DisableMouse() {
fmt.Fprint(o.tty, CSI+DisableMouseSeq)
}
// EnableMouseHilite enables Hilite Mouse Tracking mode.
func EnableMouseHilite() {
fmt.Print(CSI + EnableMouseHiliteSeq)
func (o Output) EnableMouseHilite() {
fmt.Fprint(o.tty, CSI+EnableMouseHiliteSeq)
}
// DisableMouseHilite disables Hilite Mouse Tracking mode.
func DisableMouseHilite() {
fmt.Print(CSI + DisableMouseHiliteSeq)
func (o Output) DisableMouseHilite() {
fmt.Fprint(o.tty, CSI+DisableMouseHiliteSeq)
}
// EnableMouseCellMotion enables Cell Motion Mouse Tracking mode.
func EnableMouseCellMotion() {
fmt.Print(CSI + EnableMouseCellMotionSeq)
func (o Output) EnableMouseCellMotion() {
fmt.Fprint(o.tty, CSI+EnableMouseCellMotionSeq)
}
// DisableMouseCellMotion disables Cell Motion Mouse Tracking mode.
func DisableMouseCellMotion() {
fmt.Print(CSI + DisableMouseCellMotionSeq)
func (o Output) DisableMouseCellMotion() {
fmt.Fprint(o.tty, CSI+DisableMouseCellMotionSeq)
}
// EnableMouseAllMotion enables All Motion Mouse mode.
func EnableMouseAllMotion() {
fmt.Print(CSI + EnableMouseAllMotionSeq)
func (o Output) EnableMouseAllMotion() {
fmt.Fprint(o.tty, CSI+EnableMouseAllMotionSeq)
}
// DisableMouseAllMotion disables All Motion Mouse mode.
func DisableMouseAllMotion() {
fmt.Print(CSI + DisableMouseAllMotionSeq)
func (o Output) DisableMouseAllMotion() {
fmt.Fprint(o.tty, CSI+DisableMouseAllMotionSeq)
}
// SetWindowTitle sets the terminal window title.
func SetWindowTitle(title string) {
fmt.Printf(OSC+SetWindowTitleSeq, title)
func (o Output) SetWindowTitle(title string) {
fmt.Fprintf(o.tty, OSC+SetWindowTitleSeq, title)
}
// EnableBracketedPaste enables bracketed paste.
func (o Output) EnableBracketedPaste() {
fmt.Fprintf(o.tty, CSI+EnableBracketedPasteSeq)
}
// DisableBracketedPaste disables bracketed paste.
func (o Output) DisableBracketedPaste() {
fmt.Fprintf(o.tty, CSI+DisableBracketedPasteSeq)
}
// Legacy functions.
// Reset the terminal to its default style, removing any active styles.
//
// Deprecated: please use termenv.Output instead.
func Reset() {
output.Reset()
}
// SetForegroundColor sets the default foreground color.
//
// Deprecated: please use termenv.Output instead.
func SetForegroundColor(color Color) {
output.SetForegroundColor(color)
}
// SetBackgroundColor sets the default background color.
//
// Deprecated: please use termenv.Output instead.
func SetBackgroundColor(color Color) {
output.SetBackgroundColor(color)
}
// SetCursorColor sets the cursor color.
//
// Deprecated: please use termenv.Output instead.
func SetCursorColor(color Color) {
output.SetCursorColor(color)
}
// RestoreScreen restores a previously saved screen state.
//
// Deprecated: please use termenv.Output instead.
func RestoreScreen() {
output.RestoreScreen()
}
// SaveScreen saves the screen state.
//
// Deprecated: please use termenv.Output instead.
func SaveScreen() {
output.SaveScreen()
}
// AltScreen switches to the alternate screen buffer. The former view can be
// restored with ExitAltScreen().
//
// Deprecated: please use termenv.Output instead.
func AltScreen() {
output.AltScreen()
}
// ExitAltScreen exits the alternate screen buffer and returns to the former
// terminal view.
//
// Deprecated: please use termenv.Output instead.
func ExitAltScreen() {
output.ExitAltScreen()
}
// ClearScreen clears the visible portion of the terminal.
//
// Deprecated: please use termenv.Output instead.
func ClearScreen() {
output.ClearScreen()
}
// MoveCursor moves the cursor to a given position.
//
// Deprecated: please use termenv.Output instead.
func MoveCursor(row int, column int) {
output.MoveCursor(row, column)
}
// HideCursor hides the cursor.
//
// Deprecated: please use termenv.Output instead.
func HideCursor() {
output.HideCursor()
}
// ShowCursor shows the cursor.
//
// Deprecated: please use termenv.Output instead.
func ShowCursor() {
output.ShowCursor()
}
// SaveCursorPosition saves the cursor position.
//
// Deprecated: please use termenv.Output instead.
func SaveCursorPosition() {
output.SaveCursorPosition()
}
// RestoreCursorPosition restores a saved cursor position.
//
// Deprecated: please use termenv.Output instead.
func RestoreCursorPosition() {
output.RestoreCursorPosition()
}
// CursorUp moves the cursor up a given number of lines.
//
// Deprecated: please use termenv.Output instead.
func CursorUp(n int) {
output.CursorUp(n)
}
// CursorDown moves the cursor down a given number of lines.
//
// Deprecated: please use termenv.Output instead.
func CursorDown(n int) {
output.CursorDown(n)
}
// CursorForward moves the cursor up a given number of lines.
//
// Deprecated: please use termenv.Output instead.
func CursorForward(n int) {
output.CursorForward(n)
}
// CursorBack moves the cursor backwards a given number of cells.
//
// Deprecated: please use termenv.Output instead.
func CursorBack(n int) {
output.CursorBack(n)
}
// CursorNextLine moves the cursor down a given number of lines and places it at
// the beginning of the line.
//
// Deprecated: please use termenv.Output instead.
func CursorNextLine(n int) {
output.CursorNextLine(n)
}
// CursorPrevLine moves the cursor up a given number of lines and places it at
// the beginning of the line.
//
// Deprecated: please use termenv.Output instead.
func CursorPrevLine(n int) {
output.CursorPrevLine(n)
}
// ClearLine clears the current line.
//
// Deprecated: please use termenv.Output instead.
func ClearLine() {
output.ClearLine()
}
// ClearLineLeft clears the line to the left of the cursor.
//
// Deprecated: please use termenv.Output instead.
func ClearLineLeft() {
output.ClearLineLeft()
}
// ClearLineRight clears the line to the right of the cursor.
//
// Deprecated: please use termenv.Output instead.
func ClearLineRight() {
output.ClearLineRight()
}
// ClearLines clears a given number of lines.
//
// Deprecated: please use termenv.Output instead.
func ClearLines(n int) {
output.ClearLines(n)
}
// ChangeScrollingRegion sets the scrolling region of the terminal.
//
// Deprecated: please use termenv.Output instead.
func ChangeScrollingRegion(top, bottom int) {
output.ChangeScrollingRegion(top, bottom)
}
// InsertLines inserts the given number of lines at the top of the scrollable
// region, pushing lines below down.
//
// Deprecated: please use termenv.Output instead.
func InsertLines(n int) {
output.InsertLines(n)
}
// DeleteLines deletes the given number of lines, pulling any lines in
// the scrollable region below up.
//
// Deprecated: please use termenv.Output instead.
func DeleteLines(n int) {
output.DeleteLines(n)
}
// EnableMousePress enables X10 mouse mode. Button press events are sent only.
//
// Deprecated: please use termenv.Output instead.
func EnableMousePress() {
output.EnableMousePress()
}
// DisableMousePress disables X10 mouse mode.
//
// Deprecated: please use termenv.Output instead.
func DisableMousePress() {
output.DisableMousePress()
}
// EnableMouse enables Mouse Tracking mode.
//
// Deprecated: please use termenv.Output instead.
func EnableMouse() {
output.EnableMouse()
}
// DisableMouse disables Mouse Tracking mode.
//
// Deprecated: please use termenv.Output instead.
func DisableMouse() {
output.DisableMouse()
}
// EnableMouseHilite enables Hilite Mouse Tracking mode.
//
// Deprecated: please use termenv.Output instead.
func EnableMouseHilite() {
output.EnableMouseHilite()
}
// DisableMouseHilite disables Hilite Mouse Tracking mode.
//
// Deprecated: please use termenv.Output instead.
func DisableMouseHilite() {
output.DisableMouseHilite()
}
// EnableMouseCellMotion enables Cell Motion Mouse Tracking mode.
//
// Deprecated: please use termenv.Output instead.
func EnableMouseCellMotion() {
output.EnableMouseCellMotion()
}
// DisableMouseCellMotion disables Cell Motion Mouse Tracking mode.
//
// Deprecated: please use termenv.Output instead.
func DisableMouseCellMotion() {
output.DisableMouseCellMotion()
}
// EnableMouseAllMotion enables All Motion Mouse mode.
//
// Deprecated: please use termenv.Output instead.
func EnableMouseAllMotion() {
output.EnableMouseAllMotion()
}
// DisableMouseAllMotion disables All Motion Mouse mode.
//
// Deprecated: please use termenv.Output instead.
func DisableMouseAllMotion() {
output.DisableMouseAllMotion()
}
// SetWindowTitle sets the terminal window title.
//
// Deprecated: please use termenv.Output instead.
func SetWindowTitle(title string) {
output.SetWindowTitle(title)
}
// EnableBracketedPaste enables bracketed paste.
//
// Deprecated: please use termenv.Output instead.
func EnableBracketedPaste() {
output.EnableBracketedPaste()
}
// DisableBracketedPaste disables bracketed paste.
//
// Deprecated: please use termenv.Output instead.
func DisableBracketedPaste() {
output.DisableBracketedPaste()
}

View File

@ -22,6 +22,7 @@ const (
// Style is a string that various rendering styles can be applied to.
type Style struct {
profile Profile
string
styles []string
}
@ -29,7 +30,8 @@ type Style struct {
// String returns a new Style.
func String(s ...string) Style {
return Style{
string: strings.Join(s, " "),
profile: ANSI,
string: strings.Join(s, " "),
}
}
@ -39,6 +41,9 @@ func (t Style) String() string {
// Styled renders s with all applied styles.
func (t Style) Styled(s string) string {
if t.profile == Ascii {
return s
}
if len(t.styles) == 0 {
return s
}

View File

@ -4,11 +4,20 @@ import (
"text/template"
)
// TemplateFuncs returns template helpers for the given output.
func (o Output) TemplateFuncs() template.FuncMap {
return TemplateFuncs(o.Profile)
}
// TemplateFuncs contains a few useful template helpers.
func TemplateFuncs(p Profile) template.FuncMap {
if p == Ascii {
return noopTemplateFuncs
}
return template.FuncMap{
"Color": func(values ...interface{}) string {
s := String(values[len(values)-1].(string))
s := p.String(values[len(values)-1].(string))
switch len(values) {
case 2:
s = s.Foreground(p.Color(values[0].(string)))
@ -21,7 +30,7 @@ func TemplateFuncs(p Profile) template.FuncMap {
return s.String()
},
"Foreground": func(values ...interface{}) string {
s := String(values[len(values)-1].(string))
s := p.String(values[len(values)-1].(string))
if len(values) == 2 {
s = s.Foreground(p.Color(values[0].(string)))
}
@ -29,27 +38,49 @@ func TemplateFuncs(p Profile) template.FuncMap {
return s.String()
},
"Background": func(values ...interface{}) string {
s := String(values[len(values)-1].(string))
s := p.String(values[len(values)-1].(string))
if len(values) == 2 {
s = s.Background(p.Color(values[0].(string)))
}
return s.String()
},
"Bold": styleFunc(Style.Bold),
"Faint": styleFunc(Style.Faint),
"Italic": styleFunc(Style.Italic),
"Underline": styleFunc(Style.Underline),
"Overline": styleFunc(Style.Overline),
"Blink": styleFunc(Style.Blink),
"Reverse": styleFunc(Style.Reverse),
"CrossOut": styleFunc(Style.CrossOut),
"Bold": styleFunc(p, Style.Bold),
"Faint": styleFunc(p, Style.Faint),
"Italic": styleFunc(p, Style.Italic),
"Underline": styleFunc(p, Style.Underline),
"Overline": styleFunc(p, Style.Overline),
"Blink": styleFunc(p, Style.Blink),
"Reverse": styleFunc(p, Style.Reverse),
"CrossOut": styleFunc(p, Style.CrossOut),
}
}
func styleFunc(f func(Style) Style) func(...interface{}) string {
func styleFunc(p Profile, f func(Style) Style) func(...interface{}) string {
return func(values ...interface{}) string {
s := String(values[0].(string))
s := p.String(values[0].(string))
return f(s).String()
}
}
var noopTemplateFuncs = template.FuncMap{
"Color": noColorFunc,
"Foreground": noColorFunc,
"Background": noColorFunc,
"Bold": noStyleFunc,
"Faint": noStyleFunc,
"Italic": noStyleFunc,
"Underline": noStyleFunc,
"Overline": noStyleFunc,
"Blink": noStyleFunc,
"Reverse": noStyleFunc,
"CrossOut": noStyleFunc,
}
func noColorFunc(values ...interface{}) string {
return values[len(values)-1].(string)
}
func noStyleFunc(values ...interface{}) string {
return values[0].(string)
}

View File

@ -2,7 +2,6 @@ package termenv
import (
"errors"
"os"
"github.com/mattn/go-isatty"
)
@ -12,66 +11,52 @@ var (
ErrStatusReport = errors.New("unable to retrieve status report")
)
// Profile is a color profile: Ascii, ANSI, ANSI256, or TrueColor.
type Profile int
const (
// Control Sequence Introducer
CSI = "\x1b["
// Operating System Command
OSC = "\x1b]"
// Ascii, uncolored profile.
Ascii = Profile(iota) //nolint:revive
// ANSI, 4-bit color profile
ANSI
// ANSI256, 8-bit color profile
ANSI256
// TrueColor, 24-bit color profile
TrueColor
)
func isTTY(fd uintptr) bool {
if len(os.Getenv("CI")) > 0 {
func (o *Output) isTTY() bool {
if len(o.environ.Getenv("CI")) > 0 {
return false
}
if o.TTY() == nil {
return false
}
return isatty.IsTerminal(fd)
return isatty.IsTerminal(o.TTY().Fd())
}
// ColorProfile returns the supported color profile:
// Ascii, ANSI, ANSI256, or TrueColor.
func ColorProfile() Profile {
if !isTTY(os.Stdout.Fd()) {
return Ascii
}
return colorProfile()
return output.ColorProfile()
}
// ForegroundColor returns the terminal's default foreground color.
func ForegroundColor() Color {
if !isTTY(os.Stdout.Fd()) {
return NoColor{}
}
return foregroundColor()
return output.ForegroundColor()
}
// BackgroundColor returns the terminal's default background color.
func BackgroundColor() Color {
if !isTTY(os.Stdout.Fd()) {
return NoColor{}
}
return backgroundColor()
return output.BackgroundColor()
}
// HasDarkBackground returns whether terminal uses a dark-ish background.
func HasDarkBackground() bool {
c := ConvertToRGB(BackgroundColor())
_, _, l := c.Hsl()
return l < 0.5
return output.HasDarkBackground()
}
// EnvNoColor returns true if the environment variables explicitly disable color output
// by setting NO_COLOR (https://no-color.org/)
// or CLICOLOR/CLICOLOR_FORCE (https://bixense.com/clicolors/)
// If NO_COLOR is set, this will return true, ignoring CLICOLOR/CLICOLOR_FORCE
// If CLICOLOR=="0", it will be true only if CLICOLOR_FORCE is also "0" or is unset.
func (o *Output) EnvNoColor() bool {
return o.environ.Getenv("NO_COLOR") != "" || (o.environ.Getenv("CLICOLOR") == "0" && !o.cliColorForced())
}
// EnvNoColor returns true if the environment variables explicitly disable color output
@ -80,7 +65,7 @@ func HasDarkBackground() bool {
// If NO_COLOR is set, this will return true, ignoring CLICOLOR/CLICOLOR_FORCE
// If CLICOLOR=="0", it will be true only if CLICOLOR_FORCE is also "0" or is unset.
func EnvNoColor() bool {
return os.Getenv("NO_COLOR") != "" || (os.Getenv("CLICOLOR") == "0" && !cliColorForced())
return output.EnvNoColor()
}
// EnvColorProfile returns the color profile based on environment variables set
@ -91,18 +76,27 @@ func EnvNoColor() bool {
// If the terminal does not support any colors, but CLICOLOR_FORCE is set and not "0"
// then the ANSI color profile will be returned.
func EnvColorProfile() Profile {
if EnvNoColor() {
return output.EnvColorProfile()
}
// EnvNoColor returns true if the environment variables explicitly disable color output
// by setting NO_COLOR (https://no-color.org/)
// or CLICOLOR/CLICOLOR_FORCE (https://bixense.com/clicolors/)
// If NO_COLOR is set, this will return true, ignoring CLICOLOR/CLICOLOR_FORCE
// If CLICOLOR=="0", it will be true only if CLICOLOR_FORCE is also "0" or is unset.
func (o *Output) EnvColorProfile() Profile {
if o.EnvNoColor() {
return Ascii
}
p := ColorProfile()
if cliColorForced() && p == Ascii {
p := o.ColorProfile()
if o.cliColorForced() && p == Ascii {
return ANSI
}
return p
}
func cliColorForced() bool {
if forced := os.Getenv("CLICOLOR_FORCE"); forced != "" {
func (o *Output) cliColorForced() bool {
if forced := o.environ.Getenv("CLICOLOR_FORCE"); forced != "" {
return forced != "0"
}
return false

View File

@ -7,12 +7,12 @@ func colorProfile() Profile {
return ANSI256
}
func foregroundColor() Color {
func (o Output) foregroundColor() Color {
// default gray
return ANSIColor(7)
}
func backgroundColor() Color {
func (o Output) backgroundColor() Color {
// default black
return ANSIColor(0)
}

View File

@ -5,7 +5,7 @@ package termenv
import (
"fmt"
"os"
"io"
"strconv"
"strings"
"time"
@ -18,9 +18,15 @@ const (
OSCTimeout = 5 * time.Second
)
func colorProfile() Profile {
term := os.Getenv("TERM")
colorTerm := os.Getenv("COLORTERM")
// ColorProfile returns the supported color profile:
// Ascii, ANSI, ANSI256, or TrueColor.
func (o *Output) ColorProfile() Profile {
if !o.isTTY() {
return Ascii
}
term := o.environ.Getenv("TERM")
colorTerm := o.environ.Getenv("COLORTERM")
switch strings.ToLower(colorTerm) {
case "24bit":
@ -28,7 +34,7 @@ func colorProfile() Profile {
case "truecolor":
if strings.HasPrefix(term, "screen") {
// tmux supports TrueColor, screen only ANSI256
if os.Getenv("TERM_PROGRAM") != "tmux" {
if o.environ.Getenv("TERM_PROGRAM") != "tmux" {
return ANSI256
}
}
@ -59,8 +65,8 @@ func colorProfile() Profile {
return Ascii
}
func foregroundColor() Color {
s, err := termStatusReport(10)
func (o Output) foregroundColor() Color {
s, err := o.termStatusReport(10)
if err == nil {
c, err := xTermColor(s)
if err == nil {
@ -68,7 +74,7 @@ func foregroundColor() Color {
}
}
colorFGBG := os.Getenv("COLORFGBG")
colorFGBG := o.environ.Getenv("COLORFGBG")
if strings.Contains(colorFGBG, ";") {
c := strings.Split(colorFGBG, ";")
i, err := strconv.Atoi(c[0])
@ -81,8 +87,8 @@ func foregroundColor() Color {
return ANSIColor(7)
}
func backgroundColor() Color {
s, err := termStatusReport(11)
func (o Output) backgroundColor() Color {
s, err := o.termStatusReport(11)
if err == nil {
c, err := xTermColor(s)
if err == nil {
@ -90,7 +96,7 @@ func backgroundColor() Color {
}
}
colorFGBG := os.Getenv("COLORFGBG")
colorFGBG := o.environ.Getenv("COLORFGBG")
if strings.Contains(colorFGBG, ";") {
c := strings.Split(colorFGBG, ";")
i, err := strconv.Atoi(c[len(c)-1])
@ -126,7 +132,7 @@ func waitForData(fd uintptr, timeout time.Duration) error {
return nil
}
func readNextByte(f *os.File) (byte, error) {
func readNextByte(f File) (byte, error) {
if err := waitForData(f.Fd(), OSCTimeout); err != nil {
return 0, err
}
@ -145,9 +151,9 @@ func readNextByte(f *os.File) (byte, error) {
}
// readNextResponse reads either an OSC response or a cursor position response:
// * OSC response: "\x1b]11;rgb:1111/1111/1111\x1b\\"
// * cursor position response: "\x1b[42;1R"
func readNextResponse(fd *os.File) (response string, isOSC bool, err error) {
// - OSC response: "\x1b]11;rgb:1111/1111/1111\x1b\\"
// - cursor position response: "\x1b[42;1R"
func readNextResponse(fd File) (response string, isOSC bool, err error) {
start, err := readNextByte(fd)
if err != nil {
return "", false, err
@ -182,7 +188,7 @@ func readNextResponse(fd *os.File) (response string, isOSC bool, err error) {
}
for {
b, err := readNextByte(os.Stdout)
b, err := readNextByte(fd)
if err != nil {
return "", false, err
}
@ -210,42 +216,48 @@ func readNextResponse(fd *os.File) (response string, isOSC bool, err error) {
return "", false, ErrStatusReport
}
func termStatusReport(sequence int) (string, error) {
func (o Output) termStatusReport(sequence int) (string, error) {
// screen/tmux can't support OSC, because they can be connected to multiple
// terminals concurrently.
term := os.Getenv("TERM")
if strings.HasPrefix(term, "screen") {
term := o.environ.Getenv("TERM")
if strings.HasPrefix(term, "screen") || strings.HasPrefix(term, "tmux") {
return "", ErrStatusReport
}
tty := o.TTY()
if tty == nil {
return "", ErrStatusReport
}
fd := int(tty.Fd())
// if in background, we can't control the terminal
if !isForeground(unix.Stdout) {
if !isForeground(fd) {
return "", ErrStatusReport
}
t, err := unix.IoctlGetTermios(unix.Stdout, tcgetattr)
t, err := unix.IoctlGetTermios(fd, tcgetattr)
if err != nil {
return "", ErrStatusReport
return "", fmt.Errorf("%s: %s", ErrStatusReport, err)
}
defer unix.IoctlSetTermios(unix.Stdout, tcsetattr, t) //nolint:errcheck
defer unix.IoctlSetTermios(fd, tcsetattr, t) //nolint:errcheck
noecho := *t
noecho.Lflag = noecho.Lflag &^ unix.ECHO
noecho.Lflag = noecho.Lflag &^ unix.ICANON
if err := unix.IoctlSetTermios(unix.Stdout, tcsetattr, &noecho); err != nil {
return "", ErrStatusReport
if err := unix.IoctlSetTermios(fd, tcsetattr, &noecho); err != nil {
return "", fmt.Errorf("%s: %s", ErrStatusReport, err)
}
// first, send OSC query, which is ignored by terminal which do not support it
fmt.Printf("\033]%d;?\033\\", sequence)
fmt.Fprintf(tty, "\033]%d;?\033\\", sequence)
// then, query cursor position, should be supported by all terminals
fmt.Printf("\033[6n")
fmt.Fprintf(tty, "\033[6n")
// read the next response
res, isOSC, err := readNextResponse(os.Stdout)
res, isOSC, err := readNextResponse(tty)
if err != nil {
return "", err
return "", fmt.Errorf("%s: %s", ErrStatusReport, err)
}
// if this is not OSC response, then the terminal does not support it
@ -254,7 +266,7 @@ func termStatusReport(sequence int) (string, error) {
}
// read the cursor query response next and discard the result
_, _, err = readNextResponse(os.Stdout)
_, _, err = readNextResponse(tty)
if err != nil {
return "", err
}
@ -262,3 +274,11 @@ func termStatusReport(sequence int) (string, error) {
// fmt.Println("Rcvd", res[1:])
return res, nil
}
// EnableVirtualTerminalProcessing enables virtual terminal processing on
// Windows for w and returns a function that restores w to its previous state.
// On non-Windows platforms, or if w does not refer to a terminal, then it
// returns a non-nil no-op function and no error.
func EnableVirtualTerminalProcessing(w io.Writer) (func() error, error) {
return func() error { return nil }, nil
}

View File

@ -4,22 +4,26 @@
package termenv
import (
"os"
"fmt"
"strconv"
"golang.org/x/sys/windows"
)
func colorProfile() Profile {
if os.Getenv("ConEmuANSI") == "ON" {
func (o *Output) ColorProfile() Profile {
if !o.isTTY() {
return Ascii
}
if o.environ.Getenv("ConEmuANSI") == "ON" {
return TrueColor
}
winVersion, _, buildNumber := windows.RtlGetNtVersionNumbers()
if buildNumber < 10586 || winVersion < 10 {
// No ANSI support before Windows 10 build 10586.
if os.Getenv("ANSICON") != "" {
conVersion := os.Getenv("ANSICON_VER")
if o.environ.Getenv("ANSICON") != "" {
conVersion := o.environ.Getenv("ANSICON_VER")
cv, err := strconv.ParseInt(conVersion, 10, 64)
if err != nil || cv < 181 {
// No 8 bit color support before v1.81 release.
@ -39,12 +43,12 @@ func colorProfile() Profile {
return TrueColor
}
func foregroundColor() Color {
func (o Output) foregroundColor() Color {
// default gray
return ANSIColor(7)
}
func backgroundColor() Color {
func (o Output) backgroundColor() Color {
// default black
return ANSIColor(0)
}
@ -87,3 +91,49 @@ func RestoreWindowsConsole(mode uint32) error {
return windows.SetConsoleMode(handle, mode)
}
// EnableVirtualTerminalProcessing enables virtual terminal processing on
// Windows for o and returns a function that restores o to its previous state.
// On non-Windows platforms, or if o does not refer to a terminal, then it
// returns a non-nil no-op function and no error.
func EnableVirtualTerminalProcessing(o *Output) (restoreFunc func() error, err error) {
// There is nothing to restore until we set the console mode.
restoreFunc = func() error {
return nil
}
// If o is not a tty, then there is nothing to do.
tty := o.TTY()
if tty == nil {
return
}
// Get the current console mode. If there is an error, assume that o is not
// a terminal, discard the error, and return.
var mode uint32
if err2 := windows.GetConsoleMode(windows.Handle(tty.Fd()), &mode); err2 != nil {
return
}
// If virtual terminal processing is already set, then there is nothing to
// do and nothing to restore.
if mode&windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING == windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING {
return
}
// Enable virtual terminal processing. See
// https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
if err2 := windows.SetConsoleMode(windows.Handle(tty.Fd()), mode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err2 != nil {
err = fmt.Errorf("windows.SetConsoleMode: %w", err2)
return
}
// Set the restore function. We maintain a reference to the tty in the
// closure (rather than just its handle) to ensure that the tty is not
// closed by a finalizer.
restoreFunc = func() error {
return windows.SetConsoleMode(windows.Handle(tty.Fd()), mode)
}
return
}

3
vendor/go4.org/netipx/.gitignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
crashers
suppressions
netaddr-fuzz.zip

3
vendor/go4.org/netipx/.gitmodules generated vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "corpus"]
path = corpus
url = https://github.com/inetaf/netaddr-corpus.git

4
vendor/go4.org/netipx/AUTHORS generated vendored Normal file
View File

@ -0,0 +1,4 @@
Alex Willmer <alex@moreati.org.uk>
Matt Layher <mdlayher@gmail.com>
Tailscale Inc.
Tobias Klauser <tklauser@distanz.ch>

27
vendor/go4.org/netipx/LICENSE generated vendored Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2020 The Inet.af AUTHORS. 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 Tailscale 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.

26
vendor/go4.org/netipx/README.md generated vendored Normal file
View File

@ -0,0 +1,26 @@
# netipx [![Test Status](https://github.com/go4org/netipx/workflows/Linux/badge.svg)](https://github.com/go4org/netipx/actions) [![Go Reference](https://pkg.go.dev/badge/go4.org/netipx.svg)](https://pkg.go.dev/go4.org/netipx)
## What
This is a package containing the bits of the old `inet.af/netaddr` package that didn't make it
into Go 1.18's `net/netip` standard library package.
As background, see:
* https://github.com/inetaf/netaddr/ (now deprecated)
* https://tailscale.com/blog/netaddr-new-ip-type-for-go/ - blog post about why the package came to be originally
* https://go.dev/doc/go1.18#netip - Go 1.18 release notes
This package requires Go 1.18+ to use and complements the `net/netip`.
## FAQ
**Why's it no longer under `inet.af`?** Since that joke started, that
TLD is now under control of the Taliban. (Yes, we should've known
better. We'd even previously scolded people for relying on
questionable ccTLDs. Whoops.)
**Will this stuff make it into the standard library?** [Maybe](https://github.com/golang/go/issues/53236).
We'll see.

498
vendor/go4.org/netipx/ipset.go generated vendored Normal file
View File

@ -0,0 +1,498 @@
// Copyright 2020 The Inet.Af AUTHORS. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package netipx
import (
"fmt"
"net/netip"
"runtime"
"sort"
"strings"
)
// IPSetBuilder builds an immutable IPSet.
//
// The zero value is a valid value representing a set of no IPs.
//
// The Add and Remove methods add or remove IPs to/from the set.
// Removals only affect the current membership of the set, so in
// general Adds should be called first. Input ranges may overlap in
// any way.
//
// Most IPSetBuilder methods do not return errors.
// Instead, errors are accumulated and reported by IPSetBuilder.IPSet.
type IPSetBuilder struct {
// in are the ranges in the set.
in []IPRange
// out are the ranges to be removed from 'in'.
out []IPRange
// errs are errors accumulated during construction.
errs multiErr
}
// normalize normalizes s: s.in becomes the minimal sorted list of
// ranges required to describe s, and s.out becomes empty.
func (s *IPSetBuilder) normalize() {
const debug = false
if debug {
debugf("ranges start in=%v out=%v", s.in, s.out)
}
in, ok := mergeIPRanges(s.in)
if !ok {
return
}
out, ok := mergeIPRanges(s.out)
if !ok {
return
}
if debug {
debugf("ranges sort in=%v out=%v", in, out)
}
// in and out are sorted in ascending range order, and have no
// overlaps within each other. We can run a merge of the two lists
// in one pass.
min := make([]IPRange, 0, len(in))
for len(in) > 0 && len(out) > 0 {
rin, rout := in[0], out[0]
if debug {
debugf("step in=%v out=%v", rin, rout)
}
switch {
case !rout.IsValid() || !rin.IsValid():
// mergeIPRanges should have prevented invalid ranges from
// sneaking in.
panic("invalid IPRanges during Ranges merge")
case rout.entirelyBefore(rin):
// "out" is entirely before "in".
//
// out in
// f-------t f-------t
out = out[1:]
if debug {
debugf("out before in; drop out")
}
case rin.entirelyBefore(rout):
// "in" is entirely before "out".
//
// in out
// f------t f-------t
min = append(min, rin)
in = in[1:]
if debug {
debugf("in before out; append in")
debugf("min=%v", min)
}
case rin.coveredBy(rout):
// "out" entirely covers "in".
//
// out
// f-------------t
// f------t
// in
in = in[1:]
if debug {
debugf("in inside out; drop in")
}
case rout.inMiddleOf(rin):
// "in" entirely covers "out".
//
// in
// f-------------t
// f------t
// out
min = append(min, IPRange{from: rin.from, to: AddrPrior(rout.from)})
// Adjust in[0], not ir, because we want to consider the
// mutated range on the next iteration.
in[0].from = rout.to.Next()
out = out[1:]
if debug {
debugf("out inside in; split in, append first in, drop out, adjust second in")
debugf("min=%v", min)
}
case rout.overlapsStartOf(rin):
// "out" overlaps start of "in".
//
// out
// f------t
// f------t
// in
in[0].from = rout.to.Next()
// Can't move ir onto min yet, another later out might
// trim it further. Just discard or and continue.
out = out[1:]
if debug {
debugf("out cuts start of in; adjust in, drop out")
}
case rout.overlapsEndOf(rin):
// "out" overlaps end of "in".
//
// out
// f------t
// f------t
// in
min = append(min, IPRange{from: rin.from, to: AddrPrior(rout.from)})
in = in[1:]
if debug {
debugf("merge out cuts end of in; append shortened in")
debugf("min=%v", min)
}
default:
// The above should account for all combinations of in and
// out overlapping, but insert a panic to be sure.
panic("unexpected additional overlap scenario")
}
}
if len(in) > 0 {
// Ran out of removals before the end of in.
min = append(min, in...)
if debug {
debugf("min=%v", min)
}
}
s.in = min
s.out = nil
}
// Clone returns a copy of s that shares no memory with s.
func (s *IPSetBuilder) Clone() *IPSetBuilder {
return &IPSetBuilder{
in: append([]IPRange(nil), s.in...),
out: append([]IPRange(nil), s.out...),
}
}
func (s *IPSetBuilder) addError(msg string, args ...interface{}) {
se := new(stacktraceErr)
// Skip three frames: runtime.Callers, addError, and the IPSetBuilder
// method that called addError (such as IPSetBuilder.Add).
// The resulting stack trace ends at the line in the user's
// code where they called into netaddr.
n := runtime.Callers(3, se.pcs[:])
se.at = se.pcs[:n]
se.err = fmt.Errorf(msg, args...)
s.errs = append(s.errs, se)
}
// Add adds ip to s.
func (s *IPSetBuilder) Add(ip netip.Addr) {
if !ip.IsValid() {
s.addError("Add(IP{})")
return
}
s.AddRange(IPRangeFrom(ip, ip))
}
// AddPrefix adds all IPs in p to s.
func (s *IPSetBuilder) AddPrefix(p netip.Prefix) {
if r := RangeOfPrefix(p); r.IsValid() {
s.AddRange(r)
} else {
s.addError("AddPrefix(%v/%v)", p.Addr(), p.Bits())
}
}
// AddRange adds r to s.
// If r is not Valid, AddRange does nothing.
func (s *IPSetBuilder) AddRange(r IPRange) {
if !r.IsValid() {
s.addError("AddRange(%v-%v)", r.From(), r.To())
return
}
// If there are any removals (s.out), then we need to compact the set
// first to get the order right.
if len(s.out) > 0 {
s.normalize()
}
s.in = append(s.in, r)
}
// AddSet adds all IPs in b to s.
func (s *IPSetBuilder) AddSet(b *IPSet) {
if b == nil {
return
}
for _, r := range b.rr {
s.AddRange(r)
}
}
// Remove removes ip from s.
func (s *IPSetBuilder) Remove(ip netip.Addr) {
if !ip.IsValid() {
s.addError("Remove(IP{})")
} else {
s.RemoveRange(IPRangeFrom(ip, ip))
}
}
// RemovePrefix removes all IPs in p from s.
func (s *IPSetBuilder) RemovePrefix(p netip.Prefix) {
if r := RangeOfPrefix(p); r.IsValid() {
s.RemoveRange(r)
} else {
s.addError("RemovePrefix(%v/%v)", p.Addr(), p.Bits())
}
}
// RemoveRange removes all IPs in r from s.
func (s *IPSetBuilder) RemoveRange(r IPRange) {
if r.IsValid() {
s.out = append(s.out, r)
} else {
s.addError("RemoveRange(%v-%v)", r.From(), r.To())
}
}
// RemoveSet removes all IPs in o from s.
func (s *IPSetBuilder) RemoveSet(b *IPSet) {
if b == nil {
return
}
for _, r := range b.rr {
s.RemoveRange(r)
}
}
// removeBuilder removes all IPs in b from s.
func (s *IPSetBuilder) removeBuilder(b *IPSetBuilder) {
b.normalize()
for _, r := range b.in {
s.RemoveRange(r)
}
}
// Complement updates s to contain the complement of its current
// contents.
func (s *IPSetBuilder) Complement() {
s.normalize()
s.out = s.in
s.in = []IPRange{
RangeOfPrefix(netip.PrefixFrom(netip.AddrFrom4([4]byte{}), 0)),
RangeOfPrefix(netip.PrefixFrom(netip.IPv6Unspecified(), 0)),
}
}
// Intersect updates s to the set intersection of s and b.
func (s *IPSetBuilder) Intersect(b *IPSet) {
var o IPSetBuilder
o.Complement()
o.RemoveSet(b)
s.removeBuilder(&o)
}
func discardf(format string, args ...interface{}) {}
// debugf is reassigned by tests.
var debugf = discardf
// IPSet returns an immutable IPSet representing the current state of s.
//
// Most IPSetBuilder methods do not return errors.
// Rather, the builder ignores any invalid inputs (such as an invalid IPPrefix),
// and accumulates a list of any such errors that it encountered.
//
// IPSet also reports any such accumulated errors.
// Even if the returned error is non-nil, the returned IPSet is usable
// and contains all modifications made with valid inputs.
//
// The builder remains usable after calling IPSet.
// Calling IPSet clears any accumulated errors.
func (s *IPSetBuilder) IPSet() (*IPSet, error) {
s.normalize()
ret := &IPSet{
rr: append([]IPRange{}, s.in...),
}
if len(s.errs) == 0 {
return ret, nil
} else {
errs := s.errs
s.errs = nil
return ret, errs
}
}
// IPSet represents a set of IP addresses.
//
// IPSet is safe for concurrent use.
// The zero value is a valid value representing a set of no IPs.
// Use IPSetBuilder to construct IPSets.
type IPSet struct {
// rr is the set of IPs that belong to this IPSet. The IPRanges
// are normalized according to IPSetBuilder.normalize, meaning
// they are a sorted, minimal representation (no overlapping
// ranges, no contiguous ranges). The implementation of various
// methods rely on this property.
rr []IPRange
}
// Ranges returns the minimum and sorted set of IP
// ranges that covers s.
func (s *IPSet) Ranges() []IPRange {
return append([]IPRange{}, s.rr...)
}
// Prefixes returns the minimum and sorted set of IP prefixes
// that covers s.
func (s *IPSet) Prefixes() []netip.Prefix {
out := make([]netip.Prefix, 0, len(s.rr))
for _, r := range s.rr {
out = append(out, r.Prefixes()...)
}
return out
}
// Equal reports whether s and o represent the same set of IP
// addresses.
func (s *IPSet) Equal(o *IPSet) bool {
if len(s.rr) != len(o.rr) {
return false
}
for i := range s.rr {
if s.rr[i] != o.rr[i] {
return false
}
}
return true
}
// Contains reports whether ip is in s.
// If ip has an IPv6 zone, Contains returns false,
// because IPSets do not track zones.
func (s *IPSet) Contains(ip netip.Addr) bool {
if ip.Zone() != "" {
return false
}
// TODO: data structure permitting more efficient lookups:
// https://github.com/inetaf/netaddr/issues/139
i := sort.Search(len(s.rr), func(i int) bool {
return ip.Less(s.rr[i].from)
})
if i == 0 {
return false
}
i--
return s.rr[i].contains(ip)
}
// ContainsRange reports whether all IPs in r are in s.
func (s *IPSet) ContainsRange(r IPRange) bool {
for _, x := range s.rr {
if r.coveredBy(x) {
return true
}
}
return false
}
// ContainsPrefix reports whether all IPs in p are in s.
func (s *IPSet) ContainsPrefix(p netip.Prefix) bool {
return s.ContainsRange(RangeOfPrefix(p))
}
// Overlaps reports whether any IP in b is also in s.
func (s *IPSet) Overlaps(b *IPSet) bool {
// TODO: sorted ranges lets us do this in O(n+m)
for _, r := range s.rr {
for _, or := range b.rr {
if r.Overlaps(or) {
return true
}
}
}
return false
}
// OverlapsRange reports whether any IP in r is also in s.
func (s *IPSet) OverlapsRange(r IPRange) bool {
// TODO: sorted ranges lets us do this more efficiently.
for _, x := range s.rr {
if x.Overlaps(r) {
return true
}
}
return false
}
// OverlapsPrefix reports whether any IP in p is also in s.
func (s *IPSet) OverlapsPrefix(p netip.Prefix) bool {
return s.OverlapsRange(RangeOfPrefix(p))
}
// RemoveFreePrefix splits s into a Prefix of length bitLen and a new
// IPSet with that prefix removed.
//
// If no contiguous prefix of length bitLen exists in s,
// RemoveFreePrefix returns ok=false.
func (s *IPSet) RemoveFreePrefix(bitLen uint8) (p netip.Prefix, newSet *IPSet, ok bool) {
var bestFit netip.Prefix
for _, r := range s.rr {
for _, prefix := range r.Prefixes() {
if uint8(prefix.Bits()) > bitLen {
continue
}
if !bestFit.Addr().IsValid() || prefix.Bits() > bestFit.Bits() {
bestFit = prefix
if uint8(bestFit.Bits()) == bitLen {
// exact match, done.
break
}
}
}
}
if !bestFit.Addr().IsValid() {
return netip.Prefix{}, s, false
}
prefix := netip.PrefixFrom(bestFit.Addr(), int(bitLen))
var b IPSetBuilder
b.AddSet(s)
b.RemovePrefix(prefix)
newSet, _ = b.IPSet()
return prefix, newSet, true
}
type multiErr []error
func (e multiErr) Error() string {
var ret []string
for _, err := range e {
ret = append(ret, err.Error())
}
return strings.Join(ret, "; ")
}
// A stacktraceErr combines an error with a stack trace.
type stacktraceErr struct {
pcs [16]uintptr // preallocated array of PCs
at []uintptr // stack trace whence the error
err error // underlying error
}
func (e *stacktraceErr) Error() string {
frames := runtime.CallersFrames(e.at)
buf := new(strings.Builder)
buf.WriteString(e.err.Error())
buf.WriteString(" @ ")
for {
frame, more := frames.Next()
if !more {
break
}
fmt.Fprintf(buf, "%s:%d ", frame.File, frame.Line)
}
return strings.TrimSpace(buf.String())
}
func (e *stacktraceErr) Unwrap() error {
return e.err
}

141
vendor/go4.org/netipx/mask6.go generated vendored Normal file
View File

@ -0,0 +1,141 @@
// Copyright 2021 The Inet.Af AUTHORS. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package netipx
// mask6 are bitmasks with the topmost n bits of a
// 128-bit number, where n is the array index.
//
// generated with https://play.golang.org/p/64XKxaUSa_9
var mask6 = [...]uint128{
0: {0x0000000000000000, 0x0000000000000000},
1: {0x8000000000000000, 0x0000000000000000},
2: {0xc000000000000000, 0x0000000000000000},
3: {0xe000000000000000, 0x0000000000000000},
4: {0xf000000000000000, 0x0000000000000000},
5: {0xf800000000000000, 0x0000000000000000},
6: {0xfc00000000000000, 0x0000000000000000},
7: {0xfe00000000000000, 0x0000000000000000},
8: {0xff00000000000000, 0x0000000000000000},
9: {0xff80000000000000, 0x0000000000000000},
10: {0xffc0000000000000, 0x0000000000000000},
11: {0xffe0000000000000, 0x0000000000000000},
12: {0xfff0000000000000, 0x0000000000000000},
13: {0xfff8000000000000, 0x0000000000000000},
14: {0xfffc000000000000, 0x0000000000000000},
15: {0xfffe000000000000, 0x0000000000000000},
16: {0xffff000000000000, 0x0000000000000000},
17: {0xffff800000000000, 0x0000000000000000},
18: {0xffffc00000000000, 0x0000000000000000},
19: {0xffffe00000000000, 0x0000000000000000},
20: {0xfffff00000000000, 0x0000000000000000},
21: {0xfffff80000000000, 0x0000000000000000},
22: {0xfffffc0000000000, 0x0000000000000000},
23: {0xfffffe0000000000, 0x0000000000000000},
24: {0xffffff0000000000, 0x0000000000000000},
25: {0xffffff8000000000, 0x0000000000000000},
26: {0xffffffc000000000, 0x0000000000000000},
27: {0xffffffe000000000, 0x0000000000000000},
28: {0xfffffff000000000, 0x0000000000000000},
29: {0xfffffff800000000, 0x0000000000000000},
30: {0xfffffffc00000000, 0x0000000000000000},
31: {0xfffffffe00000000, 0x0000000000000000},
32: {0xffffffff00000000, 0x0000000000000000},
33: {0xffffffff80000000, 0x0000000000000000},
34: {0xffffffffc0000000, 0x0000000000000000},
35: {0xffffffffe0000000, 0x0000000000000000},
36: {0xfffffffff0000000, 0x0000000000000000},
37: {0xfffffffff8000000, 0x0000000000000000},
38: {0xfffffffffc000000, 0x0000000000000000},
39: {0xfffffffffe000000, 0x0000000000000000},
40: {0xffffffffff000000, 0x0000000000000000},
41: {0xffffffffff800000, 0x0000000000000000},
42: {0xffffffffffc00000, 0x0000000000000000},
43: {0xffffffffffe00000, 0x0000000000000000},
44: {0xfffffffffff00000, 0x0000000000000000},
45: {0xfffffffffff80000, 0x0000000000000000},
46: {0xfffffffffffc0000, 0x0000000000000000},
47: {0xfffffffffffe0000, 0x0000000000000000},
48: {0xffffffffffff0000, 0x0000000000000000},
49: {0xffffffffffff8000, 0x0000000000000000},
50: {0xffffffffffffc000, 0x0000000000000000},
51: {0xffffffffffffe000, 0x0000000000000000},
52: {0xfffffffffffff000, 0x0000000000000000},
53: {0xfffffffffffff800, 0x0000000000000000},
54: {0xfffffffffffffc00, 0x0000000000000000},
55: {0xfffffffffffffe00, 0x0000000000000000},
56: {0xffffffffffffff00, 0x0000000000000000},
57: {0xffffffffffffff80, 0x0000000000000000},
58: {0xffffffffffffffc0, 0x0000000000000000},
59: {0xffffffffffffffe0, 0x0000000000000000},
60: {0xfffffffffffffff0, 0x0000000000000000},
61: {0xfffffffffffffff8, 0x0000000000000000},
62: {0xfffffffffffffffc, 0x0000000000000000},
63: {0xfffffffffffffffe, 0x0000000000000000},
64: {0xffffffffffffffff, 0x0000000000000000},
65: {0xffffffffffffffff, 0x8000000000000000},
66: {0xffffffffffffffff, 0xc000000000000000},
67: {0xffffffffffffffff, 0xe000000000000000},
68: {0xffffffffffffffff, 0xf000000000000000},
69: {0xffffffffffffffff, 0xf800000000000000},
70: {0xffffffffffffffff, 0xfc00000000000000},
71: {0xffffffffffffffff, 0xfe00000000000000},
72: {0xffffffffffffffff, 0xff00000000000000},
73: {0xffffffffffffffff, 0xff80000000000000},
74: {0xffffffffffffffff, 0xffc0000000000000},
75: {0xffffffffffffffff, 0xffe0000000000000},
76: {0xffffffffffffffff, 0xfff0000000000000},
77: {0xffffffffffffffff, 0xfff8000000000000},
78: {0xffffffffffffffff, 0xfffc000000000000},
79: {0xffffffffffffffff, 0xfffe000000000000},
80: {0xffffffffffffffff, 0xffff000000000000},
81: {0xffffffffffffffff, 0xffff800000000000},
82: {0xffffffffffffffff, 0xffffc00000000000},
83: {0xffffffffffffffff, 0xffffe00000000000},
84: {0xffffffffffffffff, 0xfffff00000000000},
85: {0xffffffffffffffff, 0xfffff80000000000},
86: {0xffffffffffffffff, 0xfffffc0000000000},
87: {0xffffffffffffffff, 0xfffffe0000000000},
88: {0xffffffffffffffff, 0xffffff0000000000},
89: {0xffffffffffffffff, 0xffffff8000000000},
90: {0xffffffffffffffff, 0xffffffc000000000},
91: {0xffffffffffffffff, 0xffffffe000000000},
92: {0xffffffffffffffff, 0xfffffff000000000},
93: {0xffffffffffffffff, 0xfffffff800000000},
94: {0xffffffffffffffff, 0xfffffffc00000000},
95: {0xffffffffffffffff, 0xfffffffe00000000},
96: {0xffffffffffffffff, 0xffffffff00000000},
97: {0xffffffffffffffff, 0xffffffff80000000},
98: {0xffffffffffffffff, 0xffffffffc0000000},
99: {0xffffffffffffffff, 0xffffffffe0000000},
100: {0xffffffffffffffff, 0xfffffffff0000000},
101: {0xffffffffffffffff, 0xfffffffff8000000},
102: {0xffffffffffffffff, 0xfffffffffc000000},
103: {0xffffffffffffffff, 0xfffffffffe000000},
104: {0xffffffffffffffff, 0xffffffffff000000},
105: {0xffffffffffffffff, 0xffffffffff800000},
106: {0xffffffffffffffff, 0xffffffffffc00000},
107: {0xffffffffffffffff, 0xffffffffffe00000},
108: {0xffffffffffffffff, 0xfffffffffff00000},
109: {0xffffffffffffffff, 0xfffffffffff80000},
110: {0xffffffffffffffff, 0xfffffffffffc0000},
111: {0xffffffffffffffff, 0xfffffffffffe0000},
112: {0xffffffffffffffff, 0xffffffffffff0000},
113: {0xffffffffffffffff, 0xffffffffffff8000},
114: {0xffffffffffffffff, 0xffffffffffffc000},
115: {0xffffffffffffffff, 0xffffffffffffe000},
116: {0xffffffffffffffff, 0xfffffffffffff000},
117: {0xffffffffffffffff, 0xfffffffffffff800},
118: {0xffffffffffffffff, 0xfffffffffffffc00},
119: {0xffffffffffffffff, 0xfffffffffffffe00},
120: {0xffffffffffffffff, 0xffffffffffffff00},
121: {0xffffffffffffffff, 0xffffffffffffff80},
122: {0xffffffffffffffff, 0xffffffffffffffc0},
123: {0xffffffffffffffff, 0xffffffffffffffe0},
124: {0xffffffffffffffff, 0xfffffffffffffff0},
125: {0xffffffffffffffff, 0xfffffffffffffff8},
126: {0xffffffffffffffff, 0xfffffffffffffffc},
127: {0xffffffffffffffff, 0xfffffffffffffffe},
128: {0xffffffffffffffff, 0xffffffffffffffff},
}

547
vendor/go4.org/netipx/netipx.go generated vendored Normal file
View File

@ -0,0 +1,547 @@
// Copyright 2020 The Inet.Af AUTHORS. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package netipx contains code and types that were left behind when
// the old inet.af/netaddr package moved to the standard library in Go
// 1.18 as net/netip.
package netipx // import "go4.org/netipx"
import (
"errors"
"fmt"
"math"
"net"
"net/netip"
"sort"
"strings"
)
// FromStdIP returns an IP from the standard library's IP type.
//
// If std is invalid, ok is false.
//
// FromStdIP implicitly unmaps IPv6-mapped IPv4 addresses. That is, if
// len(std) == 16 and contains an IPv4 address, only the IPv4 part is
// returned, without the IPv6 wrapper. This is the common form returned by
// the standard library's ParseIP: https://play.golang.org/p/qdjylUkKWxl.
// To convert a standard library IP without the implicit unmapping, use
// netip.AddrFromSlice.
func FromStdIP(std net.IP) (ip netip.Addr, ok bool) {
ret, ok := netip.AddrFromSlice(std)
return ret.Unmap(), ok
}
// MustFromStdIP is like FromStdIP, but it panics if std is invalid.
func MustFromStdIP(std net.IP) netip.Addr {
ret, ok := netip.AddrFromSlice(std)
if !ok {
panic("not a valid IP address")
}
return ret.Unmap()
}
// FromStdIPRaw returns an IP from the standard library's IP type.
// If std is invalid, ok is false.
// Unlike FromStdIP, FromStdIPRaw does not do an implicit Unmap if
// len(std) == 16 and contains an IPv6-mapped IPv4 address.
//
// Deprecated: use netip.AddrFromSlice instead.
func FromStdIPRaw(std net.IP) (ip netip.Addr, ok bool) {
return netip.AddrFromSlice(std)
}
// AddrNext returns the IP following ip.
// If there is none, it returns the IP zero value.
//
// Deprecated: use netip.Addr.Next instead.
func AddrNext(ip netip.Addr) netip.Addr {
addr := u128From16(ip.As16()).addOne()
if ip.Is4() {
if uint32(addr.lo) == 0 {
// Overflowed.
return netip.Addr{}
}
return addr.IP4()
} else {
if addr.isZero() {
// Overflowed
return netip.Addr{}
}
return addr.IP6().WithZone(ip.Zone())
}
}
// AddrPrior returns the IP before ip.
// If there is none, it returns the IP zero value.
//
// Deprecated: use netip.Addr.Prev instead.
func AddrPrior(ip netip.Addr) netip.Addr {
addr := u128From16(ip.As16())
if ip.Is4() {
if uint32(addr.lo) == 0 {
return netip.Addr{}
}
return addr.subOne().IP4()
} else {
if addr.isZero() {
return netip.Addr{}
}
return addr.subOne().IP6().WithZone(ip.Zone())
}
}
// FromStdAddr maps the components of a standard library TCPAddr or
// UDPAddr into an IPPort.
func FromStdAddr(stdIP net.IP, port int, zone string) (_ netip.AddrPort, ok bool) {
ip, ok := FromStdIP(stdIP)
if !ok || port < 0 || port > math.MaxUint16 {
return netip.AddrPort{}, false
}
ip = ip.Unmap()
if zone != "" {
if ip.Is4() {
ok = false
return
}
ip = ip.WithZone(zone)
}
return netip.AddrPortFrom(ip, uint16(port)), true
}
// FromStdIPNet returns an netip.Prefix from the standard library's IPNet type.
// If std is invalid, ok is false.
func FromStdIPNet(std *net.IPNet) (prefix netip.Prefix, ok bool) {
ip, ok := FromStdIP(std.IP)
if !ok {
return netip.Prefix{}, false
}
if l := len(std.Mask); l != net.IPv4len && l != net.IPv6len {
// Invalid mask.
return netip.Prefix{}, false
}
ones, bits := std.Mask.Size()
if ones == 0 && bits == 0 {
// IPPrefix does not support non-contiguous masks.
return netip.Prefix{}, false
}
return netip.PrefixFrom(ip, ones), true
}
// RangeOfPrefix returns the inclusive range of IPs that p covers.
//
// If p is zero or otherwise invalid, Range returns the zero value.
func RangeOfPrefix(p netip.Prefix) IPRange {
p = p.Masked()
if !p.IsValid() {
return IPRange{}
}
return IPRangeFrom(p.Addr(), PrefixLastIP(p))
}
// PrefixIPNet returns the net.IPNet representation of an netip.Prefix.
// The returned value is always non-nil.
// Any zone identifier is dropped in the conversion.
func PrefixIPNet(p netip.Prefix) *net.IPNet {
if !p.IsValid() {
return &net.IPNet{}
}
return &net.IPNet{
IP: p.Addr().AsSlice(),
Mask: net.CIDRMask(p.Bits(), p.Addr().BitLen()),
}
}
// AddrIPNet returns the net.IPNet representation of an netip.Addr
// with a mask corresponding to the addresses's bit length.
// The returned value is always non-nil.
// Any zone identifier is dropped in the conversion.
func AddrIPNet(addr netip.Addr) *net.IPNet {
if !addr.IsValid() {
return &net.IPNet{}
}
return &net.IPNet{
IP: addr.AsSlice(),
Mask: net.CIDRMask(addr.BitLen(), addr.BitLen()),
}
}
// PrefixLastIP returns the last IP in the prefix.
func PrefixLastIP(p netip.Prefix) netip.Addr {
if !p.IsValid() {
return netip.Addr{}
}
a16 := p.Addr().As16()
var off uint8
var bits uint8 = 128
if p.Addr().Is4() {
off = 12
bits = 32
}
for b := uint8(p.Bits()); b < bits; b++ {
byteNum, bitInByte := b/8, 7-(b%8)
a16[off+byteNum] |= 1 << uint(bitInByte)
}
if p.Addr().Is4() {
return netip.AddrFrom16(a16).Unmap()
} else {
return netip.AddrFrom16(a16) // doesn't unmap
}
}
// IPRange represents an inclusive range of IP addresses
// from the same address family.
//
// The From and To IPs are inclusive bounds, with both included in the
// range.
//
// To be valid, the From and To values must be non-zero, have matching
// address families (IPv4 vs IPv6), and From must be less than or equal to To.
// IPv6 zones are stripped out and ignored.
// An invalid range may be ignored.
type IPRange struct {
// from is the initial IP address in the range.
from netip.Addr
// to is the final IP address in the range.
to netip.Addr
}
// IPRangeFrom returns an IPRange from from to to.
// It does not allocate.
func IPRangeFrom(from, to netip.Addr) IPRange {
return IPRange{
from: from.WithZone(""),
to: to.WithZone(""),
}
}
// From returns the lower bound of r.
func (r IPRange) From() netip.Addr { return r.from }
// To returns the upper bound of r.
func (r IPRange) To() netip.Addr { return r.to }
// ParseIPRange parses a range out of two IPs separated by a hyphen.
//
// It returns an error if the range is not valid.
func ParseIPRange(s string) (IPRange, error) {
var r IPRange
h := strings.IndexByte(s, '-')
if h == -1 {
return r, fmt.Errorf("no hyphen in range %q", s)
}
from, to := s[:h], s[h+1:]
var err error
r.from, err = netip.ParseAddr(from)
if err != nil {
return r, fmt.Errorf("invalid From IP %q in range %q", from, s)
}
r.from = r.from.WithZone("")
r.to, err = netip.ParseAddr(to)
if err != nil {
return r, fmt.Errorf("invalid To IP %q in range %q", to, s)
}
r.to = r.to.WithZone("")
if !r.IsValid() {
return r, fmt.Errorf("range %v to %v not valid", r.from, r.to)
}
return r, nil
}
// MustParseIPRange calls ParseIPRange(s) and panics on error.
// It is intended for use in tests with hard-coded strings.
func MustParseIPRange(s string) IPRange {
r, err := ParseIPRange(s)
if err != nil {
panic(err)
}
return r
}
// String returns a string representation of the range.
//
// For a valid range, the form is "From-To" with a single hyphen
// separating the IPs, the same format recognized by
// ParseIPRange.
func (r IPRange) String() string {
if r.IsValid() {
return fmt.Sprintf("%s-%s", r.from, r.to)
}
if !r.from.IsValid() || !r.to.IsValid() {
return "zero IPRange"
}
return "invalid IPRange"
}
// AppendTo appends a text encoding of r,
// as generated by MarshalText,
// to b and returns the extended buffer.
func (r IPRange) AppendTo(b []byte) []byte {
if r.IsZero() {
return b
}
b = r.from.AppendTo(b)
b = append(b, '-')
b = r.to.AppendTo(b)
return b
}
// MarshalText implements the encoding.TextMarshaler interface,
// The encoding is the same as returned by String, with one exception:
// If ip is the zero value, the encoding is the empty string.
func (r IPRange) MarshalText() ([]byte, error) {
if r.IsZero() {
return []byte(""), nil
}
var max int
if r.from.Is4() {
max = len("255.255.255.255-255.255.255.255")
} else {
max = len("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
}
b := make([]byte, 0, max)
return r.AppendTo(b), nil
}
// UnmarshalText implements the encoding.TextUnmarshaler interface.
// The IP range is expected in a form accepted by ParseIPRange.
// It returns an error if *r is not the IPRange zero value.
func (r *IPRange) UnmarshalText(text []byte) error {
if *r != (IPRange{}) {
return errors.New("refusing to Unmarshal into non-zero IPRange")
}
if len(text) == 0 {
return nil
}
var err error
*r, err = ParseIPRange(string(text))
return err
}
// IsZero reports whether r is the zero value of the IPRange type.
func (r IPRange) IsZero() bool {
return r == IPRange{}
}
// IsValid reports whether r.From() and r.To() are both non-zero and
// obey the documented requirements: address families match, and From
// is less than or equal to To.
func (r IPRange) IsValid() bool {
return r.from.IsValid() &&
r.from.BitLen() == r.to.BitLen() &&
r.from.Zone() == r.to.Zone() &&
!r.to.Less(r.from)
}
// Valid reports whether r.From() and r.To() are both non-zero and
// obey the documented requirements: address families match, and From
// is less than or equal to To.
//
// Deprecated: use the correctly named and identical IsValid method instead.
func (r IPRange) Valid() bool { return r.IsValid() }
// Contains reports whether the range r includes addr.
//
// An invalid range always reports false.
//
// If ip has an IPv6 zone, Contains returns false,
// because IPPrefixes strip zones.
func (r IPRange) Contains(addr netip.Addr) bool {
return r.IsValid() && addr.Zone() == "" && r.contains(addr)
}
// contains is like Contains, but without the validity check.
// addr must not have a zone.
func (r IPRange) contains(addr netip.Addr) bool {
return r.from.Compare(addr) <= 0 && r.to.Compare(addr) >= 0
}
// less reports whether r is "before" other. It is before if r.From()
// is before other.From(). If they're equal, then the larger range
// (higher To()) comes first.
func (r IPRange) less(other IPRange) bool {
if cmp := r.from.Compare(other.from); cmp != 0 {
return cmp < 0
}
return other.to.Less(r.to)
}
// entirelyBefore returns whether r lies entirely before other in IP
// space.
func (r IPRange) entirelyBefore(other IPRange) bool {
return r.to.Less(other.from)
}
func lessOrEq(ip, ip2 netip.Addr) bool { return ip.Compare(ip2) <= 0 }
// entirelyWithin returns whether r is entirely contained within
// other.
func (r IPRange) coveredBy(other IPRange) bool {
return lessOrEq(other.from, r.from) && lessOrEq(r.to, other.to)
}
// inMiddleOf returns whether r is inside other, but not touching the
// edges of other.
func (r IPRange) inMiddleOf(other IPRange) bool {
return other.from.Less(r.from) && r.to.Less(other.to)
}
// overlapsStartOf returns whether r entirely overlaps the start of
// other, but not all of other.
func (r IPRange) overlapsStartOf(other IPRange) bool {
return lessOrEq(r.from, other.from) && r.to.Less(other.to)
}
// overlapsEndOf returns whether r entirely overlaps the end of
// other, but not all of other.
func (r IPRange) overlapsEndOf(other IPRange) bool {
return other.from.Less(r.from) && lessOrEq(other.to, r.to)
}
// mergeIPRanges returns the minimum and sorted set of IP ranges that
// cover r.
func mergeIPRanges(rr []IPRange) (out []IPRange, valid bool) {
// Always return a copy of r, to avoid aliasing slice memory in
// the caller.
switch len(rr) {
case 0:
return nil, true
case 1:
return []IPRange{rr[0]}, true
}
sort.Slice(rr, func(i, j int) bool { return rr[i].less(rr[j]) })
out = make([]IPRange, 1, len(rr))
out[0] = rr[0]
for _, r := range rr[1:] {
prev := &out[len(out)-1]
switch {
case !r.IsValid():
// Invalid ranges make no sense to merge, refuse to
// perform.
return nil, false
case prev.to.Next() == r.from:
// prev and r touch, merge them.
//
// prev r
// f------tf-----t
prev.to = r.to
case prev.to.Less(r.from):
// No overlap and not adjacent (per previous case), no
// merging possible.
//
// prev r
// f------t f-----t
out = append(out, r)
case prev.to.Less(r.to):
// Partial overlap, update prev
//
// prev
// f------t
// f-----t
// r
prev.to = r.to
default:
// r entirely contained in prev, nothing to do.
//
// prev
// f--------t
// f-----t
// r
}
}
return out, true
}
// Overlaps reports whether p and o overlap at all.
//
// If p and o are of different address families or either are invalid,
// it reports false.
func (r IPRange) Overlaps(o IPRange) bool {
return r.IsValid() &&
o.IsValid() &&
r.from.Compare(o.to) <= 0 &&
o.from.Compare(r.to) <= 0
}
// prefixMaker returns a address-family-corrected IPPrefix from a and bits,
// where the input bits is always in the IPv6-mapped form for IPv4 addresses.
type prefixMaker func(a uint128, bits uint8) netip.Prefix
// Prefixes returns the set of IPPrefix entries that covers r.
//
// If either of r's bounds are invalid, in the wrong order, or if
// they're of different address families, then Prefixes returns nil.
//
// Prefixes necessarily allocates. See AppendPrefixes for a version that uses
// memory you provide.
func (r IPRange) Prefixes() []netip.Prefix {
return r.AppendPrefixes(nil)
}
// AppendPrefixes is an append version of IPRange.Prefixes. It appends
// the netip.Prefix entries that cover r to dst.
func (r IPRange) AppendPrefixes(dst []netip.Prefix) []netip.Prefix {
if !r.IsValid() {
return nil
}
return appendRangePrefixes(dst, r.prefixFrom128AndBits, u128From16(r.from.As16()), u128From16(r.to.As16()))
}
func (r IPRange) prefixFrom128AndBits(a uint128, bits uint8) netip.Prefix {
var ip netip.Addr
if r.from.Is4() {
bits -= 12 * 8
ip = a.IP4()
} else {
ip = a.IP6()
}
return netip.PrefixFrom(ip, int(bits))
}
// aZeroBSet is whether, after the common bits, a is all zero bits and
// b is all set (one) bits.
func comparePrefixes(a, b uint128) (common uint8, aZeroBSet bool) {
common = a.commonPrefixLen(b)
// See whether a and b, after their common shared bits, end
// in all zero bits or all one bits, respectively.
if common == 128 {
return common, true
}
m := mask6[common]
return common, (a.xor(a.and(m)).isZero() &&
b.or(m) == uint128{^uint64(0), ^uint64(0)})
}
// Prefix returns r as an IPPrefix, if it can be presented exactly as such.
// If r is not valid or is not exactly equal to one prefix, ok is false.
func (r IPRange) Prefix() (p netip.Prefix, ok bool) {
if !r.IsValid() {
return
}
from128 := u128From16(r.from.As16())
to128 := u128From16(r.to.As16())
if common, ok := comparePrefixes(from128, to128); ok {
return r.prefixFrom128AndBits(from128, common), true
}
return
}
func appendRangePrefixes(dst []netip.Prefix, makePrefix prefixMaker, a, b uint128) []netip.Prefix {
common, ok := comparePrefixes(a, b)
if ok {
// a to b represents a whole range, like 10.50.0.0/16.
// (a being 10.50.0.0 and b being 10.50.255.255)
return append(dst, makePrefix(a, common))
}
// Otherwise recursively do both halves.
dst = appendRangePrefixes(dst, makePrefix, a, a.bitsSetFrom(common+1))
dst = appendRangePrefixes(dst, makePrefix, b.bitsClearedFrom(common+1), b)
return dst
}

106
vendor/go4.org/netipx/uint128.go generated vendored Normal file
View File

@ -0,0 +1,106 @@
// Copyright 2020 The Inet.Af AUTHORS. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package netipx
import (
"encoding/binary"
"math/bits"
"net/netip"
)
// uint128 represents a uint128 using two uint64s.
//
// When the methods below mention a bit number, bit 0 is the most
// significant bit (in hi) and bit 127 is the lowest (lo&1).
type uint128 struct {
hi uint64
lo uint64
}
func u128From16(a [16]byte) uint128 {
return uint128{
binary.BigEndian.Uint64(a[:8]),
binary.BigEndian.Uint64(a[8:]),
}
}
func (u uint128) IP6() netip.Addr {
var a [16]byte
binary.BigEndian.PutUint64(a[:8], u.hi)
binary.BigEndian.PutUint64(a[8:], u.lo)
return netip.AddrFrom16(a)
}
func (u uint128) IP4() netip.Addr {
var a [8]byte
binary.BigEndian.PutUint64(a[:], u.lo)
return netip.AddrFrom4([4]byte{a[4], a[5], a[6], a[7]})
}
// isZero reports whether u == 0.
//
// It's faster than u == (uint128{}) because the compiler (as of Go
// 1.15/1.16b1) doesn't do this trick and instead inserts a branch in
// its eq alg's generated code.
func (u uint128) isZero() bool { return u.hi|u.lo == 0 }
// and returns the bitwise AND of u and m (u&m).
func (u uint128) and(m uint128) uint128 {
return uint128{u.hi & m.hi, u.lo & m.lo}
}
// xor returns the bitwise XOR of u and m (u^m).
func (u uint128) xor(m uint128) uint128 {
return uint128{u.hi ^ m.hi, u.lo ^ m.lo}
}
// or returns the bitwise OR of u and m (u|m).
func (u uint128) or(m uint128) uint128 {
return uint128{u.hi | m.hi, u.lo | m.lo}
}
// not returns the bitwise NOT of u.
func (u uint128) not() uint128 {
return uint128{^u.hi, ^u.lo}
}
// subOne returns u - 1.
func (u uint128) subOne() uint128 {
lo, borrow := bits.Sub64(u.lo, 1, 0)
return uint128{u.hi - borrow, lo}
}
// addOne returns u + 1.
func (u uint128) addOne() uint128 {
lo, carry := bits.Add64(u.lo, 1, 0)
return uint128{u.hi + carry, lo}
}
func u64CommonPrefixLen(a, b uint64) uint8 {
return uint8(bits.LeadingZeros64(a ^ b))
}
func (u uint128) commonPrefixLen(v uint128) (n uint8) {
if n = u64CommonPrefixLen(u.hi, v.hi); n == 64 {
n += u64CommonPrefixLen(u.lo, v.lo)
}
return
}
// func (u *uint128) halves() [2]*uint64 {
// return [2]*uint64{&u.hi, &u.lo}
// }
// bitsSetFrom returns a copy of u with the given bit
// and all subsequent ones set.
func (u uint128) bitsSetFrom(bit uint8) uint128 {
return u.or(mask6[bit].not())
}
// bitsClearedFrom returns a copy of u with the given bit
// and all subsequent ones cleared.
func (u uint128) bitsClearedFrom(bit uint8) uint128 {
return u.and(mask6[bit])
}

View File

@ -50,7 +50,7 @@ func (ih InvalidHashPrefixError) Error() string {
type InvalidCostError int
func (ic InvalidCostError) Error() string {
return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost))
return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), MinCost, MaxCost)
}
const (

View File

@ -251,7 +251,7 @@ type algorithmOpenSSHCertSigner struct {
// private key is held by signer. It returns an error if the public key in cert
// doesn't match the key used by signer.
func NewCertSigner(cert *Certificate, signer Signer) (Signer, error) {
if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 {
if !bytes.Equal(cert.Key.Marshal(), signer.PublicKey().Marshal()) {
return nil, errors.New("ssh: signer and cert have different public key")
}

View File

@ -15,7 +15,6 @@ import (
"fmt"
"hash"
"io"
"io/ioutil"
"golang.org/x/crypto/chacha20"
"golang.org/x/crypto/internal/poly1305"
@ -97,13 +96,13 @@ func streamCipherMode(skip int, createFunc func(key, iv []byte) (cipher.Stream,
// are not supported and will not be negotiated, even if explicitly requested in
// ClientConfig.Crypto.Ciphers.
var cipherModes = map[string]*cipherMode{
// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
// Ciphers from RFC 4344, which introduced many CTR-based ciphers. Algorithms
// are defined in the order specified in the RFC.
"aes128-ctr": {16, aes.BlockSize, streamCipherMode(0, newAESCTR)},
"aes192-ctr": {24, aes.BlockSize, streamCipherMode(0, newAESCTR)},
"aes256-ctr": {32, aes.BlockSize, streamCipherMode(0, newAESCTR)},
// Ciphers from RFC4345, which introduces security-improved arcfour ciphers.
// Ciphers from RFC 4345, which introduces security-improved arcfour ciphers.
// They are defined in the order specified in the RFC.
"arcfour128": {16, 0, streamCipherMode(1536, newRC4)},
"arcfour256": {32, 0, streamCipherMode(1536, newRC4)},
@ -111,7 +110,7 @@ var cipherModes = map[string]*cipherMode{
// Cipher defined in RFC 4253, which describes SSH Transport Layer Protocol.
// Note that this cipher is not safe, as stated in RFC 4253: "Arcfour (and
// RC4) has problems with weak keys, and should be used with caution."
// RFC4345 introduces improved versions of Arcfour.
// RFC 4345 introduces improved versions of Arcfour.
"arcfour": {16, 0, streamCipherMode(0, newRC4)},
// AEAD ciphers
@ -497,7 +496,7 @@ func (c *cbcCipher) readCipherPacket(seqNum uint32, r io.Reader) ([]byte, error)
// data, to make distinguishing between
// failing MAC and failing length check more
// difficult.
io.CopyN(ioutil.Discard, r, int64(c.oracleCamouflage))
io.CopyN(io.Discard, r, int64(c.oracleCamouflage))
}
}
return p, err
@ -642,7 +641,7 @@ const chacha20Poly1305ID = "chacha20-poly1305@openssh.com"
//
// https://tools.ietf.org/html/draft-josefsson-ssh-chacha20-poly1305-openssh-00
//
// the methods here also implement padding, which RFC4253 Section 6
// the methods here also implement padding, which RFC 4253 Section 6
// also requires of stream ciphers.
type chacha20Poly1305Cipher struct {
lengthKey [32]byte

View File

@ -10,6 +10,7 @@ import (
"fmt"
"io"
"math"
"strings"
"sync"
_ "crypto/sha1"
@ -118,6 +119,20 @@ func algorithmsForKeyFormat(keyFormat string) []string {
}
}
// supportedPubKeyAuthAlgos specifies the supported client public key
// authentication algorithms. Note that this doesn't include certificate types
// since those use the underlying algorithm. This list is sent to the client if
// it supports the server-sig-algs extension. Order is irrelevant.
var supportedPubKeyAuthAlgos = []string{
KeyAlgoED25519,
KeyAlgoSKED25519, KeyAlgoSKECDSA256,
KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
KeyAlgoRSASHA256, KeyAlgoRSASHA512, KeyAlgoRSA,
KeyAlgoDSA,
}
var supportedPubKeyAuthAlgosList = strings.Join(supportedPubKeyAuthAlgos, ",")
// unexpectedMessageError results when the SSH message that we received didn't
// match what we wanted.
func unexpectedMessageError(expected, got uint8) error {
@ -149,7 +164,7 @@ type directionAlgorithms struct {
// rekeyBytes returns a rekeying intervals in bytes.
func (a *directionAlgorithms) rekeyBytes() int64 {
// According to RFC4344 block ciphers should rekey after
// According to RFC 4344 block ciphers should rekey after
// 2^(BLOCKSIZE/4) blocks. For all AES flavors BLOCKSIZE is
// 128.
switch a.Cipher {
@ -158,7 +173,7 @@ func (a *directionAlgorithms) rekeyBytes() int64 {
}
// For others, stick with RFC4253 recommendation to rekey after 1 Gb of data.
// For others, stick with RFC 4253 recommendation to rekey after 1 Gb of data.
return 1 << 30
}

View File

@ -52,7 +52,7 @@ type Conn interface {
// SendRequest sends a global request, and returns the
// reply. If wantReply is true, it returns the response status
// and payload. See also RFC4254, section 4.
// and payload. See also RFC 4254, section 4.
SendRequest(name string, wantReply bool, payload []byte) (bool, []byte, error)
// OpenChannel tries to open an channel. If the request is

View File

@ -615,7 +615,8 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
return err
}
if t.sessionID == nil {
firstKeyExchange := t.sessionID == nil
if firstKeyExchange {
t.sessionID = result.H
}
result.SessionID = t.sessionID
@ -626,6 +627,24 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
if err = t.conn.writePacket([]byte{msgNewKeys}); err != nil {
return err
}
// On the server side, after the first SSH_MSG_NEWKEYS, send a SSH_MSG_EXT_INFO
// message with the server-sig-algs extension if the client supports it. See
// RFC 8308, Sections 2.4 and 3.1.
if !isClient && firstKeyExchange && contains(clientInit.KexAlgos, "ext-info-c") {
extInfo := &extInfoMsg{
NumExtensions: 1,
Payload: make([]byte, 0, 4+15+4+len(supportedPubKeyAuthAlgosList)),
}
extInfo.Payload = appendInt(extInfo.Payload, len("server-sig-algs"))
extInfo.Payload = append(extInfo.Payload, "server-sig-algs"...)
extInfo.Payload = appendInt(extInfo.Payload, len(supportedPubKeyAuthAlgosList))
extInfo.Payload = append(extInfo.Payload, supportedPubKeyAuthAlgosList...)
if err := t.conn.writePacket(Marshal(extInfo)); err != nil {
return err
}
}
if packet, err := t.conn.readPacket(); err != nil {
return err
} else if packet[0] != msgNewKeys {

View File

@ -184,7 +184,7 @@ func ParseKnownHosts(in []byte) (marker string, hosts []string, pubKey PublicKey
return "", nil, nil, "", nil, io.EOF
}
// ParseAuthorizedKeys parses a public key from an authorized_keys
// ParseAuthorizedKey parses a public key from an authorized_keys
// file used in OpenSSH according to the sshd(8) manual page.
func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []string, rest []byte, err error) {
for len(in) > 0 {

View File

@ -68,7 +68,7 @@ type kexInitMsg struct {
// See RFC 4253, section 8.
// Diffie-Helman
// Diffie-Hellman
const msgKexDHInit = 30
type kexDHInitMsg struct {

View File

@ -68,8 +68,16 @@ type ServerConfig struct {
// NoClientAuth is true if clients are allowed to connect without
// authenticating.
// To determine NoClientAuth at runtime, set NoClientAuth to true
// and the optional NoClientAuthCallback to a non-nil value.
NoClientAuth bool
// NoClientAuthCallback, if non-nil, is called when a user
// attempts to authenticate with auth method "none".
// NoClientAuth must also be set to true for this be used, or
// this func is unused.
NoClientAuthCallback func(ConnMetadata) (*Permissions, error)
// MaxAuthTries specifies the maximum number of authentication attempts
// permitted per connection. If set to a negative number, the number of
// attempts are unlimited. If set to zero, the number of attempts are limited
@ -283,15 +291,6 @@ func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error)
return perms, err
}
func isAcceptableAlgo(algo string) bool {
switch algo {
case KeyAlgoRSA, KeyAlgoRSASHA256, KeyAlgoRSASHA512, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, KeyAlgoSKECDSA256, KeyAlgoED25519, KeyAlgoSKED25519,
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoSKECDSA256v01, CertAlgoED25519v01, CertAlgoSKED25519v01:
return true
}
return false
}
func checkSourceAddress(addr net.Addr, sourceAddrs string) error {
if addr == nil {
return errors.New("ssh: no address known for client, but source-address match required")
@ -455,7 +454,11 @@ userAuthLoop:
switch userAuthReq.Method {
case "none":
if config.NoClientAuth {
authErr = nil
if config.NoClientAuthCallback != nil {
perms, authErr = config.NoClientAuthCallback(s)
} else {
authErr = nil
}
}
// allow initial attempt of 'none' without penalty
@ -502,7 +505,7 @@ userAuthLoop:
return nil, parseError(msgUserAuthRequest)
}
algo := string(algoBytes)
if !isAcceptableAlgo(algo) {
if !contains(supportedPubKeyAuthAlgos, underlyingAlgo(algo)) {
authErr = fmt.Errorf("ssh: algorithm %q not accepted", algo)
break
}
@ -560,7 +563,7 @@ userAuthLoop:
// algorithm name that corresponds to algo with
// sig.Format. This is usually the same, but
// for certs, the names differ.
if !isAcceptableAlgo(sig.Format) {
if !contains(supportedPubKeyAuthAlgos, sig.Format) {
authErr = fmt.Errorf("ssh: algorithm %q not accepted", sig.Format)
break
}

View File

@ -13,7 +13,6 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"sync"
)
@ -124,7 +123,7 @@ type Session struct {
// output and error.
//
// If either is nil, Run connects the corresponding file
// descriptor to an instance of ioutil.Discard. There is a
// descriptor to an instance of io.Discard. There is a
// fixed amount of buffering that is shared for the two streams.
// If either blocks it may eventually cause the remote
// command to block.
@ -506,7 +505,7 @@ func (s *Session) stdout() {
return
}
if s.Stdout == nil {
s.Stdout = ioutil.Discard
s.Stdout = io.Discard
}
s.copyFuncs = append(s.copyFuncs, func() error {
_, err := io.Copy(s.Stdout, s.ch)
@ -519,7 +518,7 @@ func (s *Session) stderr() {
return
}
if s.Stderr == nil {
s.Stderr = ioutil.Discard
s.Stderr = io.Discard
}
s.copyFuncs = append(s.copyFuncs, func() error {
_, err := io.Copy(s.Stderr, s.ch.Stderr())

View File

@ -27,7 +27,14 @@ func buildCommonHeaderMaps() {
"accept-language",
"accept-ranges",
"age",
"access-control-allow-credentials",
"access-control-allow-headers",
"access-control-allow-methods",
"access-control-allow-origin",
"access-control-expose-headers",
"access-control-max-age",
"access-control-request-headers",
"access-control-request-method",
"allow",
"authorization",
"cache-control",
@ -53,6 +60,7 @@ func buildCommonHeaderMaps() {
"link",
"location",
"max-forwards",
"origin",
"proxy-authenticate",
"proxy-authorization",
"range",
@ -68,6 +76,8 @@ func buildCommonHeaderMaps() {
"vary",
"via",
"www-authenticate",
"x-forwarded-for",
"x-forwarded-proto",
}
commonLowerHeader = make(map[string]string, len(common))
commonCanonHeader = make(map[string]string, len(common))
@ -85,3 +95,11 @@ func lowerHeader(v string) (lower string, ascii bool) {
}
return asciiToLower(v)
}
func canonicalHeader(v string) string {
buildCommonHeaderMapsOnce()
if s, ok := commonCanonHeader[v]; ok {
return s
}
return http.CanonicalHeaderKey(v)
}

188
vendor/golang.org/x/net/http2/hpack/static_table.go generated vendored Normal file
View File

@ -0,0 +1,188 @@
// go generate gen.go
// Code generated by the command above; DO NOT EDIT.
package hpack
var staticTable = &headerFieldTable{
evictCount: 0,
byName: map[string]uint64{
":authority": 1,
":method": 3,
":path": 5,
":scheme": 7,
":status": 14,
"accept-charset": 15,
"accept-encoding": 16,
"accept-language": 17,
"accept-ranges": 18,
"accept": 19,
"access-control-allow-origin": 20,
"age": 21,
"allow": 22,
"authorization": 23,
"cache-control": 24,
"content-disposition": 25,
"content-encoding": 26,
"content-language": 27,
"content-length": 28,
"content-location": 29,
"content-range": 30,
"content-type": 31,
"cookie": 32,
"date": 33,
"etag": 34,
"expect": 35,
"expires": 36,
"from": 37,
"host": 38,
"if-match": 39,
"if-modified-since": 40,
"if-none-match": 41,
"if-range": 42,
"if-unmodified-since": 43,
"last-modified": 44,
"link": 45,
"location": 46,
"max-forwards": 47,
"proxy-authenticate": 48,
"proxy-authorization": 49,
"range": 50,
"referer": 51,
"refresh": 52,
"retry-after": 53,
"server": 54,
"set-cookie": 55,
"strict-transport-security": 56,
"transfer-encoding": 57,
"user-agent": 58,
"vary": 59,
"via": 60,
"www-authenticate": 61,
},
byNameValue: map[pairNameValue]uint64{
{name: ":authority", value: ""}: 1,
{name: ":method", value: "GET"}: 2,
{name: ":method", value: "POST"}: 3,
{name: ":path", value: "/"}: 4,
{name: ":path", value: "/index.html"}: 5,
{name: ":scheme", value: "http"}: 6,
{name: ":scheme", value: "https"}: 7,
{name: ":status", value: "200"}: 8,
{name: ":status", value: "204"}: 9,
{name: ":status", value: "206"}: 10,
{name: ":status", value: "304"}: 11,
{name: ":status", value: "400"}: 12,
{name: ":status", value: "404"}: 13,
{name: ":status", value: "500"}: 14,
{name: "accept-charset", value: ""}: 15,
{name: "accept-encoding", value: "gzip, deflate"}: 16,
{name: "accept-language", value: ""}: 17,
{name: "accept-ranges", value: ""}: 18,
{name: "accept", value: ""}: 19,
{name: "access-control-allow-origin", value: ""}: 20,
{name: "age", value: ""}: 21,
{name: "allow", value: ""}: 22,
{name: "authorization", value: ""}: 23,
{name: "cache-control", value: ""}: 24,
{name: "content-disposition", value: ""}: 25,
{name: "content-encoding", value: ""}: 26,
{name: "content-language", value: ""}: 27,
{name: "content-length", value: ""}: 28,
{name: "content-location", value: ""}: 29,
{name: "content-range", value: ""}: 30,
{name: "content-type", value: ""}: 31,
{name: "cookie", value: ""}: 32,
{name: "date", value: ""}: 33,
{name: "etag", value: ""}: 34,
{name: "expect", value: ""}: 35,
{name: "expires", value: ""}: 36,
{name: "from", value: ""}: 37,
{name: "host", value: ""}: 38,
{name: "if-match", value: ""}: 39,
{name: "if-modified-since", value: ""}: 40,
{name: "if-none-match", value: ""}: 41,
{name: "if-range", value: ""}: 42,
{name: "if-unmodified-since", value: ""}: 43,
{name: "last-modified", value: ""}: 44,
{name: "link", value: ""}: 45,
{name: "location", value: ""}: 46,
{name: "max-forwards", value: ""}: 47,
{name: "proxy-authenticate", value: ""}: 48,
{name: "proxy-authorization", value: ""}: 49,
{name: "range", value: ""}: 50,
{name: "referer", value: ""}: 51,
{name: "refresh", value: ""}: 52,
{name: "retry-after", value: ""}: 53,
{name: "server", value: ""}: 54,
{name: "set-cookie", value: ""}: 55,
{name: "strict-transport-security", value: ""}: 56,
{name: "transfer-encoding", value: ""}: 57,
{name: "user-agent", value: ""}: 58,
{name: "vary", value: ""}: 59,
{name: "via", value: ""}: 60,
{name: "www-authenticate", value: ""}: 61,
},
ents: []HeaderField{
{Name: ":authority", Value: "", Sensitive: false},
{Name: ":method", Value: "GET", Sensitive: false},
{Name: ":method", Value: "POST", Sensitive: false},
{Name: ":path", Value: "/", Sensitive: false},
{Name: ":path", Value: "/index.html", Sensitive: false},
{Name: ":scheme", Value: "http", Sensitive: false},
{Name: ":scheme", Value: "https", Sensitive: false},
{Name: ":status", Value: "200", Sensitive: false},
{Name: ":status", Value: "204", Sensitive: false},
{Name: ":status", Value: "206", Sensitive: false},
{Name: ":status", Value: "304", Sensitive: false},
{Name: ":status", Value: "400", Sensitive: false},
{Name: ":status", Value: "404", Sensitive: false},
{Name: ":status", Value: "500", Sensitive: false},
{Name: "accept-charset", Value: "", Sensitive: false},
{Name: "accept-encoding", Value: "gzip, deflate", Sensitive: false},
{Name: "accept-language", Value: "", Sensitive: false},
{Name: "accept-ranges", Value: "", Sensitive: false},
{Name: "accept", Value: "", Sensitive: false},
{Name: "access-control-allow-origin", Value: "", Sensitive: false},
{Name: "age", Value: "", Sensitive: false},
{Name: "allow", Value: "", Sensitive: false},
{Name: "authorization", Value: "", Sensitive: false},
{Name: "cache-control", Value: "", Sensitive: false},
{Name: "content-disposition", Value: "", Sensitive: false},
{Name: "content-encoding", Value: "", Sensitive: false},
{Name: "content-language", Value: "", Sensitive: false},
{Name: "content-length", Value: "", Sensitive: false},
{Name: "content-location", Value: "", Sensitive: false},
{Name: "content-range", Value: "", Sensitive: false},
{Name: "content-type", Value: "", Sensitive: false},
{Name: "cookie", Value: "", Sensitive: false},
{Name: "date", Value: "", Sensitive: false},
{Name: "etag", Value: "", Sensitive: false},
{Name: "expect", Value: "", Sensitive: false},
{Name: "expires", Value: "", Sensitive: false},
{Name: "from", Value: "", Sensitive: false},
{Name: "host", Value: "", Sensitive: false},
{Name: "if-match", Value: "", Sensitive: false},
{Name: "if-modified-since", Value: "", Sensitive: false},
{Name: "if-none-match", Value: "", Sensitive: false},
{Name: "if-range", Value: "", Sensitive: false},
{Name: "if-unmodified-since", Value: "", Sensitive: false},
{Name: "last-modified", Value: "", Sensitive: false},
{Name: "link", Value: "", Sensitive: false},
{Name: "location", Value: "", Sensitive: false},
{Name: "max-forwards", Value: "", Sensitive: false},
{Name: "proxy-authenticate", Value: "", Sensitive: false},
{Name: "proxy-authorization", Value: "", Sensitive: false},
{Name: "range", Value: "", Sensitive: false},
{Name: "referer", Value: "", Sensitive: false},
{Name: "refresh", Value: "", Sensitive: false},
{Name: "retry-after", Value: "", Sensitive: false},
{Name: "server", Value: "", Sensitive: false},
{Name: "set-cookie", Value: "", Sensitive: false},
{Name: "strict-transport-security", Value: "", Sensitive: false},
{Name: "transfer-encoding", Value: "", Sensitive: false},
{Name: "user-agent", Value: "", Sensitive: false},
{Name: "vary", Value: "", Sensitive: false},
{Name: "via", Value: "", Sensitive: false},
{Name: "www-authenticate", Value: "", Sensitive: false},
},
}

View File

@ -96,8 +96,7 @@ func (t *headerFieldTable) evictOldest(n int) {
// meaning t.ents is reversed for dynamic tables. Hence, when t is a dynamic
// table, the return value i actually refers to the entry t.ents[t.len()-i].
//
// All tables are assumed to be a dynamic tables except for the global
// staticTable pointer.
// All tables are assumed to be a dynamic tables except for the global staticTable.
//
// See Section 2.3.3.
func (t *headerFieldTable) search(f HeaderField) (i uint64, nameValueMatch bool) {
@ -125,81 +124,6 @@ func (t *headerFieldTable) idToIndex(id uint64) uint64 {
return k + 1
}
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#appendix-B
var staticTable = newStaticTable()
var staticTableEntries = [...]HeaderField{
{Name: ":authority"},
{Name: ":method", Value: "GET"},
{Name: ":method", Value: "POST"},
{Name: ":path", Value: "/"},
{Name: ":path", Value: "/index.html"},
{Name: ":scheme", Value: "http"},
{Name: ":scheme", Value: "https"},
{Name: ":status", Value: "200"},
{Name: ":status", Value: "204"},
{Name: ":status", Value: "206"},
{Name: ":status", Value: "304"},
{Name: ":status", Value: "400"},
{Name: ":status", Value: "404"},
{Name: ":status", Value: "500"},
{Name: "accept-charset"},
{Name: "accept-encoding", Value: "gzip, deflate"},
{Name: "accept-language"},
{Name: "accept-ranges"},
{Name: "accept"},
{Name: "access-control-allow-origin"},
{Name: "age"},
{Name: "allow"},
{Name: "authorization"},
{Name: "cache-control"},
{Name: "content-disposition"},
{Name: "content-encoding"},
{Name: "content-language"},
{Name: "content-length"},
{Name: "content-location"},
{Name: "content-range"},
{Name: "content-type"},
{Name: "cookie"},
{Name: "date"},
{Name: "etag"},
{Name: "expect"},
{Name: "expires"},
{Name: "from"},
{Name: "host"},
{Name: "if-match"},
{Name: "if-modified-since"},
{Name: "if-none-match"},
{Name: "if-range"},
{Name: "if-unmodified-since"},
{Name: "last-modified"},
{Name: "link"},
{Name: "location"},
{Name: "max-forwards"},
{Name: "proxy-authenticate"},
{Name: "proxy-authorization"},
{Name: "range"},
{Name: "referer"},
{Name: "refresh"},
{Name: "retry-after"},
{Name: "server"},
{Name: "set-cookie"},
{Name: "strict-transport-security"},
{Name: "transfer-encoding"},
{Name: "user-agent"},
{Name: "vary"},
{Name: "via"},
{Name: "www-authenticate"},
}
func newStaticTable() *headerFieldTable {
t := &headerFieldTable{}
t.init()
for _, e := range staticTableEntries[:] {
t.addEntry(e)
}
return t
}
var huffmanCodes = [256]uint32{
0x1ff8,
0x7fffd8,

View File

@ -143,7 +143,7 @@ type Server struct {
}
func (s *Server) initialConnRecvWindowSize() int32 {
if s.MaxUploadBufferPerConnection > initialWindowSize {
if s.MaxUploadBufferPerConnection >= initialWindowSize {
return s.MaxUploadBufferPerConnection
}
return 1 << 20
@ -622,7 +622,9 @@ type stream struct {
resetQueued bool // RST_STREAM queued for write; set by sc.resetStream
gotTrailerHeader bool // HEADER frame for trailers was seen
wroteHeaders bool // whether we wrote headers (not status 100)
readDeadline *time.Timer // nil if unused
writeDeadline *time.Timer // nil if unused
closeErr error // set before cw is closed
trailer http.Header // accumulated trailers
reqTrailer http.Header // handler's Request.Trailer
@ -948,6 +950,8 @@ func (sc *serverConn) serve() {
}
case *startPushRequest:
sc.startPush(v)
case func(*serverConn):
v(sc)
default:
panic(fmt.Sprintf("unexpected type %T", v))
}
@ -1371,6 +1375,9 @@ func (sc *serverConn) startGracefulShutdownInternal() {
func (sc *serverConn) goAway(code ErrCode) {
sc.serveG.check()
if sc.inGoAway {
if sc.goAwayCode == ErrCodeNo {
sc.goAwayCode = code
}
return
}
sc.inGoAway = true
@ -1458,6 +1465,21 @@ func (sc *serverConn) processFrame(f Frame) error {
sc.sawFirstSettings = true
}
// Discard frames for streams initiated after the identified last
// stream sent in a GOAWAY, or all frames after sending an error.
// We still need to return connection-level flow control for DATA frames.
// RFC 9113 Section 6.8.
if sc.inGoAway && (sc.goAwayCode != ErrCodeNo || f.Header().StreamID > sc.maxClientStreamID) {
if f, ok := f.(*DataFrame); ok {
if sc.inflow.available() < int32(f.Length) {
return sc.countError("data_flow", streamError(f.Header().StreamID, ErrCodeFlowControl))
}
sc.sendWindowUpdate(nil, int(f.Length)) // conn-level
}
return nil
}
switch f := f.(type) {
case *SettingsFrame:
return sc.processSettings(f)
@ -1500,9 +1522,6 @@ func (sc *serverConn) processPing(f *PingFrame) error {
// PROTOCOL_ERROR."
return sc.countError("ping_on_stream", ConnectionError(ErrCodeProtocol))
}
if sc.inGoAway && sc.goAwayCode != ErrCodeNo {
return nil
}
sc.writeFrame(FrameWriteRequest{write: writePingAck{f}})
return nil
}
@ -1564,6 +1583,9 @@ func (sc *serverConn) closeStream(st *stream, err error) {
panic(fmt.Sprintf("invariant; can't close stream in state %v", st.state))
}
st.state = stateClosed
if st.readDeadline != nil {
st.readDeadline.Stop()
}
if st.writeDeadline != nil {
st.writeDeadline.Stop()
}
@ -1589,6 +1611,14 @@ func (sc *serverConn) closeStream(st *stream, err error) {
p.CloseWithError(err)
}
if e, ok := err.(StreamError); ok {
if e.Cause != nil {
err = e.Cause
} else {
err = errStreamClosed
}
}
st.closeErr = err
st.cw.Close() // signals Handler's CloseNotifier, unblocks writes, etc
sc.writeSched.CloseStream(st.id)
}
@ -1685,16 +1715,6 @@ func (sc *serverConn) processSettingInitialWindowSize(val uint32) error {
func (sc *serverConn) processData(f *DataFrame) error {
sc.serveG.check()
id := f.Header().StreamID
if sc.inGoAway && (sc.goAwayCode != ErrCodeNo || id > sc.maxClientStreamID) {
// Discard all DATA frames if the GOAWAY is due to an
// error, or:
//
// Section 6.8: After sending a GOAWAY frame, the sender
// can discard frames for streams initiated by the
// receiver with identifiers higher than the identified
// last stream.
return nil
}
data := f.Data()
state, st := sc.state(id)
@ -1837,19 +1857,27 @@ func (st *stream) copyTrailersToHandlerRequest() {
}
}
// onReadTimeout is run on its own goroutine (from time.AfterFunc)
// when the stream's ReadTimeout has fired.
func (st *stream) onReadTimeout() {
// Wrap the ErrDeadlineExceeded to avoid callers depending on us
// returning the bare error.
st.body.CloseWithError(fmt.Errorf("%w", os.ErrDeadlineExceeded))
}
// onWriteTimeout is run on its own goroutine (from time.AfterFunc)
// when the stream's WriteTimeout has fired.
func (st *stream) onWriteTimeout() {
st.sc.writeFrameFromHandler(FrameWriteRequest{write: streamError(st.id, ErrCodeInternal)})
st.sc.writeFrameFromHandler(FrameWriteRequest{write: StreamError{
StreamID: st.id,
Code: ErrCodeInternal,
Cause: os.ErrDeadlineExceeded,
}})
}
func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
sc.serveG.check()
id := f.StreamID
if sc.inGoAway {
// Ignore.
return nil
}
// http://tools.ietf.org/html/rfc7540#section-5.1.1
// Streams initiated by a client MUST use odd-numbered stream
// identifiers. [...] An endpoint that receives an unexpected
@ -1952,6 +1980,9 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
// (in Go 1.8), though. That's a more sane option anyway.
if sc.hs.ReadTimeout != 0 {
sc.conn.SetReadDeadline(time.Time{})
if st.body != nil {
st.readDeadline = time.AfterFunc(sc.hs.ReadTimeout, st.onReadTimeout)
}
}
go sc.runHandler(rw, req, handler)
@ -2020,9 +2051,6 @@ func (sc *serverConn) checkPriority(streamID uint32, p PriorityParam) error {
}
func (sc *serverConn) processPriority(f *PriorityFrame) error {
if sc.inGoAway {
return nil
}
if err := sc.checkPriority(f.StreamID, f.PriorityParam); err != nil {
return err
}
@ -2096,12 +2124,6 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
return nil, nil, sc.countError("bad_path_method", streamError(f.StreamID, ErrCodeProtocol))
}
bodyOpen := !f.StreamEnded()
if rp.method == "HEAD" && bodyOpen {
// HEAD requests can't have bodies
return nil, nil, sc.countError("head_body", streamError(f.StreamID, ErrCodeProtocol))
}
rp.header = make(http.Header)
for _, hf := range f.RegularFields() {
rp.header.Add(sc.canonicalHeader(hf.Name), hf.Value)
@ -2114,6 +2136,7 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
if err != nil {
return nil, nil, err
}
bodyOpen := !f.StreamEnded()
if bodyOpen {
if vv, ok := rp.header["Content-Length"]; ok {
if cl, err := strconv.ParseUint(vv[0], 10, 63); err == nil {
@ -2343,7 +2366,7 @@ func (sc *serverConn) sendWindowUpdate(st *stream, n int) {
// a larger Read than this. Very unlikely, but we handle it here
// rather than elsewhere for now.
const maxUint31 = 1<<31 - 1
for n >= maxUint31 {
for n > maxUint31 {
sc.sendWindowUpdate32(st, maxUint31)
n -= maxUint31
}
@ -2463,7 +2486,15 @@ type responseWriterState struct {
type chunkWriter struct{ rws *responseWriterState }
func (cw chunkWriter) Write(p []byte) (n int, err error) { return cw.rws.writeChunk(p) }
func (cw chunkWriter) Write(p []byte) (n int, err error) {
n, err = cw.rws.writeChunk(p)
if err == errStreamClosed {
// If writing failed because the stream has been closed,
// return the reason it was closed.
err = cw.rws.stream.closeErr
}
return n, err
}
func (rws *responseWriterState) hasTrailers() bool { return len(rws.trailers) > 0 }
@ -2502,6 +2533,10 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
rws.writeHeader(200)
}
if rws.handlerDone {
rws.promoteUndeclaredTrailers()
}
isHeadResp := rws.req.Method == "HEAD"
if !rws.sentHeader {
rws.sentHeader = true
@ -2573,10 +2608,6 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
return 0, nil
}
if rws.handlerDone {
rws.promoteUndeclaredTrailers()
}
// only send trailers if they have actually been defined by the
// server handler.
hasNonemptyTrailers := rws.hasNonemptyTrailers()
@ -2657,23 +2688,85 @@ func (rws *responseWriterState) promoteUndeclaredTrailers() {
}
}
func (w *responseWriter) SetReadDeadline(deadline time.Time) error {
st := w.rws.stream
if !deadline.IsZero() && deadline.Before(time.Now()) {
// If we're setting a deadline in the past, reset the stream immediately
// so writes after SetWriteDeadline returns will fail.
st.onReadTimeout()
return nil
}
w.rws.conn.sendServeMsg(func(sc *serverConn) {
if st.readDeadline != nil {
if !st.readDeadline.Stop() {
// Deadline already exceeded, or stream has been closed.
return
}
}
if deadline.IsZero() {
st.readDeadline = nil
} else if st.readDeadline == nil {
st.readDeadline = time.AfterFunc(deadline.Sub(time.Now()), st.onReadTimeout)
} else {
st.readDeadline.Reset(deadline.Sub(time.Now()))
}
})
return nil
}
func (w *responseWriter) SetWriteDeadline(deadline time.Time) error {
st := w.rws.stream
if !deadline.IsZero() && deadline.Before(time.Now()) {
// If we're setting a deadline in the past, reset the stream immediately
// so writes after SetWriteDeadline returns will fail.
st.onWriteTimeout()
return nil
}
w.rws.conn.sendServeMsg(func(sc *serverConn) {
if st.writeDeadline != nil {
if !st.writeDeadline.Stop() {
// Deadline already exceeded, or stream has been closed.
return
}
}
if deadline.IsZero() {
st.writeDeadline = nil
} else if st.writeDeadline == nil {
st.writeDeadline = time.AfterFunc(deadline.Sub(time.Now()), st.onWriteTimeout)
} else {
st.writeDeadline.Reset(deadline.Sub(time.Now()))
}
})
return nil
}
func (w *responseWriter) Flush() {
w.FlushError()
}
func (w *responseWriter) FlushError() error {
rws := w.rws
if rws == nil {
panic("Header called after Handler finished")
}
var err error
if rws.bw.Buffered() > 0 {
if err := rws.bw.Flush(); err != nil {
// Ignore the error. The frame writer already knows.
return
}
err = rws.bw.Flush()
} else {
// The bufio.Writer won't call chunkWriter.Write
// (writeChunk with zero bytes, so we have to do it
// ourselves to force the HTTP response header and/or
// final DATA frame (with END_STREAM) to be sent.
rws.writeChunk(nil)
_, err = chunkWriter{rws}.Write(nil)
if err == nil {
select {
case <-rws.stream.cw:
err = rws.stream.closeErr
default:
}
}
}
return err
}
func (w *responseWriter) CloseNotify() <-chan bool {

View File

@ -16,6 +16,7 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"log"
"math"
mathrand "math/rand"
@ -258,7 +259,8 @@ func (t *Transport) initConnPool() {
// HTTP/2 server.
type ClientConn struct {
t *Transport
tconn net.Conn // usually *tls.Conn, except specialized impls
tconn net.Conn // usually *tls.Conn, except specialized impls
tconnClosed bool
tlsState *tls.ConnectionState // nil only for specialized impls
reused uint32 // whether conn is being reused; atomic
singleUse bool // whether being used for a single http.Request
@ -344,8 +346,8 @@ type clientStream struct {
readErr error // sticky read error; owned by transportResponseBody.Read
reqBody io.ReadCloser
reqBodyContentLength int64 // -1 means unknown
reqBodyClosed bool // body has been closed; guarded by cc.mu
reqBodyContentLength int64 // -1 means unknown
reqBodyClosed chan struct{} // guarded by cc.mu; non-nil on Close, closed when done
// owned by writeRequest:
sentEndStream bool // sent an END_STREAM flag to the peer
@ -385,9 +387,8 @@ func (cs *clientStream) abortStreamLocked(err error) {
cs.abortErr = err
close(cs.abort)
})
if cs.reqBody != nil && !cs.reqBodyClosed {
cs.reqBody.Close()
cs.reqBodyClosed = true
if cs.reqBody != nil {
cs.closeReqBodyLocked()
}
// TODO(dneil): Clean up tests where cs.cc.cond is nil.
if cs.cc.cond != nil {
@ -400,13 +401,24 @@ func (cs *clientStream) abortRequestBodyWrite() {
cc := cs.cc
cc.mu.Lock()
defer cc.mu.Unlock()
if cs.reqBody != nil && !cs.reqBodyClosed {
cs.reqBody.Close()
cs.reqBodyClosed = true
if cs.reqBody != nil && cs.reqBodyClosed == nil {
cs.closeReqBodyLocked()
cc.cond.Broadcast()
}
}
func (cs *clientStream) closeReqBodyLocked() {
if cs.reqBodyClosed != nil {
return
}
cs.reqBodyClosed = make(chan struct{})
reqBodyClosed := cs.reqBodyClosed
go func() {
cs.reqBody.Close()
close(reqBodyClosed)
}()
}
type stickyErrWriter struct {
conn net.Conn
timeout time.Duration
@ -490,6 +502,15 @@ func authorityAddr(scheme string, authority string) (addr string) {
return net.JoinHostPort(host, port)
}
var retryBackoffHook func(time.Duration) *time.Timer
func backoffNewTimer(d time.Duration) *time.Timer {
if retryBackoffHook != nil {
return retryBackoffHook(d)
}
return time.NewTimer(d)
}
// RoundTripOpt is like RoundTrip, but takes options.
func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
if !(req.URL.Scheme == "https" || (req.URL.Scheme == "http" && t.AllowHTTP)) {
@ -515,11 +536,14 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
}
backoff := float64(uint(1) << (uint(retry) - 1))
backoff += backoff * (0.1 * mathrand.Float64())
d := time.Second * time.Duration(backoff)
timer := backoffNewTimer(d)
select {
case <-time.After(time.Second * time.Duration(backoff)):
case <-timer.C:
t.vlogf("RoundTrip retrying after failure: %v", err)
continue
case <-req.Context().Done():
timer.Stop()
err = req.Context().Err()
}
}
@ -921,10 +945,10 @@ func (cc *ClientConn) onIdleTimeout() {
cc.closeIfIdle()
}
func (cc *ClientConn) closeConn() error {
func (cc *ClientConn) closeConn() {
t := time.AfterFunc(250*time.Millisecond, cc.forceCloseConn)
defer t.Stop()
return cc.tconn.Close()
cc.tconn.Close()
}
// A tls.Conn.Close can hang for a long time if the peer is unresponsive.
@ -990,7 +1014,8 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
shutdownEnterWaitStateHook()
select {
case <-done:
return cc.closeConn()
cc.closeConn()
return nil
case <-ctx.Done():
cc.mu.Lock()
// Free the goroutine above
@ -1027,7 +1052,7 @@ func (cc *ClientConn) sendGoAway() error {
// closes the client connection immediately. In-flight requests are interrupted.
// err is sent to streams.
func (cc *ClientConn) closeForError(err error) error {
func (cc *ClientConn) closeForError(err error) {
cc.mu.Lock()
cc.closed = true
for _, cs := range cc.streams {
@ -1035,7 +1060,7 @@ func (cc *ClientConn) closeForError(err error) error {
}
cc.cond.Broadcast()
cc.mu.Unlock()
return cc.closeConn()
cc.closeConn()
}
// Close closes the client connection immediately.
@ -1043,16 +1068,17 @@ func (cc *ClientConn) closeForError(err error) error {
// In-flight requests are interrupted. For a graceful shutdown, use Shutdown instead.
func (cc *ClientConn) Close() error {
err := errors.New("http2: client connection force closed via ClientConn.Close")
return cc.closeForError(err)
cc.closeForError(err)
return nil
}
// closes the client connection immediately. In-flight requests are interrupted.
func (cc *ClientConn) closeForLostPing() error {
func (cc *ClientConn) closeForLostPing() {
err := errors.New("http2: client connection lost")
if f := cc.t.CountError; f != nil {
f("conn_close_lost_ping")
}
return cc.closeForError(err)
cc.closeForError(err)
}
// errRequestCanceled is a copy of net/http's errRequestCanceled because it's not
@ -1062,7 +1088,7 @@ var errRequestCanceled = errors.New("net/http: request canceled")
func commaSeparatedTrailers(req *http.Request) (string, error) {
keys := make([]string, 0, len(req.Trailer))
for k := range req.Trailer {
k = http.CanonicalHeaderKey(k)
k = canonicalHeader(k)
switch k {
case "Transfer-Encoding", "Trailer", "Content-Length":
return "", fmt.Errorf("invalid Trailer key %q", k)
@ -1430,11 +1456,19 @@ func (cs *clientStream) cleanupWriteRequest(err error) {
// and in multiple cases: server replies <=299 and >299
// while still writing request body
cc.mu.Lock()
mustCloseBody := false
if cs.reqBody != nil && cs.reqBodyClosed == nil {
mustCloseBody = true
cs.reqBodyClosed = make(chan struct{})
}
bodyClosed := cs.reqBodyClosed
cs.reqBodyClosed = true
cc.mu.Unlock()
if !bodyClosed && cs.reqBody != nil {
if mustCloseBody {
cs.reqBody.Close()
close(bodyClosed)
}
if bodyClosed != nil {
<-bodyClosed
}
if err != nil && cs.sentEndStream {
@ -1591,7 +1625,7 @@ func (cs *clientStream) writeRequestBody(req *http.Request) (err error) {
var sawEOF bool
for !sawEOF {
n, err := body.Read(buf[:len(buf)])
n, err := body.Read(buf)
if hasContentLen {
remainLen -= int64(n)
if remainLen == 0 && err == nil {
@ -1614,7 +1648,7 @@ func (cs *clientStream) writeRequestBody(req *http.Request) (err error) {
}
if err != nil {
cc.mu.Lock()
bodyClosed := cs.reqBodyClosed
bodyClosed := cs.reqBodyClosed != nil
cc.mu.Unlock()
switch {
case bodyClosed:
@ -1709,7 +1743,7 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error)
if cc.closed {
return 0, errClientConnClosed
}
if cs.reqBodyClosed {
if cs.reqBodyClosed != nil {
return 0, errStopReqBodyWrite
}
select {
@ -1894,7 +1928,7 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
// Header list size is ok. Write the headers.
enumerateHeaders(func(name, value string) {
name, ascii := asciiToLower(name)
name, ascii := lowerHeader(name)
if !ascii {
// Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header
// field names have to be ASCII characters (just as in HTTP/1.x).
@ -1947,7 +1981,7 @@ func (cc *ClientConn) encodeTrailers(trailer http.Header) ([]byte, error) {
}
for k, vv := range trailer {
lowKey, ascii := asciiToLower(k)
lowKey, ascii := lowerHeader(k)
if !ascii {
// Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header
// field names have to be ASCII characters (just as in HTTP/1.x).
@ -2005,7 +2039,7 @@ func (cc *ClientConn) forgetStreamID(id uint32) {
// wake up RoundTrip if there is a pending request.
cc.cond.Broadcast()
closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives()
closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives() || cc.goAway != nil
if closeOnIdle && cc.streamsReserved == 0 && len(cc.streams) == 0 {
if VerboseLogs {
cc.vlogf("http2: Transport closing idle conn %p (forSingleUse=%v, maxStream=%v)", cc, cc.singleUse, cc.nextStreamID-2)
@ -2081,6 +2115,7 @@ func (rl *clientConnReadLoop) cleanup() {
err = io.ErrUnexpectedEOF
}
cc.closed = true
for _, cs := range cc.streams {
select {
case <-cs.peerClosed:
@ -2279,7 +2314,7 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra
Status: status + " " + http.StatusText(statusCode),
}
for _, hf := range regularFields {
key := http.CanonicalHeaderKey(hf.Name)
key := canonicalHeader(hf.Name)
if key == "Trailer" {
t := res.Trailer
if t == nil {
@ -2287,7 +2322,7 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra
res.Trailer = t
}
foreachHeaderElement(hf.Value, func(v string) {
t[http.CanonicalHeaderKey(v)] = nil
t[canonicalHeader(v)] = nil
})
} else {
vv := header[key]
@ -2392,7 +2427,7 @@ func (rl *clientConnReadLoop) processTrailers(cs *clientStream, f *MetaHeadersFr
trailer := make(http.Header)
for _, hf := range f.RegularFields() {
key := http.CanonicalHeaderKey(hf.Name)
key := canonicalHeader(hf.Name)
trailer[key] = append(trailer[key], hf.Value)
}
cs.trailer = trailer
@ -2674,7 +2709,6 @@ func (rl *clientConnReadLoop) processGoAway(f *GoAwayFrame) error {
if fn := cc.t.CountError; fn != nil {
fn("recv_goaway_" + f.ErrCode.stringToken())
}
}
cc.setGoAway(f)
return nil
@ -2964,7 +2998,11 @@ func (gz *gzipReader) Read(p []byte) (n int, err error) {
}
func (gz *gzipReader) Close() error {
return gz.body.Close()
if err := gz.body.Close(); err != nil {
return err
}
gz.zerr = fs.ErrClosed
return nil
}
type errorReader struct{ err error }
@ -3028,7 +3066,7 @@ func traceGotConn(req *http.Request, cc *ClientConn, reused bool) {
cc.mu.Lock()
ci.WasIdle = len(cc.streams) == 0 && reused
if ci.WasIdle && !cc.lastActive.IsZero() {
ci.IdleTime = time.Now().Sub(cc.lastActive)
ci.IdleTime = time.Since(cc.lastActive)
}
cc.mu.Unlock()

15
vendor/golang.org/x/sys/cpu/cpu_other_ppc64x.go generated vendored Normal file
View File

@ -0,0 +1,15 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !aix && !linux && (ppc64 || ppc64le)
// +build !aix
// +build !linux
// +build ppc64 ppc64le
package cpu
func archInit() {
PPC64.IsPOWER8 = true
Initialized = true
}

31
vendor/golang.org/x/sys/unix/asm_bsd_ppc64.s generated vendored Normal file
View File

@ -0,0 +1,31 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build (darwin || freebsd || netbsd || openbsd) && gc
// +build darwin freebsd netbsd openbsd
// +build gc
#include "textflag.h"
//
// System call support for ppc64, BSD
//
// Just jump to package syscall's implementation for all these functions.
// The runtime may know about them.
TEXT ·Syscall(SB),NOSPLIT,$0-56
JMP syscall·Syscall(SB)
TEXT ·Syscall6(SB),NOSPLIT,$0-80
JMP syscall·Syscall6(SB)
TEXT ·Syscall9(SB),NOSPLIT,$0-104
JMP syscall·Syscall9(SB)
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
JMP syscall·RawSyscall(SB)
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
JMP syscall·RawSyscall6(SB)

View File

@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
package unix

Some files were not shown because too many files have changed in this diff Show More