mirror of https://github.com/docker/buildx.git
				
				
				
			Merge pull request #2282 from crazy-max/update-k8s
vendor: bump k8s dependencies to v0.29.2
This commit is contained in:
		
						commit
						545a5c97c6
					
				
							
								
								
									
										40
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										40
									
								
								go.mod
								
								
								
								
							|  | @ -45,16 +45,16 @@ require ( | ||||||
| 	go.opentelemetry.io/otel/sdk v1.19.0 | 	go.opentelemetry.io/otel/sdk v1.19.0 | ||||||
| 	go.opentelemetry.io/otel/sdk/metric v1.19.0 | 	go.opentelemetry.io/otel/sdk/metric v1.19.0 | ||||||
| 	go.opentelemetry.io/otel/trace v1.19.0 | 	go.opentelemetry.io/otel/trace v1.19.0 | ||||||
| 	golang.org/x/mod v0.13.0 | 	golang.org/x/mod v0.14.0 | ||||||
| 	golang.org/x/sync v0.4.0 | 	golang.org/x/sync v0.5.0 | ||||||
| 	golang.org/x/sys v0.16.0 | 	golang.org/x/sys v0.16.0 | ||||||
| 	golang.org/x/term v0.15.0 | 	golang.org/x/term v0.15.0 | ||||||
| 	google.golang.org/grpc v1.59.0 | 	google.golang.org/grpc v1.59.0 | ||||||
| 	gopkg.in/yaml.v3 v3.0.1 | 	gopkg.in/yaml.v3 v3.0.1 | ||||||
| 	k8s.io/api v0.26.7 | 	k8s.io/api v0.29.2 | ||||||
| 	k8s.io/apimachinery v0.26.7 | 	k8s.io/apimachinery v0.29.2 | ||||||
| 	k8s.io/apiserver v0.26.7 | 	k8s.io/apiserver v0.29.2 | ||||||
| 	k8s.io/client-go v0.26.7 | 	k8s.io/client-go v0.29.2 | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| require ( | require ( | ||||||
|  | @ -88,19 +88,20 @@ require ( | ||||||
| 	github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect | 	github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect | ||||||
| 	github.com/docker/go-connections v0.5.0 // indirect | 	github.com/docker/go-connections v0.5.0 // indirect | ||||||
| 	github.com/docker/go-metrics v0.0.1 // indirect | 	github.com/docker/go-metrics v0.0.1 // indirect | ||||||
| 	github.com/emicklei/go-restful/v3 v3.10.1 // indirect | 	github.com/emicklei/go-restful/v3 v3.11.0 // indirect | ||||||
| 	github.com/felixge/httpsnoop v1.0.4 // indirect | 	github.com/felixge/httpsnoop v1.0.4 // indirect | ||||||
| 	github.com/fvbommel/sortorder v1.0.1 // indirect | 	github.com/fvbommel/sortorder v1.0.1 // indirect | ||||||
| 	github.com/go-logr/logr v1.3.0 // indirect | 	github.com/go-logr/logr v1.3.0 // indirect | ||||||
| 	github.com/go-logr/stdr v1.2.2 // indirect | 	github.com/go-logr/stdr v1.2.2 // indirect | ||||||
| 	github.com/go-openapi/jsonpointer v0.19.5 // indirect | 	github.com/go-openapi/jsonpointer v0.19.6 // indirect | ||||||
| 	github.com/go-openapi/jsonreference v0.20.0 // indirect | 	github.com/go-openapi/jsonreference v0.20.2 // indirect | ||||||
| 	github.com/go-openapi/swag v0.19.14 // indirect | 	github.com/go-openapi/swag v0.22.3 // indirect | ||||||
| 	github.com/gogo/googleapis v1.4.1 // indirect | 	github.com/gogo/googleapis v1.4.1 // indirect | ||||||
| 	github.com/google/gnostic v0.5.7-v3refs // indirect | 	github.com/google/gnostic-models v0.6.8 // indirect | ||||||
| 	github.com/google/go-cmp v0.6.0 // indirect | 	github.com/google/go-cmp v0.6.0 // indirect | ||||||
| 	github.com/google/gofuzz v1.2.0 // indirect | 	github.com/google/gofuzz v1.2.0 // indirect | ||||||
| 	github.com/gorilla/mux v1.8.0 // indirect | 	github.com/gorilla/mux v1.8.0 // indirect | ||||||
|  | 	github.com/gorilla/websocket v1.5.0 // indirect | ||||||
| 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect | 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect | ||||||
| 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect | 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect | ||||||
| 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect | 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect | ||||||
|  | @ -110,7 +111,7 @@ require ( | ||||||
| 	github.com/josharian/intern v1.0.0 // indirect | 	github.com/josharian/intern v1.0.0 // indirect | ||||||
| 	github.com/json-iterator/go v1.1.12 // indirect | 	github.com/json-iterator/go v1.1.12 // indirect | ||||||
| 	github.com/klauspost/compress v1.17.4 // indirect | 	github.com/klauspost/compress v1.17.4 // indirect | ||||||
| 	github.com/mailru/easyjson v0.7.6 // indirect | 	github.com/mailru/easyjson v0.7.7 // indirect | ||||||
| 	github.com/mattn/go-runewidth v0.0.15 // indirect | 	github.com/mattn/go-runewidth v0.0.15 // indirect | ||||||
| 	github.com/mattn/go-shellwords v1.0.12 // indirect | 	github.com/mattn/go-shellwords v1.0.12 // indirect | ||||||
| 	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect | 	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect | ||||||
|  | @ -129,6 +130,7 @@ require ( | ||||||
| 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||||||
| 	github.com/modern-go/reflect2 v1.0.2 // indirect | 	github.com/modern-go/reflect2 v1.0.2 // indirect | ||||||
| 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect | 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect | ||||||
|  | 	github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect | ||||||
| 	github.com/pmezard/go-difflib v1.0.0 // indirect | 	github.com/pmezard/go-difflib v1.0.0 // indirect | ||||||
| 	github.com/prometheus/client_golang v1.17.0 // indirect | 	github.com/prometheus/client_golang v1.17.0 // indirect | ||||||
| 	github.com/prometheus/client_model v0.5.0 // indirect | 	github.com/prometheus/client_model v0.5.0 // indirect | ||||||
|  | @ -155,11 +157,11 @@ require ( | ||||||
| 	go.opentelemetry.io/proto/otlp v1.0.0 // indirect | 	go.opentelemetry.io/proto/otlp v1.0.0 // indirect | ||||||
| 	golang.org/x/crypto v0.17.0 // indirect | 	golang.org/x/crypto v0.17.0 // indirect | ||||||
| 	golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect | 	golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect | ||||||
| 	golang.org/x/net v0.17.0 // indirect | 	golang.org/x/net v0.19.0 // indirect | ||||||
| 	golang.org/x/oauth2 v0.11.0 // indirect | 	golang.org/x/oauth2 v0.11.0 // indirect | ||||||
| 	golang.org/x/text v0.14.0 // indirect | 	golang.org/x/text v0.14.0 // indirect | ||||||
| 	golang.org/x/time v0.3.0 // indirect | 	golang.org/x/time v0.3.0 // indirect | ||||||
| 	golang.org/x/tools v0.14.0 // indirect | 	golang.org/x/tools v0.16.1 // indirect | ||||||
| 	google.golang.org/appengine v1.6.7 // indirect | 	google.golang.org/appengine v1.6.7 // indirect | ||||||
| 	google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect | 	google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect | ||||||
| 	google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect | 	google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect | ||||||
|  | @ -167,10 +169,10 @@ require ( | ||||||
| 	google.golang.org/protobuf v1.31.0 // indirect | 	google.golang.org/protobuf v1.31.0 // indirect | ||||||
| 	gopkg.in/inf.v0 v0.9.1 // indirect | 	gopkg.in/inf.v0 v0.9.1 // indirect | ||||||
| 	gopkg.in/yaml.v2 v2.4.0 // indirect | 	gopkg.in/yaml.v2 v2.4.0 // indirect | ||||||
| 	k8s.io/klog/v2 v2.90.1 // indirect | 	k8s.io/klog/v2 v2.110.1 // indirect | ||||||
| 	k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect | 	k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect | ||||||
| 	k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect | 	k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect | ||||||
| 	sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect | 	sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect | ||||||
| 	sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect | 	sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect | ||||||
| 	sigs.k8s.io/yaml v1.3.0 // indirect | 	sigs.k8s.io/yaml v1.3.0 // indirect | ||||||
| ) | ) | ||||||
|  |  | ||||||
							
								
								
									
										130
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										130
									
								
								go.sum
								
								
								
								
							|  | @ -146,12 +146,9 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 | ||||||
| github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= | ||||||
| github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= | github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= | ||||||
| github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= | github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= | ||||||
| github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= |  | ||||||
| github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM= | github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM= | ||||||
| github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= | github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= | ||||||
| github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= | github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= | ||||||
| github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= |  | ||||||
| github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= |  | ||||||
| github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= | ||||||
| github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= | ||||||
| github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= | ||||||
|  | @ -168,23 +165,22 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 | ||||||
| github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= | ||||||
| github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= | ||||||
| github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= | ||||||
| github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= |  | ||||||
| github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= | ||||||
| github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= | github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= | ||||||
| github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= | github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= | ||||||
| github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= | ||||||
| github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= | ||||||
| github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= | github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= | ||||||
| github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= | ||||||
| github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= | ||||||
| github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= | ||||||
| github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= | github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= | ||||||
| github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= | ||||||
| github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= |  | ||||||
| github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= |  | ||||||
| github.com/go-sql-driver/mysql v1.3.0 h1:pgwjLi/dvffoP9aabwkT3AKpXQM93QARkjFhDDqC1UE= | github.com/go-sql-driver/mysql v1.3.0 h1:pgwjLi/dvffoP9aabwkT3AKpXQM93QARkjFhDDqC1UE= | ||||||
| github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= | github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= | ||||||
| github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= | ||||||
|  | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= | ||||||
|  | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= | ||||||
| github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= | github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= | ||||||
| github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= | github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= | ||||||
| github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= | github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= | ||||||
|  | @ -208,31 +204,26 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y | ||||||
| github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||||||
| github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= | ||||||
| github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= | ||||||
| github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= |  | ||||||
| github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= |  | ||||||
| github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= |  | ||||||
| github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= |  | ||||||
| github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= |  | ||||||
| github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= |  | ||||||
| github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= | ||||||
| github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= |  | ||||||
| github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= | ||||||
| github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | ||||||
| github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93 h1:jc2UWq7CbdszqeH6qu1ougXMIUBfSy8Pbh/anURYbGI= | github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93 h1:jc2UWq7CbdszqeH6qu1ougXMIUBfSy8Pbh/anURYbGI= | ||||||
| github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= | github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= | ||||||
| github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= | github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= | ||||||
| github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= | github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= | ||||||
| github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= | ||||||
| github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | ||||||
| github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | ||||||
| github.com/google/go-cmp v0.4.0/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.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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
|  | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||||
| github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | ||||||
| github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||||
| github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | ||||||
| github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= | ||||||
| github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | ||||||
|  | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= | ||||||
|  | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= | ||||||
| github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= | ||||||
| github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= | ||||||
| github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||||
|  | @ -242,6 +233,8 @@ github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z | ||||||
| github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= | ||||||
| github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= | ||||||
| github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | ||||||
|  | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= | ||||||
|  | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | ||||||
| github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= | ||||||
| github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= | ||||||
| github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= | github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= | ||||||
|  | @ -283,7 +276,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv | ||||||
| github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | ||||||
| github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= | ||||||
| github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | ||||||
| github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= | ||||||
| github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= | ||||||
| github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= | ||||||
| github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||||||
|  | @ -295,10 +288,8 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ | ||||||
| github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= | github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= | ||||||
| github.com/magiconair/properties v1.5.3 h1:C8fxWnhYyME3n0klPOhVM7PtYUB3eV1W3DeFmN3j53Y= | github.com/magiconair/properties v1.5.3 h1:C8fxWnhYyME3n0klPOhVM7PtYUB3eV1W3DeFmN3j53Y= | ||||||
| github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= | github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= | ||||||
| github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= | ||||||
| github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= | ||||||
| github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= |  | ||||||
| github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= |  | ||||||
| github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= | ||||||
| github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | ||||||
| github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= | github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= | ||||||
|  | @ -352,16 +343,18 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P | ||||||
| github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= | ||||||
| github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= | ||||||
| github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= | ||||||
|  | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= | ||||||
|  | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= | ||||||
| github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= | ||||||
| github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | ||||||
| github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= | github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= | ||||||
| github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= | github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= | ||||||
| github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= | github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= | ||||||
| github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= | github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= | ||||||
| github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= | ||||||
| github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= | github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= | ||||||
| github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= | github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= | ||||||
| github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= | github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= | ||||||
| github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= | github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= | ||||||
| github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= | ||||||
| github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= | ||||||
|  | @ -440,16 +433,19 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= | ||||||
| github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||||
| github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c h1:2EejZtjFjKJGk71ANb+wtFK5EjUzUkEM3R0xnp559xg= | github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c h1:2EejZtjFjKJGk71ANb+wtFK5EjUzUkEM3R0xnp559xg= | ||||||
| github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= | github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= | ||||||
| github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= |  | ||||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||||
| github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||||
| github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= | ||||||
|  | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | ||||||
|  | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | ||||||
| github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||||||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||||
| github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||||||
| github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | ||||||
| github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |  | ||||||
| github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||||
|  | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||||
|  | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | ||||||
|  | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | ||||||
| github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | ||||||
| github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||||||
| github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c= | github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c= | ||||||
|  | @ -532,8 +528,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx | ||||||
| golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | ||||||
| golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||||
| golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||||
| golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= | golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= | ||||||
| golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||||||
| golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
| golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
| golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
|  | @ -546,8 +542,8 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL | ||||||
| golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||||
| golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||||
| golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||||||
| golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= | golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= | ||||||
| golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= | golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= | ||||||
| golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||||||
| golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= | golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= | ||||||
| golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= | golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= | ||||||
|  | @ -558,8 +554,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ | ||||||
| golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= | golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= | ||||||
| golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= | golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||||||
| golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
| golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
| golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
|  | @ -597,8 +593,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn | ||||||
| golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||||
| golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= | ||||||
| golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | ||||||
| golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= | golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= | ||||||
| golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= | golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= | ||||||
| golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
|  | @ -611,8 +607,6 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA | ||||||
| google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= | ||||||
| google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | ||||||
| google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | ||||||
| google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= |  | ||||||
| google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= |  | ||||||
| google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= | google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= | ||||||
| google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= | google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= | ||||||
| google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= | google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= | ||||||
|  | @ -628,14 +622,6 @@ google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 | ||||||
| google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= | ||||||
| google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= | google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= | ||||||
| google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= | google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= | ||||||
| google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= |  | ||||||
| google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= |  | ||||||
| google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= |  | ||||||
| google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= |  | ||||||
| google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= |  | ||||||
| google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= |  | ||||||
| google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= |  | ||||||
| google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= |  | ||||||
| google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= | ||||||
| google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | ||||||
| google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | ||||||
|  | @ -647,7 +633,6 @@ gopkg.in/cenkalti/backoff.v2 v2.2.1 h1:eJ9UAg01/HIHG987TwxvnzK2MgxXq97YY6rYDpY9a | ||||||
| gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU= | gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU= | ||||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
| gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
| gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |  | ||||||
| gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
| gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= | ||||||
| gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= | ||||||
|  | @ -665,30 +650,29 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
| gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= | ||||||
| gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= | ||||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
| gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |  | ||||||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||||
| gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
| gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= | gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= | ||||||
| gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= | gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= | ||||||
| honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||||
| honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||||
| k8s.io/api v0.26.7 h1:Lf4iEBEJb5OFNmawtBfSZV/UNi9riSJ0t1qdhyZqI40= | k8s.io/api v0.29.2 h1:hBC7B9+MU+ptchxEqTNW2DkUosJpp1P+Wn6YncZ474A= | ||||||
| k8s.io/api v0.26.7/go.mod h1:Vk9bMadzA49UHPmHB//lX7VRCQSXGoVwfLd3Sc1SSXI= | k8s.io/api v0.29.2/go.mod h1:sdIaaKuU7P44aoyyLlikSLayT6Vb7bvJNCX105xZXY0= | ||||||
| k8s.io/apimachinery v0.26.7 h1:590jSBwaSHCAFCqltaEogY/zybFlhGsnLteLpuF2wig= | k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= | ||||||
| k8s.io/apimachinery v0.26.7/go.mod h1:qYzLkrQ9lhrZRh0jNKo2cfvf/R1/kQONnSiyB7NUJU0= | k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= | ||||||
| k8s.io/apiserver v0.26.7 h1:NX/zBZZn4R+Cq6shwyn8Pn8REd0yJJ16dbtv9WkEVEU= | k8s.io/apiserver v0.29.2 h1:+Z9S0dSNr+CjnVXQePG8TcBWHr3Q7BmAr7NraHvsMiQ= | ||||||
| k8s.io/apiserver v0.26.7/go.mod h1:r0wDRWHI7VL/KlQLTkJJBVGZ3KeNfv+VetlyRtr86xs= | k8s.io/apiserver v0.29.2/go.mod h1:B0LieKVoyU7ykQvPFm7XSdIHaCHSzCzQWPFa5bqbeMQ= | ||||||
| k8s.io/client-go v0.26.7 h1:hyU9aKHlwVOykgyxzGYkrDSLCc4+mimZVyUJjPyUn1E= | k8s.io/client-go v0.29.2 h1:FEg85el1TeZp+/vYJM7hkDlSTFZ+c5nnK44DJ4FyoRg= | ||||||
| k8s.io/client-go v0.26.7/go.mod h1:okYjy0jtq6sdeztALDvCh24tg4opOQS1XNvsJlERDAo= | k8s.io/client-go v0.29.2/go.mod h1:knlvFZE58VpqbQpJNbCbctTVXcd35mMyAAwBdpt4jrA= | ||||||
| k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= | k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= | ||||||
| k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= | k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= | ||||||
| k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= | k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= | ||||||
| k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= | k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= | ||||||
| k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk= | k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= | ||||||
| k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= | k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= | ||||||
| sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= | ||||||
| sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= | ||||||
| sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= | ||||||
| sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= | sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= | ||||||
| sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= | sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= | ||||||
| sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= | ||||||
|  |  | ||||||
|  | @ -1,6 +1,15 @@ | ||||||
| # Change history of go-restful | # Change history of go-restful | ||||||
| 
 | 
 | ||||||
| ## [v3.10.1] - 2022-11-19 | ## [v3.11.0] - 2023-08-19 | ||||||
|  | 
 | ||||||
|  | - restored behavior as <= v3.9.0 with option to change path strategy using TrimRightSlashEnabled.  | ||||||
|  | 
 | ||||||
|  | ## [v3.10.2] - 2023-03-09 - DO NOT USE | ||||||
|  | 
 | ||||||
|  | - introduced MergePathStrategy to be able to revert behaviour of path concatenation to 3.9.0 | ||||||
|  |   see comment in Readme how to customize this behaviour. | ||||||
|  | 
 | ||||||
|  | ## [v3.10.1] - 2022-11-19 - DO NOT USE | ||||||
| 
 | 
 | ||||||
| - fix broken 3.10.0 by using path package for joining paths | - fix broken 3.10.0 by using path package for joining paths | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -79,7 +79,7 @@ func (u UserResource) findUser(request *restful.Request, response *restful.Respo | ||||||
| - Content encoding (gzip,deflate) of request and response payloads | - Content encoding (gzip,deflate) of request and response payloads | ||||||
| - Automatic responses on OPTIONS (using a filter) | - Automatic responses on OPTIONS (using a filter) | ||||||
| - Automatic CORS request handling (using a filter) | - Automatic CORS request handling (using a filter) | ||||||
| - API declaration for Swagger UI ([go-restful-openapi](https://github.com/emicklei/go-restful-openapi), see [go-restful-swagger12](https://github.com/emicklei/go-restful-swagger12)) | - API declaration for Swagger UI ([go-restful-openapi](https://github.com/emicklei/go-restful-openapi)) | ||||||
| - Panic recovery to produce HTTP 500, customizable using RecoverHandler(...) | - Panic recovery to produce HTTP 500, customizable using RecoverHandler(...) | ||||||
| - Route errors produce HTTP 404/405/406/415 errors, customizable using ServiceErrorHandler(...) | - Route errors produce HTTP 404/405/406/415 errors, customizable using ServiceErrorHandler(...) | ||||||
| - Configurable (trace) logging | - Configurable (trace) logging | ||||||
|  | @ -96,6 +96,7 @@ There are several hooks to customize the behavior of the go-restful package. | ||||||
| - Compression | - Compression | ||||||
| - Encoders for other serializers | - Encoders for other serializers | ||||||
| - Use [jsoniter](https://github.com/json-iterator/go) by building this package using a build tag, e.g. `go build -tags=jsoniter .`  | - Use [jsoniter](https://github.com/json-iterator/go) by building this package using a build tag, e.g. `go build -tags=jsoniter .`  | ||||||
|  | - Use the package variable `TrimRightSlashEnabled` (default true) to control the behavior of matching routes that end with a slash `/`  | ||||||
| 
 | 
 | ||||||
| ## Resources | ## Resources | ||||||
| 
 | 
 | ||||||
|  | @ -108,4 +109,4 @@ There are several hooks to customize the behavior of the go-restful package. | ||||||
| 
 | 
 | ||||||
| Type ```git shortlog -s``` for a full list of contributors. | Type ```git shortlog -s``` for a full list of contributors. | ||||||
| 
 | 
 | ||||||
| © 2012 - 2022, http://ernestmicklei.com. MIT License. Contributions are welcome. | © 2012 - 2023, http://ernestmicklei.com. MIT License. Contributions are welcome. | ||||||
|  |  | ||||||
|  | @ -40,7 +40,8 @@ type Route struct { | ||||||
| 	ParameterDocs           []*Parameter | 	ParameterDocs           []*Parameter | ||||||
| 	ResponseErrors          map[int]ResponseError | 	ResponseErrors          map[int]ResponseError | ||||||
| 	DefaultResponse         *ResponseError | 	DefaultResponse         *ResponseError | ||||||
| 	ReadSample, WriteSample interface{} // structs that model an example request or response payload
 | 	ReadSample, WriteSample interface{}   // structs that model an example request or response payload
 | ||||||
|  | 	WriteSamples            []interface{} // if more than one return types is possible (oneof) then this will contain multiple values
 | ||||||
| 
 | 
 | ||||||
| 	// Extra information used to store custom information about the route.
 | 	// Extra information used to store custom information about the route.
 | ||||||
| 	Metadata map[string]interface{} | 	Metadata map[string]interface{} | ||||||
|  | @ -164,7 +165,13 @@ func tokenizePath(path string) []string { | ||||||
| 	if "/" == path { | 	if "/" == path { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	return strings.Split(strings.TrimLeft(path, "/"), "/") | 	if TrimRightSlashEnabled { | ||||||
|  | 		// 3.9.0
 | ||||||
|  | 		return strings.Split(strings.Trim(path, "/"), "/") | ||||||
|  | 	} else { | ||||||
|  | 		// 3.10.2
 | ||||||
|  | 		return strings.Split(strings.TrimLeft(path, "/"), "/") | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // for debugging
 | // for debugging
 | ||||||
|  | @ -177,4 +184,8 @@ func (r *Route) EnableContentEncoding(enabled bool) { | ||||||
| 	r.contentEncodingEnabled = &enabled | 	r.contentEncodingEnabled = &enabled | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var TrimRightSlashEnabled = false | // TrimRightSlashEnabled controls whether
 | ||||||
|  | // - path on route building is using path.Join
 | ||||||
|  | // - the path of the incoming request is trimmed of its slash suffux.
 | ||||||
|  | // Value of true matches the behavior of <= 3.9.0
 | ||||||
|  | var TrimRightSlashEnabled = true | ||||||
|  |  | ||||||
|  | @ -31,17 +31,18 @@ type RouteBuilder struct { | ||||||
| 	typeNameHandleFunc TypeNameHandleFunction // required
 | 	typeNameHandleFunc TypeNameHandleFunction // required
 | ||||||
| 
 | 
 | ||||||
| 	// documentation
 | 	// documentation
 | ||||||
| 	doc                     string | 	doc                    string | ||||||
| 	notes                   string | 	notes                  string | ||||||
| 	operation               string | 	operation              string | ||||||
| 	readSample, writeSample interface{} | 	readSample             interface{} | ||||||
| 	parameters              []*Parameter | 	writeSamples           []interface{} | ||||||
| 	errorMap                map[int]ResponseError | 	parameters             []*Parameter | ||||||
| 	defaultResponse         *ResponseError | 	errorMap               map[int]ResponseError | ||||||
| 	metadata                map[string]interface{} | 	defaultResponse        *ResponseError | ||||||
| 	extensions              map[string]interface{} | 	metadata               map[string]interface{} | ||||||
| 	deprecated              bool | 	extensions             map[string]interface{} | ||||||
| 	contentEncodingEnabled  *bool | 	deprecated             bool | ||||||
|  | 	contentEncodingEnabled *bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Do evaluates each argument with the RouteBuilder itself.
 | // Do evaluates each argument with the RouteBuilder itself.
 | ||||||
|  | @ -135,9 +136,9 @@ func (b RouteBuilder) ParameterNamed(name string) (p *Parameter) { | ||||||
| 	return p | 	return p | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Writes tells what resource type will be written as the response payload. Optional.
 | // Writes tells which one of the resource types will be written as the response payload. Optional.
 | ||||||
| func (b *RouteBuilder) Writes(sample interface{}) *RouteBuilder { | func (b *RouteBuilder) Writes(samples ...interface{}) *RouteBuilder { | ||||||
| 	b.writeSample = sample | 	b.writeSamples = samples // oneof
 | ||||||
| 	return b | 	return b | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -342,19 +343,29 @@ func (b *RouteBuilder) Build() Route { | ||||||
| 		ResponseErrors:                   b.errorMap, | 		ResponseErrors:                   b.errorMap, | ||||||
| 		DefaultResponse:                  b.defaultResponse, | 		DefaultResponse:                  b.defaultResponse, | ||||||
| 		ReadSample:                       b.readSample, | 		ReadSample:                       b.readSample, | ||||||
| 		WriteSample:                      b.writeSample, | 		WriteSamples:                     b.writeSamples, | ||||||
| 		Metadata:                         b.metadata, | 		Metadata:                         b.metadata, | ||||||
| 		Deprecated:                       b.deprecated, | 		Deprecated:                       b.deprecated, | ||||||
| 		contentEncodingEnabled:           b.contentEncodingEnabled, | 		contentEncodingEnabled:           b.contentEncodingEnabled, | ||||||
| 		allowedMethodsWithoutContentType: b.allowedMethodsWithoutContentType, | 		allowedMethodsWithoutContentType: b.allowedMethodsWithoutContentType, | ||||||
| 	} | 	} | ||||||
|  | 	// set WriteSample if one specified
 | ||||||
|  | 	if len(b.writeSamples) == 1 { | ||||||
|  | 		route.WriteSample = b.writeSamples[0] | ||||||
|  | 	} | ||||||
| 	route.Extensions = b.extensions | 	route.Extensions = b.extensions | ||||||
| 	route.postBuild() | 	route.postBuild() | ||||||
| 	return route | 	return route | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func concatPath(path1, path2 string) string { | // merge two paths using the current (package global) merge path strategy.
 | ||||||
| 	return path.Join(path1, path2) | func concatPath(rootPath, routePath string) string { | ||||||
|  | 
 | ||||||
|  | 	if TrimRightSlashEnabled { | ||||||
|  | 		return strings.TrimRight(rootPath, "/") + "/" + strings.TrimLeft(routePath, "/") | ||||||
|  | 	} else { | ||||||
|  | 		return path.Join(rootPath, routePath) | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var anonymousFuncCount int32 | var anonymousFuncCount int32 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,168 @@ | ||||||
|  | //go:build go1.21
 | ||||||
|  | // +build go1.21
 | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  | Copyright 2023 The logr Authors. | ||||||
|  | 
 | ||||||
|  | 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 slogr | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"log/slog" | ||||||
|  | 
 | ||||||
|  | 	"github.com/go-logr/logr" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type slogHandler struct { | ||||||
|  | 	// May be nil, in which case all logs get discarded.
 | ||||||
|  | 	sink logr.LogSink | ||||||
|  | 	// Non-nil if sink is non-nil and implements SlogSink.
 | ||||||
|  | 	slogSink SlogSink | ||||||
|  | 
 | ||||||
|  | 	// groupPrefix collects values from WithGroup calls. It gets added as
 | ||||||
|  | 	// prefix to value keys when handling a log record.
 | ||||||
|  | 	groupPrefix string | ||||||
|  | 
 | ||||||
|  | 	// levelBias can be set when constructing the handler to influence the
 | ||||||
|  | 	// slog.Level of log records. A positive levelBias reduces the
 | ||||||
|  | 	// slog.Level value. slog has no API to influence this value after the
 | ||||||
|  | 	// handler got created, so it can only be set indirectly through
 | ||||||
|  | 	// Logger.V.
 | ||||||
|  | 	levelBias slog.Level | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var _ slog.Handler = &slogHandler{} | ||||||
|  | 
 | ||||||
|  | // groupSeparator is used to concatenate WithGroup names and attribute keys.
 | ||||||
|  | const groupSeparator = "." | ||||||
|  | 
 | ||||||
|  | // GetLevel is used for black box unit testing.
 | ||||||
|  | func (l *slogHandler) GetLevel() slog.Level { | ||||||
|  | 	return l.levelBias | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *slogHandler) Enabled(ctx context.Context, level slog.Level) bool { | ||||||
|  | 	return l.sink != nil && (level >= slog.LevelError || l.sink.Enabled(l.levelFromSlog(level))) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *slogHandler) Handle(ctx context.Context, record slog.Record) error { | ||||||
|  | 	if l.slogSink != nil { | ||||||
|  | 		// Only adjust verbosity level of log entries < slog.LevelError.
 | ||||||
|  | 		if record.Level < slog.LevelError { | ||||||
|  | 			record.Level -= l.levelBias | ||||||
|  | 		} | ||||||
|  | 		return l.slogSink.Handle(ctx, record) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// No need to check for nil sink here because Handle will only be called
 | ||||||
|  | 	// when Enabled returned true.
 | ||||||
|  | 
 | ||||||
|  | 	kvList := make([]any, 0, 2*record.NumAttrs()) | ||||||
|  | 	record.Attrs(func(attr slog.Attr) bool { | ||||||
|  | 		if attr.Key != "" { | ||||||
|  | 			kvList = append(kvList, l.addGroupPrefix(attr.Key), attr.Value.Resolve().Any()) | ||||||
|  | 		} | ||||||
|  | 		return true | ||||||
|  | 	}) | ||||||
|  | 	if record.Level >= slog.LevelError { | ||||||
|  | 		l.sinkWithCallDepth().Error(nil, record.Message, kvList...) | ||||||
|  | 	} else { | ||||||
|  | 		level := l.levelFromSlog(record.Level) | ||||||
|  | 		l.sinkWithCallDepth().Info(level, record.Message, kvList...) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // sinkWithCallDepth adjusts the stack unwinding so that when Error or Info
 | ||||||
|  | // are called by Handle, code in slog gets skipped.
 | ||||||
|  | //
 | ||||||
|  | // This offset currently (Go 1.21.0) works for calls through
 | ||||||
|  | // slog.New(NewSlogHandler(...)).  There's no guarantee that the call
 | ||||||
|  | // chain won't change. Wrapping the handler will also break unwinding. It's
 | ||||||
|  | // still better than not adjusting at all....
 | ||||||
|  | //
 | ||||||
|  | // This cannot be done when constructing the handler because NewLogr needs
 | ||||||
|  | // access to the original sink without this adjustment. A second copy would
 | ||||||
|  | // work, but then WithAttrs would have to be called for both of them.
 | ||||||
|  | func (l *slogHandler) sinkWithCallDepth() logr.LogSink { | ||||||
|  | 	if sink, ok := l.sink.(logr.CallDepthLogSink); ok { | ||||||
|  | 		return sink.WithCallDepth(2) | ||||||
|  | 	} | ||||||
|  | 	return l.sink | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *slogHandler) WithAttrs(attrs []slog.Attr) slog.Handler { | ||||||
|  | 	if l.sink == nil || len(attrs) == 0 { | ||||||
|  | 		return l | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	copy := *l | ||||||
|  | 	if l.slogSink != nil { | ||||||
|  | 		copy.slogSink = l.slogSink.WithAttrs(attrs) | ||||||
|  | 		copy.sink = copy.slogSink | ||||||
|  | 	} else { | ||||||
|  | 		kvList := make([]any, 0, 2*len(attrs)) | ||||||
|  | 		for _, attr := range attrs { | ||||||
|  | 			if attr.Key != "" { | ||||||
|  | 				kvList = append(kvList, l.addGroupPrefix(attr.Key), attr.Value.Resolve().Any()) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		copy.sink = l.sink.WithValues(kvList...) | ||||||
|  | 	} | ||||||
|  | 	return © | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *slogHandler) WithGroup(name string) slog.Handler { | ||||||
|  | 	if l.sink == nil { | ||||||
|  | 		return l | ||||||
|  | 	} | ||||||
|  | 	copy := *l | ||||||
|  | 	if l.slogSink != nil { | ||||||
|  | 		copy.slogSink = l.slogSink.WithGroup(name) | ||||||
|  | 		copy.sink = l.slogSink | ||||||
|  | 	} else { | ||||||
|  | 		copy.groupPrefix = copy.addGroupPrefix(name) | ||||||
|  | 	} | ||||||
|  | 	return © | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *slogHandler) addGroupPrefix(name string) string { | ||||||
|  | 	if l.groupPrefix == "" { | ||||||
|  | 		return name | ||||||
|  | 	} | ||||||
|  | 	return l.groupPrefix + groupSeparator + name | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // levelFromSlog adjusts the level by the logger's verbosity and negates it.
 | ||||||
|  | // It ensures that the result is >= 0. This is necessary because the result is
 | ||||||
|  | // passed to a logr.LogSink and that API did not historically document whether
 | ||||||
|  | // levels could be negative or what that meant.
 | ||||||
|  | //
 | ||||||
|  | // Some example usage:
 | ||||||
|  | //     logrV0 := getMyLogger()
 | ||||||
|  | //     logrV2 := logrV0.V(2)
 | ||||||
|  | //     slogV2 := slog.New(slogr.NewSlogHandler(logrV2))
 | ||||||
|  | //     slogV2.Debug("msg") // =~ logrV2.V(4) =~ logrV0.V(6)
 | ||||||
|  | //     slogV2.Info("msg")  // =~  logrV2.V(0) =~ logrV0.V(2)
 | ||||||
|  | //     slogv2.Warn("msg")  // =~ logrV2.V(-4) =~ logrV0.V(0)
 | ||||||
|  | func (l *slogHandler) levelFromSlog(level slog.Level) int { | ||||||
|  | 	result := -level | ||||||
|  | 	result += l.levelBias // in case the original logr.Logger had a V level
 | ||||||
|  | 	if result < 0 { | ||||||
|  | 		result = 0 // because logr.LogSink doesn't expect negative V levels
 | ||||||
|  | 	} | ||||||
|  | 	return int(result) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,108 @@ | ||||||
|  | //go:build go1.21
 | ||||||
|  | // +build go1.21
 | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  | Copyright 2023 The logr Authors. | ||||||
|  | 
 | ||||||
|  | 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 slogr enables usage of a slog.Handler with logr.Logger as front-end
 | ||||||
|  | // API and of a logr.LogSink through the slog.Handler and thus slog.Logger
 | ||||||
|  | // APIs.
 | ||||||
|  | //
 | ||||||
|  | // See the README in the top-level [./logr] package for a discussion of
 | ||||||
|  | // interoperability.
 | ||||||
|  | package slogr | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"log/slog" | ||||||
|  | 
 | ||||||
|  | 	"github.com/go-logr/logr" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // NewLogr returns a logr.Logger which writes to the slog.Handler.
 | ||||||
|  | //
 | ||||||
|  | // The logr verbosity level is mapped to slog levels such that V(0) becomes
 | ||||||
|  | // slog.LevelInfo and V(4) becomes slog.LevelDebug.
 | ||||||
|  | func NewLogr(handler slog.Handler) logr.Logger { | ||||||
|  | 	if handler, ok := handler.(*slogHandler); ok { | ||||||
|  | 		if handler.sink == nil { | ||||||
|  | 			return logr.Discard() | ||||||
|  | 		} | ||||||
|  | 		return logr.New(handler.sink).V(int(handler.levelBias)) | ||||||
|  | 	} | ||||||
|  | 	return logr.New(&slogSink{handler: handler}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewSlogHandler returns a slog.Handler which writes to the same sink as the logr.Logger.
 | ||||||
|  | //
 | ||||||
|  | // The returned logger writes all records with level >= slog.LevelError as
 | ||||||
|  | // error log entries with LogSink.Error, regardless of the verbosity level of
 | ||||||
|  | // the logr.Logger:
 | ||||||
|  | //
 | ||||||
|  | //	logger := <some logr.Logger with 0 as verbosity level>
 | ||||||
|  | //	slog.New(NewSlogHandler(logger.V(10))).Error(...) -> logSink.Error(...)
 | ||||||
|  | //
 | ||||||
|  | // The level of all other records gets reduced by the verbosity
 | ||||||
|  | // level of the logr.Logger and the result is negated. If it happens
 | ||||||
|  | // to be negative, then it gets replaced by zero because a LogSink
 | ||||||
|  | // is not expected to handled negative levels:
 | ||||||
|  | //
 | ||||||
|  | //	slog.New(NewSlogHandler(logger)).Debug(...) -> logger.GetSink().Info(level=4, ...)
 | ||||||
|  | //	slog.New(NewSlogHandler(logger)).Warning(...) -> logger.GetSink().Info(level=0, ...)
 | ||||||
|  | //	slog.New(NewSlogHandler(logger)).Info(...) -> logger.GetSink().Info(level=0, ...)
 | ||||||
|  | //	slog.New(NewSlogHandler(logger.V(4))).Info(...) -> logger.GetSink().Info(level=4, ...)
 | ||||||
|  | func NewSlogHandler(logger logr.Logger) slog.Handler { | ||||||
|  | 	if sink, ok := logger.GetSink().(*slogSink); ok && logger.GetV() == 0 { | ||||||
|  | 		return sink.handler | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	handler := &slogHandler{sink: logger.GetSink(), levelBias: slog.Level(logger.GetV())} | ||||||
|  | 	if slogSink, ok := handler.sink.(SlogSink); ok { | ||||||
|  | 		handler.slogSink = slogSink | ||||||
|  | 	} | ||||||
|  | 	return handler | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SlogSink is an optional interface that a LogSink can implement to support
 | ||||||
|  | // logging through the slog.Logger or slog.Handler APIs better. It then should
 | ||||||
|  | // also support special slog values like slog.Group. When used as a
 | ||||||
|  | // slog.Handler, the advantages are:
 | ||||||
|  | //
 | ||||||
|  | //   - stack unwinding gets avoided in favor of logging the pre-recorded PC,
 | ||||||
|  | //     as intended by slog
 | ||||||
|  | //   - proper grouping of key/value pairs via WithGroup
 | ||||||
|  | //   - verbosity levels > slog.LevelInfo can be recorded
 | ||||||
|  | //   - less overhead
 | ||||||
|  | //
 | ||||||
|  | // Both APIs (logr.Logger and slog.Logger/Handler) then are supported equally
 | ||||||
|  | // well. Developers can pick whatever API suits them better and/or mix
 | ||||||
|  | // packages which use either API in the same binary with a common logging
 | ||||||
|  | // implementation.
 | ||||||
|  | //
 | ||||||
|  | // This interface is necessary because the type implementing the LogSink
 | ||||||
|  | // interface cannot also implement the slog.Handler interface due to the
 | ||||||
|  | // different prototype of the common Enabled method.
 | ||||||
|  | //
 | ||||||
|  | // An implementation could support both interfaces in two different types, but then
 | ||||||
|  | // additional interfaces would be needed to convert between those types in NewLogr
 | ||||||
|  | // and NewSlogHandler.
 | ||||||
|  | type SlogSink interface { | ||||||
|  | 	logr.LogSink | ||||||
|  | 
 | ||||||
|  | 	Handle(ctx context.Context, record slog.Record) error | ||||||
|  | 	WithAttrs(attrs []slog.Attr) SlogSink | ||||||
|  | 	WithGroup(name string) SlogSink | ||||||
|  | } | ||||||
|  | @ -0,0 +1,122 @@ | ||||||
|  | //go:build go1.21
 | ||||||
|  | // +build go1.21
 | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  | Copyright 2023 The logr Authors. | ||||||
|  | 
 | ||||||
|  | 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 slogr | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"log/slog" | ||||||
|  | 	"runtime" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/go-logr/logr" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	_ logr.LogSink          = &slogSink{} | ||||||
|  | 	_ logr.CallDepthLogSink = &slogSink{} | ||||||
|  | 	_ Underlier             = &slogSink{} | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Underlier is implemented by the LogSink returned by NewLogr.
 | ||||||
|  | type Underlier interface { | ||||||
|  | 	// GetUnderlying returns the Handler used by the LogSink.
 | ||||||
|  | 	GetUnderlying() slog.Handler | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// nameKey is used to log the `WithName` values as an additional attribute.
 | ||||||
|  | 	nameKey = "logger" | ||||||
|  | 
 | ||||||
|  | 	// errKey is used to log the error parameter of Error as an additional attribute.
 | ||||||
|  | 	errKey = "err" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type slogSink struct { | ||||||
|  | 	callDepth int | ||||||
|  | 	name      string | ||||||
|  | 	handler   slog.Handler | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *slogSink) Init(info logr.RuntimeInfo) { | ||||||
|  | 	l.callDepth = info.CallDepth | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *slogSink) GetUnderlying() slog.Handler { | ||||||
|  | 	return l.handler | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *slogSink) WithCallDepth(depth int) logr.LogSink { | ||||||
|  | 	newLogger := *l | ||||||
|  | 	newLogger.callDepth += depth | ||||||
|  | 	return &newLogger | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *slogSink) Enabled(level int) bool { | ||||||
|  | 	return l.handler.Enabled(context.Background(), slog.Level(-level)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *slogSink) Info(level int, msg string, kvList ...interface{}) { | ||||||
|  | 	l.log(nil, msg, slog.Level(-level), kvList...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *slogSink) Error(err error, msg string, kvList ...interface{}) { | ||||||
|  | 	l.log(err, msg, slog.LevelError, kvList...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *slogSink) log(err error, msg string, level slog.Level, kvList ...interface{}) { | ||||||
|  | 	var pcs [1]uintptr | ||||||
|  | 	// skip runtime.Callers, this function, Info/Error, and all helper functions above that.
 | ||||||
|  | 	runtime.Callers(3+l.callDepth, pcs[:]) | ||||||
|  | 
 | ||||||
|  | 	record := slog.NewRecord(time.Now(), level, msg, pcs[0]) | ||||||
|  | 	if l.name != "" { | ||||||
|  | 		record.AddAttrs(slog.String(nameKey, l.name)) | ||||||
|  | 	} | ||||||
|  | 	if err != nil { | ||||||
|  | 		record.AddAttrs(slog.Any(errKey, err)) | ||||||
|  | 	} | ||||||
|  | 	record.Add(kvList...) | ||||||
|  | 	l.handler.Handle(context.Background(), record) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l slogSink) WithName(name string) logr.LogSink { | ||||||
|  | 	if l.name != "" { | ||||||
|  | 		l.name = l.name + "/" | ||||||
|  | 	} | ||||||
|  | 	l.name += name | ||||||
|  | 	return &l | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l slogSink) WithValues(kvList ...interface{}) logr.LogSink { | ||||||
|  | 	l.handler = l.handler.WithAttrs(kvListToAttrs(kvList...)) | ||||||
|  | 	return &l | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func kvListToAttrs(kvList ...interface{}) []slog.Attr { | ||||||
|  | 	// We don't need the record itself, only its Add method.
 | ||||||
|  | 	record := slog.NewRecord(time.Time{}, 0, "", 0) | ||||||
|  | 	record.Add(kvList...) | ||||||
|  | 	attrs := make([]slog.Attr, 0, record.NumAttrs()) | ||||||
|  | 	record.Attrs(func(attr slog.Attr) bool { | ||||||
|  | 		attrs = append(attrs, attr) | ||||||
|  | 		return true | ||||||
|  | 	}) | ||||||
|  | 	return attrs | ||||||
|  | } | ||||||
|  | @ -1,15 +0,0 @@ | ||||||
| after_success: |  | ||||||
| - bash <(curl -s https://codecov.io/bash) |  | ||||||
| go: |  | ||||||
| - 1.14.x |  | ||||||
| - 1.15.x |  | ||||||
| install: |  | ||||||
| - GO111MODULE=off go get -u gotest.tools/gotestsum |  | ||||||
| env: |  | ||||||
| - GO111MODULE=on |  | ||||||
| language: go |  | ||||||
| notifications: |  | ||||||
|   slack: |  | ||||||
|     secure: a5VgoiwB1G/AZqzmephPZIhEB9avMlsWSlVnM1dSAtYAwdrQHGTQxAmpOxYIoSPDhWNN5bfZmjd29++UlTwLcHSR+e0kJhH6IfDlsHj/HplNCJ9tyI0zYc7XchtdKgeMxMzBKCzgwFXGSbQGydXTliDNBo0HOzmY3cou/daMFTP60K+offcjS+3LRAYb1EroSRXZqrk1nuF/xDL3792DZUdPMiFR/L/Df6y74D6/QP4sTkTDFQitz4Wy/7jbsfj8dG6qK2zivgV6/l+w4OVjFkxVpPXogDWY10vVXNVynqxfJ7to2d1I9lNCHE2ilBCkWMIPdyJF7hjF8pKW+82yP4EzRh0vu8Xn0HT5MZpQxdRY/YMxNrWaG7SxsoEaO4q5uhgdzAqLYY3TRa7MjIK+7Ur+aqOeTXn6OKwVi0CjvZ6mIU3WUKSwiwkFZMbjRAkSb5CYwMEfGFO/z964xz83qGt6WAtBXNotqCQpTIiKtDHQeLOMfksHImCg6JLhQcWBVxamVgu0G3Pdh8Y6DyPnxraXY95+QDavbjqv7TeYT9T/FNnrkXaTTK0s4iWE5H4ACU0Qvz0wUYgfQrZv0/Hp7V17+rabUwnzYySHCy9SWX/7OV9Cfh31iMp9ZIffr76xmmThtOEqs8TrTtU6BWI3rWwvA9cXQipZTVtL0oswrGw= |  | ||||||
| script: |  | ||||||
| - gotestsum -f short-verbose -- -race -coverprofile=coverage.txt -covermode=atomic ./... |  | ||||||
|  | @ -1,8 +1,6 @@ | ||||||
| linters-settings: | linters-settings: | ||||||
|   govet: |   govet: | ||||||
|     check-shadowing: true |     check-shadowing: true | ||||||
|   golint: |  | ||||||
|     min-confidence: 0 |  | ||||||
|   gocyclo: |   gocyclo: | ||||||
|     min-complexity: 30 |     min-complexity: 30 | ||||||
|   maligned: |   maligned: | ||||||
|  | @ -12,6 +10,8 @@ linters-settings: | ||||||
|   goconst: |   goconst: | ||||||
|     min-len: 2 |     min-len: 2 | ||||||
|     min-occurrences: 4 |     min-occurrences: 4 | ||||||
|  |   paralleltest: | ||||||
|  |     ignore-missing: true | ||||||
| linters: | linters: | ||||||
|   enable-all: true |   enable-all: true | ||||||
|   disable: |   disable: | ||||||
|  | @ -39,3 +39,12 @@ linters: | ||||||
|     - nestif |     - nestif | ||||||
|     - godot |     - godot | ||||||
|     - errorlint |     - errorlint | ||||||
|  |     - varcheck | ||||||
|  |     - interfacer | ||||||
|  |     - deadcode | ||||||
|  |     - golint | ||||||
|  |     - ifshort | ||||||
|  |     - structcheck | ||||||
|  |     - nosnakecase | ||||||
|  |     - varnamelen | ||||||
|  |     - exhaustruct | ||||||
|  |  | ||||||
|  | @ -1,24 +0,0 @@ | ||||||
| after_success: |  | ||||||
| - bash <(curl -s https://codecov.io/bash) |  | ||||||
| go: |  | ||||||
| - 1.14.x |  | ||||||
| - 1.x |  | ||||||
| install: |  | ||||||
| - go get gotest.tools/gotestsum |  | ||||||
| jobs: |  | ||||||
|   include: |  | ||||||
|   # include linting job, but only for latest go version and amd64 arch |  | ||||||
|   - go: 1.x |  | ||||||
|     arch: amd64 |  | ||||||
|     install: |  | ||||||
|       go get github.com/golangci/golangci-lint/cmd/golangci-lint |  | ||||||
|     script: |  | ||||||
|     - golangci-lint run --new-from-rev master |  | ||||||
| env: |  | ||||||
| - GO111MODULE=on |  | ||||||
| language: go |  | ||||||
| notifications: |  | ||||||
|   slack: |  | ||||||
|     secure: OpQG/36F7DSF00HLm9WZMhyqFCYYyYTsVDObW226cWiR8PWYiNfLZiSEvIzT1Gx4dDjhigKTIqcLhG34CkL5iNXDjm9Yyo2RYhQPlK8NErNqUEXuBqn4RqYHW48VGhEhOyDd4Ei0E2FN5ZbgpvHgtpkdZ6XDi64r3Ac89isP9aPHXQTuv2Jog6b4/OKKiUTftLcTIst0p4Cp3gqOJWf1wnoj+IadWiECNVQT6zb47IYjtyw6+uV8iUjTzdKcRB6Zc6b4Dq7JAg1Zd7Jfxkql3hlKp4PNlRf9Cy7y5iA3G7MLyg3FcPX5z2kmcyPt2jOTRMBWUJ5zIQpOxizAcN8WsT3WWBL5KbuYK6k0PzujrIDLqdxGpNmjkkMfDBT9cKmZpm2FdW+oZgPFJP+oKmAo4u4KJz/vjiPTXgQlN5bmrLuRMCp+AwC5wkIohTqWZVPE2TK6ZSnMYcg/W39s+RP/9mJoyryAvPSpBOLTI+biCgaUCTOAZxNTWpMFc3tPYntc41WWkdKcooZ9JA5DwfcaVFyTGQ3YXz+HvX6G1z/gW0Q/A4dBi9mj2iE1xm7tRTT+4VQ2AXFvSEI1HJpfPgYnwAtwOD1v3Qm2EUHk9sCdtEDR4wVGEPIVn44GnwFMnGKx9JWppMPYwFu3SVDdHt+E+LOlhZUply11Aa+IVrT2KUQ= |  | ||||||
| script: |  | ||||||
| - gotestsum -f short-verbose -- -race -coverprofile=coverage.txt -covermode=atomic ./... |  | ||||||
|  | @ -7,8 +7,8 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| 	defaultHttpPort  = ":80" | 	defaultHTTPPort  = ":80" | ||||||
| 	defaultHttpsPort = ":443" | 	defaultHTTPSPort = ":443" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Regular expressions used by the normalizations
 | // Regular expressions used by the normalizations
 | ||||||
|  | @ -18,18 +18,24 @@ var rxDupSlashes = regexp.MustCompile(`/{2,}`) | ||||||
| // NormalizeURL will normalize the specified URL
 | // NormalizeURL will normalize the specified URL
 | ||||||
| // This was added to replace a previous call to the no longer maintained purell library:
 | // This was added to replace a previous call to the no longer maintained purell library:
 | ||||||
| // The call that was used looked like the following:
 | // The call that was used looked like the following:
 | ||||||
| //   url.Parse(purell.NormalizeURL(parsed, purell.FlagsSafe|purell.FlagRemoveDuplicateSlashes))
 | //
 | ||||||
|  | //	url.Parse(purell.NormalizeURL(parsed, purell.FlagsSafe|purell.FlagRemoveDuplicateSlashes))
 | ||||||
| //
 | //
 | ||||||
| // To explain all that was included in the call above, purell.FlagsSafe was really just the following:
 | // To explain all that was included in the call above, purell.FlagsSafe was really just the following:
 | ||||||
| //	  - FlagLowercaseScheme
 | //   - FlagLowercaseScheme
 | ||||||
| //	  - FlagLowercaseHost
 | //   - FlagLowercaseHost
 | ||||||
| //	  - FlagRemoveDefaultPort
 | //   - FlagRemoveDefaultPort
 | ||||||
| //	  - FlagRemoveDuplicateSlashes (and this was mixed in with the |)
 | //   - FlagRemoveDuplicateSlashes (and this was mixed in with the |)
 | ||||||
|  | //
 | ||||||
|  | // This also normalizes the URL into its urlencoded form by removing RawPath and RawFragment.
 | ||||||
| func NormalizeURL(u *url.URL) { | func NormalizeURL(u *url.URL) { | ||||||
| 	lowercaseScheme(u) | 	lowercaseScheme(u) | ||||||
| 	lowercaseHost(u) | 	lowercaseHost(u) | ||||||
| 	removeDefaultPort(u) | 	removeDefaultPort(u) | ||||||
| 	removeDuplicateSlashes(u) | 	removeDuplicateSlashes(u) | ||||||
|  | 
 | ||||||
|  | 	u.RawPath = "" | ||||||
|  | 	u.RawFragment = "" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func lowercaseScheme(u *url.URL) { | func lowercaseScheme(u *url.URL) { | ||||||
|  | @ -48,7 +54,7 @@ func removeDefaultPort(u *url.URL) { | ||||||
| 	if len(u.Host) > 0 { | 	if len(u.Host) > 0 { | ||||||
| 		scheme := strings.ToLower(u.Scheme) | 		scheme := strings.ToLower(u.Scheme) | ||||||
| 		u.Host = rxPort.ReplaceAllStringFunc(u.Host, func(val string) string { | 		u.Host = rxPort.ReplaceAllStringFunc(u.Host, func(val string) string { | ||||||
| 			if (scheme == "http" && val == defaultHttpPort) || (scheme == "https" && val == defaultHttpsPort) { | 			if (scheme == "http" && val == defaultHTTPPort) || (scheme == "https" && val == defaultHTTPSPort) { | ||||||
| 				return "" | 				return "" | ||||||
| 			} | 			} | ||||||
| 			return val | 			return val | ||||||
|  |  | ||||||
|  | @ -0,0 +1,2 @@ | ||||||
|  | # gofmt always uses LF, whereas Git uses CRLF on Windows. | ||||||
|  | *.go text eol=lf | ||||||
|  | @ -37,3 +37,18 @@ linters: | ||||||
|     - gci |     - gci | ||||||
|     - gocognit |     - gocognit | ||||||
|     - paralleltest |     - paralleltest | ||||||
|  |     - thelper | ||||||
|  |     - ifshort | ||||||
|  |     - gomoddirectives | ||||||
|  |     - cyclop | ||||||
|  |     - forcetypeassert | ||||||
|  |     - ireturn | ||||||
|  |     - tagliatelle | ||||||
|  |     - varnamelen | ||||||
|  |     - goimports | ||||||
|  |     - tenv | ||||||
|  |     - golint | ||||||
|  |     - exhaustruct | ||||||
|  |     - nilnil | ||||||
|  |     - nonamedreturns | ||||||
|  |     - nosnakecase | ||||||
|  |  | ||||||
|  | @ -1,37 +0,0 @@ | ||||||
| after_success: |  | ||||||
| - bash <(curl -s https://codecov.io/bash) |  | ||||||
| go: |  | ||||||
| - 1.14.x |  | ||||||
| - 1.x |  | ||||||
| arch: |  | ||||||
| - amd64 |  | ||||||
| jobs: |  | ||||||
|   include: |  | ||||||
|   # include arch ppc, but only for latest go version - skip testing for race |  | ||||||
|   - go: 1.x |  | ||||||
|     arch: ppc64le |  | ||||||
|     install: ~ |  | ||||||
|     script: |  | ||||||
|     - go test -v |  | ||||||
| 
 |  | ||||||
|   #- go: 1.x |  | ||||||
|   #  arch: arm |  | ||||||
|   #  install: ~ |  | ||||||
|   #  script: |  | ||||||
|   #  - go test -v |  | ||||||
| 
 |  | ||||||
|   # include linting job, but only for latest go version and amd64 arch |  | ||||||
|   - go: 1.x |  | ||||||
|     arch: amd64 |  | ||||||
|     install: |  | ||||||
|       go get github.com/golangci/golangci-lint/cmd/golangci-lint |  | ||||||
|     script: |  | ||||||
|     - golangci-lint run --new-from-rev master |  | ||||||
| install: |  | ||||||
| - GO111MODULE=off go get -u gotest.tools/gotestsum |  | ||||||
| language: go |  | ||||||
| notifications: |  | ||||||
|   slack: |  | ||||||
|     secure: QUWvCkBBK09GF7YtEvHHVt70JOkdlNBG0nIKu/5qc4/nW5HP8I2w0SEf/XR2je0eED1Qe3L/AfMCWwrEj+IUZc3l4v+ju8X8R3Lomhme0Eb0jd1MTMCuPcBT47YCj0M7RON7vXtbFfm1hFJ/jLe5+9FXz0hpXsR24PJc5ZIi/ogNwkaPqG4BmndzecpSh0vc2FJPZUD9LT0I09REY/vXR0oQAalLkW0asGD5taHZTUZq/kBpsNxaAFrLM23i4mUcf33M5fjLpvx5LRICrX/57XpBrDh2TooBU6Qj3CgoY0uPRYUmSNxbVx1czNzl2JtEpb5yjoxfVPQeg0BvQM00G8LJINISR+ohrjhkZmAqchDupAX+yFrxTtORa78CtnIL6z/aTNlgwwVD8kvL/1pFA/JWYmKDmz93mV/+6wubGzNSQCstzjkFA4/iZEKewKUoRIAi/fxyscP6L/rCpmY/4llZZvrnyTqVbt6URWpopUpH4rwYqreXAtJxJsfBJIeSmUIiDIOMGkCTvyTEW3fWGmGoqWtSHLoaWDyAIGb7azb+KvfpWtEcoPFWfSWU+LGee0A/YsUhBl7ADB9A0CJEuR8q4BPpKpfLwPKSiKSAXL7zDkyjExyhtgqbSl2jS+rKIHOZNL8JkCcTP2MKMVd563C5rC5FMKqu3S9m2b6380E= |  | ||||||
| script: |  | ||||||
| - gotestsum -f short-verbose -- -race -coverprofile=coverage.txt -covermode=atomic ./... |  | ||||||
|  | @ -17,16 +17,15 @@ Package swag contains a bunch of helper functions for go-openapi and go-swagger | ||||||
| 
 | 
 | ||||||
| You may also use it standalone for your projects. | You may also use it standalone for your projects. | ||||||
| 
 | 
 | ||||||
|   * convert between value and pointers for builtin types |   - convert between value and pointers for builtin types | ||||||
|   * convert from string to builtin types (wraps strconv) |   - convert from string to builtin types (wraps strconv) | ||||||
|   * fast json concatenation |   - fast json concatenation | ||||||
|   * search in path |   - search in path | ||||||
|   * load from file or http |   - load from file or http | ||||||
|   * name mangling |   - name mangling | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| This repo has only few dependencies outside of the standard library: | This repo has only few dependencies outside of the standard library: | ||||||
| 
 | 
 | ||||||
|   * YAML utilities depend on gopkg.in/yaml.v2 |   - YAML utilities depend on gopkg.in/yaml.v2 | ||||||
| */ | */ | ||||||
| package swag | package swag | ||||||
|  |  | ||||||
|  | @ -0,0 +1,33 @@ | ||||||
|  | // Copyright 2015 go-swagger maintainers
 | ||||||
|  | //
 | ||||||
|  | // 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 swag | ||||||
|  | 
 | ||||||
|  | import "mime/multipart" | ||||||
|  | 
 | ||||||
|  | // File represents an uploaded file.
 | ||||||
|  | type File struct { | ||||||
|  | 	Data   multipart.File | ||||||
|  | 	Header *multipart.FileHeader | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Read bytes from the file
 | ||||||
|  | func (f *File) Read(p []byte) (n int, err error) { | ||||||
|  | 	return f.Data.Read(p) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Close the file
 | ||||||
|  | func (f *File) Close() error { | ||||||
|  | 	return f.Data.Close() | ||||||
|  | } | ||||||
|  | @ -16,10 +16,11 @@ package swag | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io/ioutil" | 	"io" | ||||||
| 	"log" | 	"log" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
|  | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"runtime" | 	"runtime" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | @ -40,13 +41,13 @@ var LoadHTTPCustomHeaders = map[string]string{} | ||||||
| 
 | 
 | ||||||
| // LoadFromFileOrHTTP loads the bytes from a file or a remote http server based on the path passed in
 | // LoadFromFileOrHTTP loads the bytes from a file or a remote http server based on the path passed in
 | ||||||
| func LoadFromFileOrHTTP(path string) ([]byte, error) { | func LoadFromFileOrHTTP(path string) ([]byte, error) { | ||||||
| 	return LoadStrategy(path, ioutil.ReadFile, loadHTTPBytes(LoadHTTPTimeout))(path) | 	return LoadStrategy(path, os.ReadFile, loadHTTPBytes(LoadHTTPTimeout))(path) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // LoadFromFileOrHTTPWithTimeout loads the bytes from a file or a remote http server based on the path passed in
 | // LoadFromFileOrHTTPWithTimeout loads the bytes from a file or a remote http server based on the path passed in
 | ||||||
| // timeout arg allows for per request overriding of the request timeout
 | // timeout arg allows for per request overriding of the request timeout
 | ||||||
| func LoadFromFileOrHTTPWithTimeout(path string, timeout time.Duration) ([]byte, error) { | func LoadFromFileOrHTTPWithTimeout(path string, timeout time.Duration) ([]byte, error) { | ||||||
| 	return LoadStrategy(path, ioutil.ReadFile, loadHTTPBytes(timeout))(path) | 	return LoadStrategy(path, os.ReadFile, loadHTTPBytes(timeout))(path) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // LoadStrategy returns a loader function for a given path or uri
 | // LoadStrategy returns a loader function for a given path or uri
 | ||||||
|  | @ -86,7 +87,7 @@ func LoadStrategy(path string, local, remote func(string) ([]byte, error)) func( | ||||||
| func loadHTTPBytes(timeout time.Duration) func(path string) ([]byte, error) { | func loadHTTPBytes(timeout time.Duration) func(path string) ([]byte, error) { | ||||||
| 	return func(path string) ([]byte, error) { | 	return func(path string) ([]byte, error) { | ||||||
| 		client := &http.Client{Timeout: timeout} | 		client := &http.Client{Timeout: timeout} | ||||||
| 		req, err := http.NewRequest("GET", path, nil) // nolint: noctx
 | 		req, err := http.NewRequest(http.MethodGet, path, nil) //nolint:noctx
 | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  | @ -115,6 +116,6 @@ func loadHTTPBytes(timeout time.Duration) func(path string) ([]byte, error) { | ||||||
| 			return nil, fmt.Errorf("could not access document at %q [%s] ", path, resp.Status) | 			return nil, fmt.Errorf("could not access document at %q [%s] ", path, resp.Status) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		return ioutil.ReadAll(resp.Body) | 		return io.ReadAll(resp.Body) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ | ||||||
| // See the License for the specific language governing permissions and
 | // See the License for the specific language governing permissions and
 | ||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
|  | //go:build go1.8
 | ||||||
| // +build go1.8
 | // +build go1.8
 | ||||||
| 
 | 
 | ||||||
| package swag | package swag | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ | ||||||
| // See the License for the specific language governing permissions and
 | // See the License for the specific language governing permissions and
 | ||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
|  | //go:build go1.9
 | ||||||
| // +build go1.9
 | // +build go1.9
 | ||||||
| 
 | 
 | ||||||
| package swag | package swag | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ | ||||||
| // See the License for the specific language governing permissions and
 | // See the License for the specific language governing permissions and
 | ||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
|  | //go:build !go1.8
 | ||||||
| // +build !go1.8
 | // +build !go1.8
 | ||||||
| 
 | 
 | ||||||
| package swag | package swag | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ | ||||||
| // See the License for the specific language governing permissions and
 | // See the License for the specific language governing permissions and
 | ||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
|  | //go:build !go1.9
 | ||||||
| // +build !go1.9
 | // +build !go1.9
 | ||||||
| 
 | 
 | ||||||
| package swag | package swag | ||||||
|  |  | ||||||
|  | @ -99,10 +99,11 @@ const ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // JoinByFormat joins a string array by a known format (e.g. swagger's collectionFormat attribute):
 | // JoinByFormat joins a string array by a known format (e.g. swagger's collectionFormat attribute):
 | ||||||
| //		ssv: space separated value
 | //
 | ||||||
| //		tsv: tab separated value
 | //	ssv: space separated value
 | ||||||
| //		pipes: pipe (|) separated value
 | //	tsv: tab separated value
 | ||||||
| //		csv: comma separated value (default)
 | //	pipes: pipe (|) separated value
 | ||||||
|  | //	csv: comma separated value (default)
 | ||||||
| func JoinByFormat(data []string, format string) []string { | func JoinByFormat(data []string, format string) []string { | ||||||
| 	if len(data) == 0 { | 	if len(data) == 0 { | ||||||
| 		return data | 		return data | ||||||
|  | @ -124,11 +125,11 @@ func JoinByFormat(data []string, format string) []string { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SplitByFormat splits a string by a known format:
 | // SplitByFormat splits a string by a known format:
 | ||||||
| //		ssv: space separated value
 |  | ||||||
| //		tsv: tab separated value
 |  | ||||||
| //		pipes: pipe (|) separated value
 |  | ||||||
| //		csv: comma separated value (default)
 |  | ||||||
| //
 | //
 | ||||||
|  | //	ssv: space separated value
 | ||||||
|  | //	tsv: tab separated value
 | ||||||
|  | //	pipes: pipe (|) separated value
 | ||||||
|  | //	csv: comma separated value (default)
 | ||||||
| func SplitByFormat(data, format string) []string { | func SplitByFormat(data, format string) []string { | ||||||
| 	if data == "" { | 	if data == "" { | ||||||
| 		return nil | 		return nil | ||||||
|  |  | ||||||
|  | @ -22,7 +22,7 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.com/mailru/easyjson/jlexer" | 	"github.com/mailru/easyjson/jlexer" | ||||||
| 	"github.com/mailru/easyjson/jwriter" | 	"github.com/mailru/easyjson/jwriter" | ||||||
| 	yaml "gopkg.in/yaml.v2" | 	yaml "gopkg.in/yaml.v3" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // YAMLMatcher matches yaml
 | // YAMLMatcher matches yaml
 | ||||||
|  | @ -43,16 +43,126 @@ func YAMLToJSON(data interface{}) (json.RawMessage, error) { | ||||||
| 
 | 
 | ||||||
| // BytesToYAMLDoc converts a byte slice into a YAML document
 | // BytesToYAMLDoc converts a byte slice into a YAML document
 | ||||||
| func BytesToYAMLDoc(data []byte) (interface{}, error) { | func BytesToYAMLDoc(data []byte) (interface{}, error) { | ||||||
| 	var canary map[interface{}]interface{} // validate this is an object and not a different type
 | 	var document yaml.Node // preserve order that is present in the document
 | ||||||
| 	if err := yaml.Unmarshal(data, &canary); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var document yaml.MapSlice // preserve order that is present in the document
 |  | ||||||
| 	if err := yaml.Unmarshal(data, &document); err != nil { | 	if err := yaml.Unmarshal(data, &document); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	return document, nil | 	if document.Kind != yaml.DocumentNode || len(document.Content) != 1 || document.Content[0].Kind != yaml.MappingNode { | ||||||
|  | 		return nil, fmt.Errorf("only YAML documents that are objects are supported") | ||||||
|  | 	} | ||||||
|  | 	return &document, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func yamlNode(root *yaml.Node) (interface{}, error) { | ||||||
|  | 	switch root.Kind { | ||||||
|  | 	case yaml.DocumentNode: | ||||||
|  | 		return yamlDocument(root) | ||||||
|  | 	case yaml.SequenceNode: | ||||||
|  | 		return yamlSequence(root) | ||||||
|  | 	case yaml.MappingNode: | ||||||
|  | 		return yamlMapping(root) | ||||||
|  | 	case yaml.ScalarNode: | ||||||
|  | 		return yamlScalar(root) | ||||||
|  | 	case yaml.AliasNode: | ||||||
|  | 		return yamlNode(root.Alias) | ||||||
|  | 	default: | ||||||
|  | 		return nil, fmt.Errorf("unsupported YAML node type: %v", root.Kind) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func yamlDocument(node *yaml.Node) (interface{}, error) { | ||||||
|  | 	if len(node.Content) != 1 { | ||||||
|  | 		return nil, fmt.Errorf("unexpected YAML Document node content length: %d", len(node.Content)) | ||||||
|  | 	} | ||||||
|  | 	return yamlNode(node.Content[0]) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func yamlMapping(node *yaml.Node) (interface{}, error) { | ||||||
|  | 	m := make(JSONMapSlice, len(node.Content)/2) | ||||||
|  | 
 | ||||||
|  | 	var j int | ||||||
|  | 	for i := 0; i < len(node.Content); i += 2 { | ||||||
|  | 		var nmi JSONMapItem | ||||||
|  | 		k, err := yamlStringScalarC(node.Content[i]) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("unable to decode YAML map key: %w", err) | ||||||
|  | 		} | ||||||
|  | 		nmi.Key = k | ||||||
|  | 		v, err := yamlNode(node.Content[i+1]) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("unable to process YAML map value for key %q: %w", k, err) | ||||||
|  | 		} | ||||||
|  | 		nmi.Value = v | ||||||
|  | 		m[j] = nmi | ||||||
|  | 		j++ | ||||||
|  | 	} | ||||||
|  | 	return m, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func yamlSequence(node *yaml.Node) (interface{}, error) { | ||||||
|  | 	s := make([]interface{}, 0) | ||||||
|  | 
 | ||||||
|  | 	for i := 0; i < len(node.Content); i++ { | ||||||
|  | 
 | ||||||
|  | 		v, err := yamlNode(node.Content[i]) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("unable to decode YAML sequence value: %w", err) | ||||||
|  | 		} | ||||||
|  | 		s = append(s, v) | ||||||
|  | 	} | ||||||
|  | 	return s, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const ( // See https://yaml.org/type/
 | ||||||
|  | 	yamlStringScalar = "tag:yaml.org,2002:str" | ||||||
|  | 	yamlIntScalar    = "tag:yaml.org,2002:int" | ||||||
|  | 	yamlBoolScalar   = "tag:yaml.org,2002:bool" | ||||||
|  | 	yamlFloatScalar  = "tag:yaml.org,2002:float" | ||||||
|  | 	yamlTimestamp    = "tag:yaml.org,2002:timestamp" | ||||||
|  | 	yamlNull         = "tag:yaml.org,2002:null" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func yamlScalar(node *yaml.Node) (interface{}, error) { | ||||||
|  | 	switch node.LongTag() { | ||||||
|  | 	case yamlStringScalar: | ||||||
|  | 		return node.Value, nil | ||||||
|  | 	case yamlBoolScalar: | ||||||
|  | 		b, err := strconv.ParseBool(node.Value) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("unable to process scalar node. Got %q. Expecting bool content: %w", node.Value, err) | ||||||
|  | 		} | ||||||
|  | 		return b, nil | ||||||
|  | 	case yamlIntScalar: | ||||||
|  | 		i, err := strconv.ParseInt(node.Value, 10, 64) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("unable to process scalar node. Got %q. Expecting integer content: %w", node.Value, err) | ||||||
|  | 		} | ||||||
|  | 		return i, nil | ||||||
|  | 	case yamlFloatScalar: | ||||||
|  | 		f, err := strconv.ParseFloat(node.Value, 64) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("unable to process scalar node. Got %q. Expecting float content: %w", node.Value, err) | ||||||
|  | 		} | ||||||
|  | 		return f, nil | ||||||
|  | 	case yamlTimestamp: | ||||||
|  | 		return node.Value, nil | ||||||
|  | 	case yamlNull: | ||||||
|  | 		return nil, nil | ||||||
|  | 	default: | ||||||
|  | 		return nil, fmt.Errorf("YAML tag %q is not supported", node.LongTag()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func yamlStringScalarC(node *yaml.Node) (string, error) { | ||||||
|  | 	if node.Kind != yaml.ScalarNode { | ||||||
|  | 		return "", fmt.Errorf("expecting a string scalar but got %q", node.Kind) | ||||||
|  | 	} | ||||||
|  | 	switch node.LongTag() { | ||||||
|  | 	case yamlStringScalar, yamlIntScalar, yamlFloatScalar: | ||||||
|  | 		return node.Value, nil | ||||||
|  | 	default: | ||||||
|  | 		return "", fmt.Errorf("YAML tag %q is not supported as map key", node.LongTag()) | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // JSONMapSlice represent a JSON object, with the order of keys maintained
 | // JSONMapSlice represent a JSON object, with the order of keys maintained
 | ||||||
|  | @ -105,6 +215,113 @@ func (s *JSONMapSlice) UnmarshalEasyJSON(in *jlexer.Lexer) { | ||||||
| 	*s = result | 	*s = result | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (s JSONMapSlice) MarshalYAML() (interface{}, error) { | ||||||
|  | 	var n yaml.Node | ||||||
|  | 	n.Kind = yaml.DocumentNode | ||||||
|  | 	var nodes []*yaml.Node | ||||||
|  | 	for _, item := range s { | ||||||
|  | 		nn, err := json2yaml(item.Value) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		ns := []*yaml.Node{ | ||||||
|  | 			{ | ||||||
|  | 				Kind:  yaml.ScalarNode, | ||||||
|  | 				Tag:   yamlStringScalar, | ||||||
|  | 				Value: item.Key, | ||||||
|  | 			}, | ||||||
|  | 			nn, | ||||||
|  | 		} | ||||||
|  | 		nodes = append(nodes, ns...) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	n.Content = []*yaml.Node{ | ||||||
|  | 		{ | ||||||
|  | 			Kind:    yaml.MappingNode, | ||||||
|  | 			Content: nodes, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return yaml.Marshal(&n) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func json2yaml(item interface{}) (*yaml.Node, error) { | ||||||
|  | 	switch val := item.(type) { | ||||||
|  | 	case JSONMapSlice: | ||||||
|  | 		var n yaml.Node | ||||||
|  | 		n.Kind = yaml.MappingNode | ||||||
|  | 		for i := range val { | ||||||
|  | 			childNode, err := json2yaml(&val[i].Value) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			n.Content = append(n.Content, &yaml.Node{ | ||||||
|  | 				Kind:  yaml.ScalarNode, | ||||||
|  | 				Tag:   yamlStringScalar, | ||||||
|  | 				Value: val[i].Key, | ||||||
|  | 			}, childNode) | ||||||
|  | 		} | ||||||
|  | 		return &n, nil | ||||||
|  | 	case map[string]interface{}: | ||||||
|  | 		var n yaml.Node | ||||||
|  | 		n.Kind = yaml.MappingNode | ||||||
|  | 		for k, v := range val { | ||||||
|  | 			childNode, err := json2yaml(v) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			n.Content = append(n.Content, &yaml.Node{ | ||||||
|  | 				Kind:  yaml.ScalarNode, | ||||||
|  | 				Tag:   yamlStringScalar, | ||||||
|  | 				Value: k, | ||||||
|  | 			}, childNode) | ||||||
|  | 		} | ||||||
|  | 		return &n, nil | ||||||
|  | 	case []interface{}: | ||||||
|  | 		var n yaml.Node | ||||||
|  | 		n.Kind = yaml.SequenceNode | ||||||
|  | 		for i := range val { | ||||||
|  | 			childNode, err := json2yaml(val[i]) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			n.Content = append(n.Content, childNode) | ||||||
|  | 		} | ||||||
|  | 		return &n, nil | ||||||
|  | 	case string: | ||||||
|  | 		return &yaml.Node{ | ||||||
|  | 			Kind:  yaml.ScalarNode, | ||||||
|  | 			Tag:   yamlStringScalar, | ||||||
|  | 			Value: val, | ||||||
|  | 		}, nil | ||||||
|  | 	case float64: | ||||||
|  | 		return &yaml.Node{ | ||||||
|  | 			Kind:  yaml.ScalarNode, | ||||||
|  | 			Tag:   yamlFloatScalar, | ||||||
|  | 			Value: strconv.FormatFloat(val, 'f', -1, 64), | ||||||
|  | 		}, nil | ||||||
|  | 	case int64: | ||||||
|  | 		return &yaml.Node{ | ||||||
|  | 			Kind:  yaml.ScalarNode, | ||||||
|  | 			Tag:   yamlIntScalar, | ||||||
|  | 			Value: strconv.FormatInt(val, 10), | ||||||
|  | 		}, nil | ||||||
|  | 	case uint64: | ||||||
|  | 		return &yaml.Node{ | ||||||
|  | 			Kind:  yaml.ScalarNode, | ||||||
|  | 			Tag:   yamlIntScalar, | ||||||
|  | 			Value: strconv.FormatUint(val, 10), | ||||||
|  | 		}, nil | ||||||
|  | 	case bool: | ||||||
|  | 		return &yaml.Node{ | ||||||
|  | 			Kind:  yaml.ScalarNode, | ||||||
|  | 			Tag:   yamlBoolScalar, | ||||||
|  | 			Value: strconv.FormatBool(val), | ||||||
|  | 		}, nil | ||||||
|  | 	} | ||||||
|  | 	return nil, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // JSONMapItem represents the value of a key in a JSON object held by JSONMapSlice
 | // JSONMapItem represents the value of a key in a JSON object held by JSONMapSlice
 | ||||||
| type JSONMapItem struct { | type JSONMapItem struct { | ||||||
| 	Key   string | 	Key   string | ||||||
|  | @ -173,23 +390,10 @@ func transformData(input interface{}) (out interface{}, err error) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	switch in := input.(type) { | 	switch in := input.(type) { | ||||||
| 	case yaml.MapSlice: | 	case yaml.Node: | ||||||
| 
 | 		return yamlNode(&in) | ||||||
| 		o := make(JSONMapSlice, len(in)) | 	case *yaml.Node: | ||||||
| 		for i, mi := range in { | 		return yamlNode(in) | ||||||
| 			var nmi JSONMapItem |  | ||||||
| 			if nmi.Key, err = format(mi.Key); err != nil { |  | ||||||
| 				return nil, err |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			v, ert := transformData(mi.Value) |  | ||||||
| 			if ert != nil { |  | ||||||
| 				return nil, ert |  | ||||||
| 			} |  | ||||||
| 			nmi.Value = v |  | ||||||
| 			o[i] = nmi |  | ||||||
| 		} |  | ||||||
| 		return o, nil |  | ||||||
| 	case map[interface{}]interface{}: | 	case map[interface{}]interface{}: | ||||||
| 		o := make(JSONMapSlice, 0, len(in)) | 		o := make(JSONMapSlice, 0, len(in)) | ||||||
| 		for ke, va := range in { | 		for ke, va := range in { | ||||||
|  |  | ||||||
|  | @ -24,7 +24,7 @@ import ( | ||||||
| 	"github.com/golang/protobuf/ptypes/any" | 	"github.com/golang/protobuf/ptypes/any" | ||||||
| 	yaml "gopkg.in/yaml.v3" | 	yaml "gopkg.in/yaml.v3" | ||||||
| 
 | 
 | ||||||
| 	extensions "github.com/google/gnostic/extensions" | 	extensions "github.com/google/gnostic-models/extensions" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // ExtensionHandler describes a binary that is called by the compiler to handle specification extensions.
 | // ExtensionHandler describes a binary that is called by the compiler to handle specification extensions.
 | ||||||
|  | @ -22,7 +22,7 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"gopkg.in/yaml.v3" | 	"gopkg.in/yaml.v3" | ||||||
| 
 | 
 | ||||||
| 	"github.com/google/gnostic/jsonschema" | 	"github.com/google/gnostic-models/jsonschema" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // compiler helper functions, usually called from generated code
 | // compiler helper functions, usually called from generated code
 | ||||||
|  | @ -14,8 +14,8 @@ | ||||||
| 
 | 
 | ||||||
| // Code generated by protoc-gen-go. DO NOT EDIT.
 | // Code generated by protoc-gen-go. DO NOT EDIT.
 | ||||||
| // versions:
 | // versions:
 | ||||||
| // 	protoc-gen-go v1.26.0
 | // 	protoc-gen-go v1.27.1
 | ||||||
| // 	protoc        v3.18.1
 | // 	protoc        v3.19.3
 | ||||||
| // source: extensions/extension.proto
 | // source: extensions/extension.proto
 | ||||||
| 
 | 
 | ||||||
| package gnostic_extension_v1 | package gnostic_extension_v1 | ||||||
|  | @ -1,3 +1,16 @@ | ||||||
|  | // Copyright 2017 Google LLC. 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.
 | ||||||
| 
 | 
 | ||||||
| // THIS FILE IS AUTOMATICALLY GENERATED.
 | // THIS FILE IS AUTOMATICALLY GENERATED.
 | ||||||
| 
 | 
 | ||||||
|  | @ -23,7 +23,7 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"gopkg.in/yaml.v3" | 	"gopkg.in/yaml.v3" | ||||||
| 
 | 
 | ||||||
| 	"github.com/google/gnostic/compiler" | 	"github.com/google/gnostic-models/compiler" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Version returns the package name (and OpenAPI version).
 | // Version returns the package name (and OpenAPI version).
 | ||||||
|  | @ -16,8 +16,8 @@ | ||||||
| 
 | 
 | ||||||
| // Code generated by protoc-gen-go. DO NOT EDIT.
 | // Code generated by protoc-gen-go. DO NOT EDIT.
 | ||||||
| // versions:
 | // versions:
 | ||||||
| // 	protoc-gen-go v1.26.0
 | // 	protoc-gen-go v1.27.1
 | ||||||
| // 	protoc        v3.18.1
 | // 	protoc        v3.19.3
 | ||||||
| // source: openapiv2/OpenAPIv2.proto
 | // source: openapiv2/OpenAPIv2.proto
 | ||||||
| 
 | 
 | ||||||
| package openapi_v2 | package openapi_v2 | ||||||
|  | @ -17,7 +17,7 @@ package openapi_v2 | ||||||
| import ( | import ( | ||||||
| 	"gopkg.in/yaml.v3" | 	"gopkg.in/yaml.v3" | ||||||
| 
 | 
 | ||||||
| 	"github.com/google/gnostic/compiler" | 	"github.com/google/gnostic-models/compiler" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // ParseDocument reads an OpenAPI v2 description from a YAML/JSON representation.
 | // ParseDocument reads an OpenAPI v2 description from a YAML/JSON representation.
 | ||||||
|  | @ -23,7 +23,7 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"gopkg.in/yaml.v3" | 	"gopkg.in/yaml.v3" | ||||||
| 
 | 
 | ||||||
| 	"github.com/google/gnostic/compiler" | 	"github.com/google/gnostic-models/compiler" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Version returns the package name (and OpenAPI version).
 | // Version returns the package name (and OpenAPI version).
 | ||||||
|  | @ -16,8 +16,8 @@ | ||||||
| 
 | 
 | ||||||
| // Code generated by protoc-gen-go. DO NOT EDIT.
 | // Code generated by protoc-gen-go. DO NOT EDIT.
 | ||||||
| // versions:
 | // versions:
 | ||||||
| // 	protoc-gen-go v1.26.0
 | // 	protoc-gen-go v1.27.1
 | ||||||
| // 	protoc        v3.18.1
 | // 	protoc        v3.19.3
 | ||||||
| // source: openapiv3/OpenAPIv3.proto
 | // source: openapiv3/OpenAPIv3.proto
 | ||||||
| 
 | 
 | ||||||
| package openapi_v3 | package openapi_v3 | ||||||
|  | @ -17,7 +17,7 @@ package openapi_v3 | ||||||
| import ( | import ( | ||||||
| 	"gopkg.in/yaml.v3" | 	"gopkg.in/yaml.v3" | ||||||
| 
 | 
 | ||||||
| 	"github.com/google/gnostic/compiler" | 	"github.com/google/gnostic-models/compiler" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // ParseDocument reads an OpenAPI v3 description from a YAML/JSON representation.
 | // ParseDocument reads an OpenAPI v3 description from a YAML/JSON representation.
 | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,25 @@ | ||||||
|  | # Compiled Object files, Static and Dynamic libs (Shared Objects) | ||||||
|  | *.o | ||||||
|  | *.a | ||||||
|  | *.so | ||||||
|  | 
 | ||||||
|  | # Folders | ||||||
|  | _obj | ||||||
|  | _test | ||||||
|  | 
 | ||||||
|  | # Architecture specific extensions/prefixes | ||||||
|  | *.[568vq] | ||||||
|  | [568vq].out | ||||||
|  | 
 | ||||||
|  | *.cgo1.go | ||||||
|  | *.cgo2.c | ||||||
|  | _cgo_defun.c | ||||||
|  | _cgo_gotypes.go | ||||||
|  | _cgo_export.* | ||||||
|  | 
 | ||||||
|  | _testmain.go | ||||||
|  | 
 | ||||||
|  | *.exe | ||||||
|  | 
 | ||||||
|  | .idea/ | ||||||
|  | *.iml | ||||||
|  | @ -0,0 +1,9 @@ | ||||||
|  | # This is the official list of Gorilla WebSocket authors for copyright | ||||||
|  | # purposes. | ||||||
|  | # | ||||||
|  | # Please keep the list sorted. | ||||||
|  | 
 | ||||||
|  | Gary Burd <gary@beagledreams.com> | ||||||
|  | Google LLC (https://opensource.google.com/) | ||||||
|  | Joachim Bauch <mail@joachim-bauch.de> | ||||||
|  | 
 | ||||||
|  | @ -0,0 +1,22 @@ | ||||||
|  | Copyright (c) 2013 The Gorilla WebSocket 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. | ||||||
|  | 
 | ||||||
|  | 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 HOLDER 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. | ||||||
|  | @ -0,0 +1,39 @@ | ||||||
|  | # Gorilla WebSocket | ||||||
|  | 
 | ||||||
|  | [](https://godoc.org/github.com/gorilla/websocket) | ||||||
|  | [](https://circleci.com/gh/gorilla/websocket) | ||||||
|  | 
 | ||||||
|  | Gorilla WebSocket is a [Go](http://golang.org/) implementation of the | ||||||
|  | [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | --- | ||||||
|  | 
 | ||||||
|  | ⚠️ **[The Gorilla WebSocket Package is looking for a new maintainer](https://github.com/gorilla/websocket/issues/370)** | ||||||
|  | 
 | ||||||
|  | --- | ||||||
|  | 
 | ||||||
|  | ### Documentation | ||||||
|  | 
 | ||||||
|  | * [API Reference](https://pkg.go.dev/github.com/gorilla/websocket?tab=doc) | ||||||
|  | * [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat) | ||||||
|  | * [Command example](https://github.com/gorilla/websocket/tree/master/examples/command) | ||||||
|  | * [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo) | ||||||
|  | * [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch) | ||||||
|  | 
 | ||||||
|  | ### Status | ||||||
|  | 
 | ||||||
|  | The Gorilla WebSocket package provides a complete and tested implementation of | ||||||
|  | the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. The | ||||||
|  | package API is stable. | ||||||
|  | 
 | ||||||
|  | ### Installation | ||||||
|  | 
 | ||||||
|  |     go get github.com/gorilla/websocket | ||||||
|  | 
 | ||||||
|  | ### Protocol Compliance | ||||||
|  | 
 | ||||||
|  | The Gorilla WebSocket package passes the server tests in the [Autobahn Test | ||||||
|  | Suite](https://github.com/crossbario/autobahn-testsuite) using the application in the [examples/autobahn | ||||||
|  | subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn). | ||||||
|  | 
 | ||||||
|  | @ -0,0 +1,422 @@ | ||||||
|  | // Copyright 2013 The Gorilla WebSocket 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 websocket | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"context" | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"errors" | ||||||
|  | 	"io" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"net" | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/http/httptrace" | ||||||
|  | 	"net/url" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // ErrBadHandshake is returned when the server response to opening handshake is
 | ||||||
|  | // invalid.
 | ||||||
|  | var ErrBadHandshake = errors.New("websocket: bad handshake") | ||||||
|  | 
 | ||||||
|  | var errInvalidCompression = errors.New("websocket: invalid compression negotiation") | ||||||
|  | 
 | ||||||
|  | // NewClient creates a new client connection using the given net connection.
 | ||||||
|  | // The URL u specifies the host and request URI. Use requestHeader to specify
 | ||||||
|  | // the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies
 | ||||||
|  | // (Cookie). Use the response.Header to get the selected subprotocol
 | ||||||
|  | // (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
 | ||||||
|  | //
 | ||||||
|  | // If the WebSocket handshake fails, ErrBadHandshake is returned along with a
 | ||||||
|  | // non-nil *http.Response so that callers can handle redirects, authentication,
 | ||||||
|  | // etc.
 | ||||||
|  | //
 | ||||||
|  | // Deprecated: Use Dialer instead.
 | ||||||
|  | func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) { | ||||||
|  | 	d := Dialer{ | ||||||
|  | 		ReadBufferSize:  readBufSize, | ||||||
|  | 		WriteBufferSize: writeBufSize, | ||||||
|  | 		NetDial: func(net, addr string) (net.Conn, error) { | ||||||
|  | 			return netConn, nil | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	return d.Dial(u.String(), requestHeader) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // A Dialer contains options for connecting to WebSocket server.
 | ||||||
|  | //
 | ||||||
|  | // It is safe to call Dialer's methods concurrently.
 | ||||||
|  | type Dialer struct { | ||||||
|  | 	// NetDial specifies the dial function for creating TCP connections. If
 | ||||||
|  | 	// NetDial is nil, net.Dial is used.
 | ||||||
|  | 	NetDial func(network, addr string) (net.Conn, error) | ||||||
|  | 
 | ||||||
|  | 	// NetDialContext specifies the dial function for creating TCP connections. If
 | ||||||
|  | 	// NetDialContext is nil, NetDial is used.
 | ||||||
|  | 	NetDialContext func(ctx context.Context, network, addr string) (net.Conn, error) | ||||||
|  | 
 | ||||||
|  | 	// NetDialTLSContext specifies the dial function for creating TLS/TCP connections. If
 | ||||||
|  | 	// NetDialTLSContext is nil, NetDialContext is used.
 | ||||||
|  | 	// If NetDialTLSContext is set, Dial assumes the TLS handshake is done there and
 | ||||||
|  | 	// TLSClientConfig is ignored.
 | ||||||
|  | 	NetDialTLSContext func(ctx context.Context, network, addr string) (net.Conn, error) | ||||||
|  | 
 | ||||||
|  | 	// Proxy specifies a function to return a proxy for a given
 | ||||||
|  | 	// Request. If the function returns a non-nil error, the
 | ||||||
|  | 	// request is aborted with the provided error.
 | ||||||
|  | 	// If Proxy is nil or returns a nil *URL, no proxy is used.
 | ||||||
|  | 	Proxy func(*http.Request) (*url.URL, error) | ||||||
|  | 
 | ||||||
|  | 	// TLSClientConfig specifies the TLS configuration to use with tls.Client.
 | ||||||
|  | 	// If nil, the default configuration is used.
 | ||||||
|  | 	// If either NetDialTLS or NetDialTLSContext are set, Dial assumes the TLS handshake
 | ||||||
|  | 	// is done there and TLSClientConfig is ignored.
 | ||||||
|  | 	TLSClientConfig *tls.Config | ||||||
|  | 
 | ||||||
|  | 	// HandshakeTimeout specifies the duration for the handshake to complete.
 | ||||||
|  | 	HandshakeTimeout time.Duration | ||||||
|  | 
 | ||||||
|  | 	// ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer
 | ||||||
|  | 	// size is zero, then a useful default size is used. The I/O buffer sizes
 | ||||||
|  | 	// do not limit the size of the messages that can be sent or received.
 | ||||||
|  | 	ReadBufferSize, WriteBufferSize int | ||||||
|  | 
 | ||||||
|  | 	// WriteBufferPool is a pool of buffers for write operations. If the value
 | ||||||
|  | 	// is not set, then write buffers are allocated to the connection for the
 | ||||||
|  | 	// lifetime of the connection.
 | ||||||
|  | 	//
 | ||||||
|  | 	// A pool is most useful when the application has a modest volume of writes
 | ||||||
|  | 	// across a large number of connections.
 | ||||||
|  | 	//
 | ||||||
|  | 	// Applications should use a single pool for each unique value of
 | ||||||
|  | 	// WriteBufferSize.
 | ||||||
|  | 	WriteBufferPool BufferPool | ||||||
|  | 
 | ||||||
|  | 	// Subprotocols specifies the client's requested subprotocols.
 | ||||||
|  | 	Subprotocols []string | ||||||
|  | 
 | ||||||
|  | 	// EnableCompression specifies if the client should attempt to negotiate
 | ||||||
|  | 	// per message compression (RFC 7692). Setting this value to true does not
 | ||||||
|  | 	// guarantee that compression will be supported. Currently only "no context
 | ||||||
|  | 	// takeover" modes are supported.
 | ||||||
|  | 	EnableCompression bool | ||||||
|  | 
 | ||||||
|  | 	// Jar specifies the cookie jar.
 | ||||||
|  | 	// If Jar is nil, cookies are not sent in requests and ignored
 | ||||||
|  | 	// in responses.
 | ||||||
|  | 	Jar http.CookieJar | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Dial creates a new client connection by calling DialContext with a background context.
 | ||||||
|  | func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) { | ||||||
|  | 	return d.DialContext(context.Background(), urlStr, requestHeader) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var errMalformedURL = errors.New("malformed ws or wss URL") | ||||||
|  | 
 | ||||||
|  | func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) { | ||||||
|  | 	hostPort = u.Host | ||||||
|  | 	hostNoPort = u.Host | ||||||
|  | 	if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") { | ||||||
|  | 		hostNoPort = hostNoPort[:i] | ||||||
|  | 	} else { | ||||||
|  | 		switch u.Scheme { | ||||||
|  | 		case "wss": | ||||||
|  | 			hostPort += ":443" | ||||||
|  | 		case "https": | ||||||
|  | 			hostPort += ":443" | ||||||
|  | 		default: | ||||||
|  | 			hostPort += ":80" | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return hostPort, hostNoPort | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DefaultDialer is a dialer with all fields set to the default values.
 | ||||||
|  | var DefaultDialer = &Dialer{ | ||||||
|  | 	Proxy:            http.ProxyFromEnvironment, | ||||||
|  | 	HandshakeTimeout: 45 * time.Second, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // nilDialer is dialer to use when receiver is nil.
 | ||||||
|  | var nilDialer = *DefaultDialer | ||||||
|  | 
 | ||||||
|  | // DialContext creates a new client connection. Use requestHeader to specify the
 | ||||||
|  | // origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
 | ||||||
|  | // Use the response.Header to get the selected subprotocol
 | ||||||
|  | // (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
 | ||||||
|  | //
 | ||||||
|  | // The context will be used in the request and in the Dialer.
 | ||||||
|  | //
 | ||||||
|  | // If the WebSocket handshake fails, ErrBadHandshake is returned along with a
 | ||||||
|  | // non-nil *http.Response so that callers can handle redirects, authentication,
 | ||||||
|  | // etcetera. The response body may not contain the entire response and does not
 | ||||||
|  | // need to be closed by the application.
 | ||||||
|  | func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) { | ||||||
|  | 	if d == nil { | ||||||
|  | 		d = &nilDialer | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	challengeKey, err := generateChallengeKey() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	u, err := url.Parse(urlStr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch u.Scheme { | ||||||
|  | 	case "ws": | ||||||
|  | 		u.Scheme = "http" | ||||||
|  | 	case "wss": | ||||||
|  | 		u.Scheme = "https" | ||||||
|  | 	default: | ||||||
|  | 		return nil, nil, errMalformedURL | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if u.User != nil { | ||||||
|  | 		// User name and password are not allowed in websocket URIs.
 | ||||||
|  | 		return nil, nil, errMalformedURL | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	req := &http.Request{ | ||||||
|  | 		Method:     http.MethodGet, | ||||||
|  | 		URL:        u, | ||||||
|  | 		Proto:      "HTTP/1.1", | ||||||
|  | 		ProtoMajor: 1, | ||||||
|  | 		ProtoMinor: 1, | ||||||
|  | 		Header:     make(http.Header), | ||||||
|  | 		Host:       u.Host, | ||||||
|  | 	} | ||||||
|  | 	req = req.WithContext(ctx) | ||||||
|  | 
 | ||||||
|  | 	// Set the cookies present in the cookie jar of the dialer
 | ||||||
|  | 	if d.Jar != nil { | ||||||
|  | 		for _, cookie := range d.Jar.Cookies(u) { | ||||||
|  | 			req.AddCookie(cookie) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Set the request headers using the capitalization for names and values in
 | ||||||
|  | 	// RFC examples. Although the capitalization shouldn't matter, there are
 | ||||||
|  | 	// servers that depend on it. The Header.Set method is not used because the
 | ||||||
|  | 	// method canonicalizes the header names.
 | ||||||
|  | 	req.Header["Upgrade"] = []string{"websocket"} | ||||||
|  | 	req.Header["Connection"] = []string{"Upgrade"} | ||||||
|  | 	req.Header["Sec-WebSocket-Key"] = []string{challengeKey} | ||||||
|  | 	req.Header["Sec-WebSocket-Version"] = []string{"13"} | ||||||
|  | 	if len(d.Subprotocols) > 0 { | ||||||
|  | 		req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")} | ||||||
|  | 	} | ||||||
|  | 	for k, vs := range requestHeader { | ||||||
|  | 		switch { | ||||||
|  | 		case k == "Host": | ||||||
|  | 			if len(vs) > 0 { | ||||||
|  | 				req.Host = vs[0] | ||||||
|  | 			} | ||||||
|  | 		case k == "Upgrade" || | ||||||
|  | 			k == "Connection" || | ||||||
|  | 			k == "Sec-Websocket-Key" || | ||||||
|  | 			k == "Sec-Websocket-Version" || | ||||||
|  | 			k == "Sec-Websocket-Extensions" || | ||||||
|  | 			(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0): | ||||||
|  | 			return nil, nil, errors.New("websocket: duplicate header not allowed: " + k) | ||||||
|  | 		case k == "Sec-Websocket-Protocol": | ||||||
|  | 			req.Header["Sec-WebSocket-Protocol"] = vs | ||||||
|  | 		default: | ||||||
|  | 			req.Header[k] = vs | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if d.EnableCompression { | ||||||
|  | 		req.Header["Sec-WebSocket-Extensions"] = []string{"permessage-deflate; server_no_context_takeover; client_no_context_takeover"} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if d.HandshakeTimeout != 0 { | ||||||
|  | 		var cancel func() | ||||||
|  | 		ctx, cancel = context.WithTimeout(ctx, d.HandshakeTimeout) | ||||||
|  | 		defer cancel() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Get network dial function.
 | ||||||
|  | 	var netDial func(network, add string) (net.Conn, error) | ||||||
|  | 
 | ||||||
|  | 	switch u.Scheme { | ||||||
|  | 	case "http": | ||||||
|  | 		if d.NetDialContext != nil { | ||||||
|  | 			netDial = func(network, addr string) (net.Conn, error) { | ||||||
|  | 				return d.NetDialContext(ctx, network, addr) | ||||||
|  | 			} | ||||||
|  | 		} else if d.NetDial != nil { | ||||||
|  | 			netDial = d.NetDial | ||||||
|  | 		} | ||||||
|  | 	case "https": | ||||||
|  | 		if d.NetDialTLSContext != nil { | ||||||
|  | 			netDial = func(network, addr string) (net.Conn, error) { | ||||||
|  | 				return d.NetDialTLSContext(ctx, network, addr) | ||||||
|  | 			} | ||||||
|  | 		} else if d.NetDialContext != nil { | ||||||
|  | 			netDial = func(network, addr string) (net.Conn, error) { | ||||||
|  | 				return d.NetDialContext(ctx, network, addr) | ||||||
|  | 			} | ||||||
|  | 		} else if d.NetDial != nil { | ||||||
|  | 			netDial = d.NetDial | ||||||
|  | 		} | ||||||
|  | 	default: | ||||||
|  | 		return nil, nil, errMalformedURL | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if netDial == nil { | ||||||
|  | 		netDialer := &net.Dialer{} | ||||||
|  | 		netDial = func(network, addr string) (net.Conn, error) { | ||||||
|  | 			return netDialer.DialContext(ctx, network, addr) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// If needed, wrap the dial function to set the connection deadline.
 | ||||||
|  | 	if deadline, ok := ctx.Deadline(); ok { | ||||||
|  | 		forwardDial := netDial | ||||||
|  | 		netDial = func(network, addr string) (net.Conn, error) { | ||||||
|  | 			c, err := forwardDial(network, addr) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			err = c.SetDeadline(deadline) | ||||||
|  | 			if err != nil { | ||||||
|  | 				c.Close() | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			return c, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// If needed, wrap the dial function to connect through a proxy.
 | ||||||
|  | 	if d.Proxy != nil { | ||||||
|  | 		proxyURL, err := d.Proxy(req) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, nil, err | ||||||
|  | 		} | ||||||
|  | 		if proxyURL != nil { | ||||||
|  | 			dialer, err := proxy_FromURL(proxyURL, netDialerFunc(netDial)) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, nil, err | ||||||
|  | 			} | ||||||
|  | 			netDial = dialer.Dial | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	hostPort, hostNoPort := hostPortNoPort(u) | ||||||
|  | 	trace := httptrace.ContextClientTrace(ctx) | ||||||
|  | 	if trace != nil && trace.GetConn != nil { | ||||||
|  | 		trace.GetConn(hostPort) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	netConn, err := netDial("tcp", hostPort) | ||||||
|  | 	if trace != nil && trace.GotConn != nil { | ||||||
|  | 		trace.GotConn(httptrace.GotConnInfo{ | ||||||
|  | 			Conn: netConn, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	defer func() { | ||||||
|  | 		if netConn != nil { | ||||||
|  | 			netConn.Close() | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 
 | ||||||
|  | 	if u.Scheme == "https" && d.NetDialTLSContext == nil { | ||||||
|  | 		// If NetDialTLSContext is set, assume that the TLS handshake has already been done
 | ||||||
|  | 
 | ||||||
|  | 		cfg := cloneTLSConfig(d.TLSClientConfig) | ||||||
|  | 		if cfg.ServerName == "" { | ||||||
|  | 			cfg.ServerName = hostNoPort | ||||||
|  | 		} | ||||||
|  | 		tlsConn := tls.Client(netConn, cfg) | ||||||
|  | 		netConn = tlsConn | ||||||
|  | 
 | ||||||
|  | 		if trace != nil && trace.TLSHandshakeStart != nil { | ||||||
|  | 			trace.TLSHandshakeStart() | ||||||
|  | 		} | ||||||
|  | 		err := doHandshake(ctx, tlsConn, cfg) | ||||||
|  | 		if trace != nil && trace.TLSHandshakeDone != nil { | ||||||
|  | 			trace.TLSHandshakeDone(tlsConn.ConnectionState(), err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize, d.WriteBufferPool, nil, nil) | ||||||
|  | 
 | ||||||
|  | 	if err := req.Write(netConn); err != nil { | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if trace != nil && trace.GotFirstResponseByte != nil { | ||||||
|  | 		if peek, err := conn.br.Peek(1); err == nil && len(peek) == 1 { | ||||||
|  | 			trace.GotFirstResponseByte() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	resp, err := http.ReadResponse(conn.br, req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if d.Jar != nil { | ||||||
|  | 		if rc := resp.Cookies(); len(rc) > 0 { | ||||||
|  | 			d.Jar.SetCookies(u, rc) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if resp.StatusCode != 101 || | ||||||
|  | 		!tokenListContainsValue(resp.Header, "Upgrade", "websocket") || | ||||||
|  | 		!tokenListContainsValue(resp.Header, "Connection", "upgrade") || | ||||||
|  | 		resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) { | ||||||
|  | 		// Before closing the network connection on return from this
 | ||||||
|  | 		// function, slurp up some of the response to aid application
 | ||||||
|  | 		// debugging.
 | ||||||
|  | 		buf := make([]byte, 1024) | ||||||
|  | 		n, _ := io.ReadFull(resp.Body, buf) | ||||||
|  | 		resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n])) | ||||||
|  | 		return nil, resp, ErrBadHandshake | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, ext := range parseExtensions(resp.Header) { | ||||||
|  | 		if ext[""] != "permessage-deflate" { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		_, snct := ext["server_no_context_takeover"] | ||||||
|  | 		_, cnct := ext["client_no_context_takeover"] | ||||||
|  | 		if !snct || !cnct { | ||||||
|  | 			return nil, resp, errInvalidCompression | ||||||
|  | 		} | ||||||
|  | 		conn.newCompressionWriter = compressNoContextTakeover | ||||||
|  | 		conn.newDecompressionReader = decompressNoContextTakeover | ||||||
|  | 		break | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) | ||||||
|  | 	conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol") | ||||||
|  | 
 | ||||||
|  | 	netConn.SetDeadline(time.Time{}) | ||||||
|  | 	netConn = nil // to avoid close in defer.
 | ||||||
|  | 	return conn, resp, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func cloneTLSConfig(cfg *tls.Config) *tls.Config { | ||||||
|  | 	if cfg == nil { | ||||||
|  | 		return &tls.Config{} | ||||||
|  | 	} | ||||||
|  | 	return cfg.Clone() | ||||||
|  | } | ||||||
|  | @ -0,0 +1,148 @@ | ||||||
|  | // Copyright 2017 The Gorilla WebSocket 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 websocket | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"compress/flate" | ||||||
|  | 	"errors" | ||||||
|  | 	"io" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	minCompressionLevel     = -2 // flate.HuffmanOnly not defined in Go < 1.6
 | ||||||
|  | 	maxCompressionLevel     = flate.BestCompression | ||||||
|  | 	defaultCompressionLevel = 1 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	flateWriterPools [maxCompressionLevel - minCompressionLevel + 1]sync.Pool | ||||||
|  | 	flateReaderPool  = sync.Pool{New: func() interface{} { | ||||||
|  | 		return flate.NewReader(nil) | ||||||
|  | 	}} | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func decompressNoContextTakeover(r io.Reader) io.ReadCloser { | ||||||
|  | 	const tail = | ||||||
|  | 	// Add four bytes as specified in RFC
 | ||||||
|  | 	"\x00\x00\xff\xff" + | ||||||
|  | 		// Add final block to squelch unexpected EOF error from flate reader.
 | ||||||
|  | 		"\x01\x00\x00\xff\xff" | ||||||
|  | 
 | ||||||
|  | 	fr, _ := flateReaderPool.Get().(io.ReadCloser) | ||||||
|  | 	fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil) | ||||||
|  | 	return &flateReadWrapper{fr} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isValidCompressionLevel(level int) bool { | ||||||
|  | 	return minCompressionLevel <= level && level <= maxCompressionLevel | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser { | ||||||
|  | 	p := &flateWriterPools[level-minCompressionLevel] | ||||||
|  | 	tw := &truncWriter{w: w} | ||||||
|  | 	fw, _ := p.Get().(*flate.Writer) | ||||||
|  | 	if fw == nil { | ||||||
|  | 		fw, _ = flate.NewWriter(tw, level) | ||||||
|  | 	} else { | ||||||
|  | 		fw.Reset(tw) | ||||||
|  | 	} | ||||||
|  | 	return &flateWriteWrapper{fw: fw, tw: tw, p: p} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // truncWriter is an io.Writer that writes all but the last four bytes of the
 | ||||||
|  | // stream to another io.Writer.
 | ||||||
|  | type truncWriter struct { | ||||||
|  | 	w io.WriteCloser | ||||||
|  | 	n int | ||||||
|  | 	p [4]byte | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *truncWriter) Write(p []byte) (int, error) { | ||||||
|  | 	n := 0 | ||||||
|  | 
 | ||||||
|  | 	// fill buffer first for simplicity.
 | ||||||
|  | 	if w.n < len(w.p) { | ||||||
|  | 		n = copy(w.p[w.n:], p) | ||||||
|  | 		p = p[n:] | ||||||
|  | 		w.n += n | ||||||
|  | 		if len(p) == 0 { | ||||||
|  | 			return n, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	m := len(p) | ||||||
|  | 	if m > len(w.p) { | ||||||
|  | 		m = len(w.p) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if nn, err := w.w.Write(w.p[:m]); err != nil { | ||||||
|  | 		return n + nn, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	copy(w.p[:], w.p[m:]) | ||||||
|  | 	copy(w.p[len(w.p)-m:], p[len(p)-m:]) | ||||||
|  | 	nn, err := w.w.Write(p[:len(p)-m]) | ||||||
|  | 	return n + nn, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type flateWriteWrapper struct { | ||||||
|  | 	fw *flate.Writer | ||||||
|  | 	tw *truncWriter | ||||||
|  | 	p  *sync.Pool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *flateWriteWrapper) Write(p []byte) (int, error) { | ||||||
|  | 	if w.fw == nil { | ||||||
|  | 		return 0, errWriteClosed | ||||||
|  | 	} | ||||||
|  | 	return w.fw.Write(p) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *flateWriteWrapper) Close() error { | ||||||
|  | 	if w.fw == nil { | ||||||
|  | 		return errWriteClosed | ||||||
|  | 	} | ||||||
|  | 	err1 := w.fw.Flush() | ||||||
|  | 	w.p.Put(w.fw) | ||||||
|  | 	w.fw = nil | ||||||
|  | 	if w.tw.p != [4]byte{0, 0, 0xff, 0xff} { | ||||||
|  | 		return errors.New("websocket: internal error, unexpected bytes at end of flate stream") | ||||||
|  | 	} | ||||||
|  | 	err2 := w.tw.w.Close() | ||||||
|  | 	if err1 != nil { | ||||||
|  | 		return err1 | ||||||
|  | 	} | ||||||
|  | 	return err2 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type flateReadWrapper struct { | ||||||
|  | 	fr io.ReadCloser | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *flateReadWrapper) Read(p []byte) (int, error) { | ||||||
|  | 	if r.fr == nil { | ||||||
|  | 		return 0, io.ErrClosedPipe | ||||||
|  | 	} | ||||||
|  | 	n, err := r.fr.Read(p) | ||||||
|  | 	if err == io.EOF { | ||||||
|  | 		// Preemptively place the reader back in the pool. This helps with
 | ||||||
|  | 		// scenarios where the application does not call NextReader() soon after
 | ||||||
|  | 		// this final read.
 | ||||||
|  | 		r.Close() | ||||||
|  | 	} | ||||||
|  | 	return n, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *flateReadWrapper) Close() error { | ||||||
|  | 	if r.fr == nil { | ||||||
|  | 		return io.ErrClosedPipe | ||||||
|  | 	} | ||||||
|  | 	err := r.fr.Close() | ||||||
|  | 	flateReaderPool.Put(r.fr) | ||||||
|  | 	r.fr = nil | ||||||
|  | 	return err | ||||||
|  | } | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,227 @@ | ||||||
|  | // Copyright 2013 The Gorilla WebSocket 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 websocket implements the WebSocket protocol defined in RFC 6455.
 | ||||||
|  | //
 | ||||||
|  | // Overview
 | ||||||
|  | //
 | ||||||
|  | // The Conn type represents a WebSocket connection. A server application calls
 | ||||||
|  | // the Upgrader.Upgrade method from an HTTP request handler to get a *Conn:
 | ||||||
|  | //
 | ||||||
|  | //  var upgrader = websocket.Upgrader{
 | ||||||
|  | //      ReadBufferSize:  1024,
 | ||||||
|  | //      WriteBufferSize: 1024,
 | ||||||
|  | //  }
 | ||||||
|  | //
 | ||||||
|  | //  func handler(w http.ResponseWriter, r *http.Request) {
 | ||||||
|  | //      conn, err := upgrader.Upgrade(w, r, nil)
 | ||||||
|  | //      if err != nil {
 | ||||||
|  | //          log.Println(err)
 | ||||||
|  | //          return
 | ||||||
|  | //      }
 | ||||||
|  | //      ... Use conn to send and receive messages.
 | ||||||
|  | //  }
 | ||||||
|  | //
 | ||||||
|  | // Call the connection's WriteMessage and ReadMessage methods to send and
 | ||||||
|  | // receive messages as a slice of bytes. This snippet of code shows how to echo
 | ||||||
|  | // messages using these methods:
 | ||||||
|  | //
 | ||||||
|  | //  for {
 | ||||||
|  | //      messageType, p, err := conn.ReadMessage()
 | ||||||
|  | //      if err != nil {
 | ||||||
|  | //          log.Println(err)
 | ||||||
|  | //          return
 | ||||||
|  | //      }
 | ||||||
|  | //      if err := conn.WriteMessage(messageType, p); err != nil {
 | ||||||
|  | //          log.Println(err)
 | ||||||
|  | //          return
 | ||||||
|  | //      }
 | ||||||
|  | //  }
 | ||||||
|  | //
 | ||||||
|  | // In above snippet of code, p is a []byte and messageType is an int with value
 | ||||||
|  | // websocket.BinaryMessage or websocket.TextMessage.
 | ||||||
|  | //
 | ||||||
|  | // An application can also send and receive messages using the io.WriteCloser
 | ||||||
|  | // and io.Reader interfaces. To send a message, call the connection NextWriter
 | ||||||
|  | // method to get an io.WriteCloser, write the message to the writer and close
 | ||||||
|  | // the writer when done. To receive a message, call the connection NextReader
 | ||||||
|  | // method to get an io.Reader and read until io.EOF is returned. This snippet
 | ||||||
|  | // shows how to echo messages using the NextWriter and NextReader methods:
 | ||||||
|  | //
 | ||||||
|  | //  for {
 | ||||||
|  | //      messageType, r, err := conn.NextReader()
 | ||||||
|  | //      if err != nil {
 | ||||||
|  | //          return
 | ||||||
|  | //      }
 | ||||||
|  | //      w, err := conn.NextWriter(messageType)
 | ||||||
|  | //      if err != nil {
 | ||||||
|  | //          return err
 | ||||||
|  | //      }
 | ||||||
|  | //      if _, err := io.Copy(w, r); err != nil {
 | ||||||
|  | //          return err
 | ||||||
|  | //      }
 | ||||||
|  | //      if err := w.Close(); err != nil {
 | ||||||
|  | //          return err
 | ||||||
|  | //      }
 | ||||||
|  | //  }
 | ||||||
|  | //
 | ||||||
|  | // Data Messages
 | ||||||
|  | //
 | ||||||
|  | // The WebSocket protocol distinguishes between text and binary data messages.
 | ||||||
|  | // Text messages are interpreted as UTF-8 encoded text. The interpretation of
 | ||||||
|  | // binary messages is left to the application.
 | ||||||
|  | //
 | ||||||
|  | // This package uses the TextMessage and BinaryMessage integer constants to
 | ||||||
|  | // identify the two data message types. The ReadMessage and NextReader methods
 | ||||||
|  | // return the type of the received message. The messageType argument to the
 | ||||||
|  | // WriteMessage and NextWriter methods specifies the type of a sent message.
 | ||||||
|  | //
 | ||||||
|  | // It is the application's responsibility to ensure that text messages are
 | ||||||
|  | // valid UTF-8 encoded text.
 | ||||||
|  | //
 | ||||||
|  | // Control Messages
 | ||||||
|  | //
 | ||||||
|  | // The WebSocket protocol defines three types of control messages: close, ping
 | ||||||
|  | // and pong. Call the connection WriteControl, WriteMessage or NextWriter
 | ||||||
|  | // methods to send a control message to the peer.
 | ||||||
|  | //
 | ||||||
|  | // Connections handle received close messages by calling the handler function
 | ||||||
|  | // set with the SetCloseHandler method and by returning a *CloseError from the
 | ||||||
|  | // NextReader, ReadMessage or the message Read method. The default close
 | ||||||
|  | // handler sends a close message to the peer.
 | ||||||
|  | //
 | ||||||
|  | // Connections handle received ping messages by calling the handler function
 | ||||||
|  | // set with the SetPingHandler method. The default ping handler sends a pong
 | ||||||
|  | // message to the peer.
 | ||||||
|  | //
 | ||||||
|  | // Connections handle received pong messages by calling the handler function
 | ||||||
|  | // set with the SetPongHandler method. The default pong handler does nothing.
 | ||||||
|  | // If an application sends ping messages, then the application should set a
 | ||||||
|  | // pong handler to receive the corresponding pong.
 | ||||||
|  | //
 | ||||||
|  | // The control message handler functions are called from the NextReader,
 | ||||||
|  | // ReadMessage and message reader Read methods. The default close and ping
 | ||||||
|  | // handlers can block these methods for a short time when the handler writes to
 | ||||||
|  | // the connection.
 | ||||||
|  | //
 | ||||||
|  | // The application must read the connection to process close, ping and pong
 | ||||||
|  | // messages sent from the peer. If the application is not otherwise interested
 | ||||||
|  | // in messages from the peer, then the application should start a goroutine to
 | ||||||
|  | // read and discard messages from the peer. A simple example is:
 | ||||||
|  | //
 | ||||||
|  | //  func readLoop(c *websocket.Conn) {
 | ||||||
|  | //      for {
 | ||||||
|  | //          if _, _, err := c.NextReader(); err != nil {
 | ||||||
|  | //              c.Close()
 | ||||||
|  | //              break
 | ||||||
|  | //          }
 | ||||||
|  | //      }
 | ||||||
|  | //  }
 | ||||||
|  | //
 | ||||||
|  | // Concurrency
 | ||||||
|  | //
 | ||||||
|  | // Connections support one concurrent reader and one concurrent writer.
 | ||||||
|  | //
 | ||||||
|  | // Applications are responsible for ensuring that no more than one goroutine
 | ||||||
|  | // calls the write methods (NextWriter, SetWriteDeadline, WriteMessage,
 | ||||||
|  | // WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently and
 | ||||||
|  | // that no more than one goroutine calls the read methods (NextReader,
 | ||||||
|  | // SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler)
 | ||||||
|  | // concurrently.
 | ||||||
|  | //
 | ||||||
|  | // The Close and WriteControl methods can be called concurrently with all other
 | ||||||
|  | // methods.
 | ||||||
|  | //
 | ||||||
|  | // Origin Considerations
 | ||||||
|  | //
 | ||||||
|  | // Web browsers allow Javascript applications to open a WebSocket connection to
 | ||||||
|  | // any host. It's up to the server to enforce an origin policy using the Origin
 | ||||||
|  | // request header sent by the browser.
 | ||||||
|  | //
 | ||||||
|  | // The Upgrader calls the function specified in the CheckOrigin field to check
 | ||||||
|  | // the origin. If the CheckOrigin function returns false, then the Upgrade
 | ||||||
|  | // method fails the WebSocket handshake with HTTP status 403.
 | ||||||
|  | //
 | ||||||
|  | // If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail
 | ||||||
|  | // the handshake if the Origin request header is present and the Origin host is
 | ||||||
|  | // not equal to the Host request header.
 | ||||||
|  | //
 | ||||||
|  | // The deprecated package-level Upgrade function does not perform origin
 | ||||||
|  | // checking. The application is responsible for checking the Origin header
 | ||||||
|  | // before calling the Upgrade function.
 | ||||||
|  | //
 | ||||||
|  | // Buffers
 | ||||||
|  | //
 | ||||||
|  | // Connections buffer network input and output to reduce the number
 | ||||||
|  | // of system calls when reading or writing messages.
 | ||||||
|  | //
 | ||||||
|  | // Write buffers are also used for constructing WebSocket frames. See RFC 6455,
 | ||||||
|  | // Section 5 for a discussion of message framing. A WebSocket frame header is
 | ||||||
|  | // written to the network each time a write buffer is flushed to the network.
 | ||||||
|  | // Decreasing the size of the write buffer can increase the amount of framing
 | ||||||
|  | // overhead on the connection.
 | ||||||
|  | //
 | ||||||
|  | // The buffer sizes in bytes are specified by the ReadBufferSize and
 | ||||||
|  | // WriteBufferSize fields in the Dialer and Upgrader. The Dialer uses a default
 | ||||||
|  | // size of 4096 when a buffer size field is set to zero. The Upgrader reuses
 | ||||||
|  | // buffers created by the HTTP server when a buffer size field is set to zero.
 | ||||||
|  | // The HTTP server buffers have a size of 4096 at the time of this writing.
 | ||||||
|  | //
 | ||||||
|  | // The buffer sizes do not limit the size of a message that can be read or
 | ||||||
|  | // written by a connection.
 | ||||||
|  | //
 | ||||||
|  | // Buffers are held for the lifetime of the connection by default. If the
 | ||||||
|  | // Dialer or Upgrader WriteBufferPool field is set, then a connection holds the
 | ||||||
|  | // write buffer only when writing a message.
 | ||||||
|  | //
 | ||||||
|  | // Applications should tune the buffer sizes to balance memory use and
 | ||||||
|  | // performance. Increasing the buffer size uses more memory, but can reduce the
 | ||||||
|  | // number of system calls to read or write the network. In the case of writing,
 | ||||||
|  | // increasing the buffer size can reduce the number of frame headers written to
 | ||||||
|  | // the network.
 | ||||||
|  | //
 | ||||||
|  | // Some guidelines for setting buffer parameters are:
 | ||||||
|  | //
 | ||||||
|  | // Limit the buffer sizes to the maximum expected message size. Buffers larger
 | ||||||
|  | // than the largest message do not provide any benefit.
 | ||||||
|  | //
 | ||||||
|  | // Depending on the distribution of message sizes, setting the buffer size to
 | ||||||
|  | // a value less than the maximum expected message size can greatly reduce memory
 | ||||||
|  | // use with a small impact on performance. Here's an example: If 99% of the
 | ||||||
|  | // messages are smaller than 256 bytes and the maximum message size is 512
 | ||||||
|  | // bytes, then a buffer size of 256 bytes will result in 1.01 more system calls
 | ||||||
|  | // than a buffer size of 512 bytes. The memory savings is 50%.
 | ||||||
|  | //
 | ||||||
|  | // A write buffer pool is useful when the application has a modest number
 | ||||||
|  | // writes over a large number of connections. when buffers are pooled, a larger
 | ||||||
|  | // buffer size has a reduced impact on total memory use and has the benefit of
 | ||||||
|  | // reducing system calls and frame overhead.
 | ||||||
|  | //
 | ||||||
|  | // Compression EXPERIMENTAL
 | ||||||
|  | //
 | ||||||
|  | // Per message compression extensions (RFC 7692) are experimentally supported
 | ||||||
|  | // by this package in a limited capacity. Setting the EnableCompression option
 | ||||||
|  | // to true in Dialer or Upgrader will attempt to negotiate per message deflate
 | ||||||
|  | // support.
 | ||||||
|  | //
 | ||||||
|  | //  var upgrader = websocket.Upgrader{
 | ||||||
|  | //      EnableCompression: true,
 | ||||||
|  | //  }
 | ||||||
|  | //
 | ||||||
|  | // If compression was successfully negotiated with the connection's peer, any
 | ||||||
|  | // message received in compressed form will be automatically decompressed.
 | ||||||
|  | // All Read methods will return uncompressed bytes.
 | ||||||
|  | //
 | ||||||
|  | // Per message compression of messages written to a connection can be enabled
 | ||||||
|  | // or disabled by calling the corresponding Conn method:
 | ||||||
|  | //
 | ||||||
|  | //  conn.EnableWriteCompression(false)
 | ||||||
|  | //
 | ||||||
|  | // Currently this package does not support compression with "context takeover".
 | ||||||
|  | // This means that messages must be compressed and decompressed in isolation,
 | ||||||
|  | // without retaining sliding window or dictionary state across messages. For
 | ||||||
|  | // more details refer to RFC 7692.
 | ||||||
|  | //
 | ||||||
|  | // Use of compression is experimental and may result in decreased performance.
 | ||||||
|  | package websocket | ||||||
|  | @ -0,0 +1,42 @@ | ||||||
|  | // Copyright 2019 The Gorilla WebSocket 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 websocket | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"io" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // JoinMessages concatenates received messages to create a single io.Reader.
 | ||||||
|  | // The string term is appended to each message. The returned reader does not
 | ||||||
|  | // support concurrent calls to the Read method.
 | ||||||
|  | func JoinMessages(c *Conn, term string) io.Reader { | ||||||
|  | 	return &joinReader{c: c, term: term} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type joinReader struct { | ||||||
|  | 	c    *Conn | ||||||
|  | 	term string | ||||||
|  | 	r    io.Reader | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *joinReader) Read(p []byte) (int, error) { | ||||||
|  | 	if r.r == nil { | ||||||
|  | 		var err error | ||||||
|  | 		_, r.r, err = r.c.NextReader() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return 0, err | ||||||
|  | 		} | ||||||
|  | 		if r.term != "" { | ||||||
|  | 			r.r = io.MultiReader(r.r, strings.NewReader(r.term)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	n, err := r.r.Read(p) | ||||||
|  | 	if err == io.EOF { | ||||||
|  | 		err = nil | ||||||
|  | 		r.r = nil | ||||||
|  | 	} | ||||||
|  | 	return n, err | ||||||
|  | } | ||||||
|  | @ -0,0 +1,60 @@ | ||||||
|  | // Copyright 2013 The Gorilla WebSocket 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 websocket | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"io" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // WriteJSON writes the JSON encoding of v as a message.
 | ||||||
|  | //
 | ||||||
|  | // Deprecated: Use c.WriteJSON instead.
 | ||||||
|  | func WriteJSON(c *Conn, v interface{}) error { | ||||||
|  | 	return c.WriteJSON(v) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WriteJSON writes the JSON encoding of v as a message.
 | ||||||
|  | //
 | ||||||
|  | // See the documentation for encoding/json Marshal for details about the
 | ||||||
|  | // conversion of Go values to JSON.
 | ||||||
|  | func (c *Conn) WriteJSON(v interface{}) error { | ||||||
|  | 	w, err := c.NextWriter(TextMessage) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	err1 := json.NewEncoder(w).Encode(v) | ||||||
|  | 	err2 := w.Close() | ||||||
|  | 	if err1 != nil { | ||||||
|  | 		return err1 | ||||||
|  | 	} | ||||||
|  | 	return err2 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ReadJSON reads the next JSON-encoded message from the connection and stores
 | ||||||
|  | // it in the value pointed to by v.
 | ||||||
|  | //
 | ||||||
|  | // Deprecated: Use c.ReadJSON instead.
 | ||||||
|  | func ReadJSON(c *Conn, v interface{}) error { | ||||||
|  | 	return c.ReadJSON(v) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ReadJSON reads the next JSON-encoded message from the connection and stores
 | ||||||
|  | // it in the value pointed to by v.
 | ||||||
|  | //
 | ||||||
|  | // See the documentation for the encoding/json Unmarshal function for details
 | ||||||
|  | // about the conversion of JSON to a Go value.
 | ||||||
|  | func (c *Conn) ReadJSON(v interface{}) error { | ||||||
|  | 	_, r, err := c.NextReader() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	err = json.NewDecoder(r).Decode(v) | ||||||
|  | 	if err == io.EOF { | ||||||
|  | 		// One value is expected in the message.
 | ||||||
|  | 		err = io.ErrUnexpectedEOF | ||||||
|  | 	} | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | @ -0,0 +1,55 @@ | ||||||
|  | // Copyright 2016 The Gorilla WebSocket 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 !appengine
 | ||||||
|  | // +build !appengine
 | ||||||
|  | 
 | ||||||
|  | package websocket | ||||||
|  | 
 | ||||||
|  | import "unsafe" | ||||||
|  | 
 | ||||||
|  | const wordSize = int(unsafe.Sizeof(uintptr(0))) | ||||||
|  | 
 | ||||||
|  | func maskBytes(key [4]byte, pos int, b []byte) int { | ||||||
|  | 	// Mask one byte at a time for small buffers.
 | ||||||
|  | 	if len(b) < 2*wordSize { | ||||||
|  | 		for i := range b { | ||||||
|  | 			b[i] ^= key[pos&3] | ||||||
|  | 			pos++ | ||||||
|  | 		} | ||||||
|  | 		return pos & 3 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Mask one byte at a time to word boundary.
 | ||||||
|  | 	if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 { | ||||||
|  | 		n = wordSize - n | ||||||
|  | 		for i := range b[:n] { | ||||||
|  | 			b[i] ^= key[pos&3] | ||||||
|  | 			pos++ | ||||||
|  | 		} | ||||||
|  | 		b = b[n:] | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Create aligned word size key.
 | ||||||
|  | 	var k [wordSize]byte | ||||||
|  | 	for i := range k { | ||||||
|  | 		k[i] = key[(pos+i)&3] | ||||||
|  | 	} | ||||||
|  | 	kw := *(*uintptr)(unsafe.Pointer(&k)) | ||||||
|  | 
 | ||||||
|  | 	// Mask one word at a time.
 | ||||||
|  | 	n := (len(b) / wordSize) * wordSize | ||||||
|  | 	for i := 0; i < n; i += wordSize { | ||||||
|  | 		*(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Mask one byte at a time for remaining bytes.
 | ||||||
|  | 	b = b[n:] | ||||||
|  | 	for i := range b { | ||||||
|  | 		b[i] ^= key[pos&3] | ||||||
|  | 		pos++ | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return pos & 3 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,16 @@ | ||||||
|  | // Copyright 2016 The Gorilla WebSocket 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 appengine
 | ||||||
|  | // +build appengine
 | ||||||
|  | 
 | ||||||
|  | package websocket | ||||||
|  | 
 | ||||||
|  | func maskBytes(key [4]byte, pos int, b []byte) int { | ||||||
|  | 	for i := range b { | ||||||
|  | 		b[i] ^= key[pos&3] | ||||||
|  | 		pos++ | ||||||
|  | 	} | ||||||
|  | 	return pos & 3 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,102 @@ | ||||||
|  | // Copyright 2017 The Gorilla WebSocket 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 websocket | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"net" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // PreparedMessage caches on the wire representations of a message payload.
 | ||||||
|  | // Use PreparedMessage to efficiently send a message payload to multiple
 | ||||||
|  | // connections. PreparedMessage is especially useful when compression is used
 | ||||||
|  | // because the CPU and memory expensive compression operation can be executed
 | ||||||
|  | // once for a given set of compression options.
 | ||||||
|  | type PreparedMessage struct { | ||||||
|  | 	messageType int | ||||||
|  | 	data        []byte | ||||||
|  | 	mu          sync.Mutex | ||||||
|  | 	frames      map[prepareKey]*preparedFrame | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // prepareKey defines a unique set of options to cache prepared frames in PreparedMessage.
 | ||||||
|  | type prepareKey struct { | ||||||
|  | 	isServer         bool | ||||||
|  | 	compress         bool | ||||||
|  | 	compressionLevel int | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // preparedFrame contains data in wire representation.
 | ||||||
|  | type preparedFrame struct { | ||||||
|  | 	once sync.Once | ||||||
|  | 	data []byte | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewPreparedMessage returns an initialized PreparedMessage. You can then send
 | ||||||
|  | // it to connection using WritePreparedMessage method. Valid wire
 | ||||||
|  | // representation will be calculated lazily only once for a set of current
 | ||||||
|  | // connection options.
 | ||||||
|  | func NewPreparedMessage(messageType int, data []byte) (*PreparedMessage, error) { | ||||||
|  | 	pm := &PreparedMessage{ | ||||||
|  | 		messageType: messageType, | ||||||
|  | 		frames:      make(map[prepareKey]*preparedFrame), | ||||||
|  | 		data:        data, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Prepare a plain server frame.
 | ||||||
|  | 	_, frameData, err := pm.frame(prepareKey{isServer: true, compress: false}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// To protect against caller modifying the data argument, remember the data
 | ||||||
|  | 	// copied to the plain server frame.
 | ||||||
|  | 	pm.data = frameData[len(frameData)-len(data):] | ||||||
|  | 	return pm, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) { | ||||||
|  | 	pm.mu.Lock() | ||||||
|  | 	frame, ok := pm.frames[key] | ||||||
|  | 	if !ok { | ||||||
|  | 		frame = &preparedFrame{} | ||||||
|  | 		pm.frames[key] = frame | ||||||
|  | 	} | ||||||
|  | 	pm.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	var err error | ||||||
|  | 	frame.once.Do(func() { | ||||||
|  | 		// Prepare a frame using a 'fake' connection.
 | ||||||
|  | 		// TODO: Refactor code in conn.go to allow more direct construction of
 | ||||||
|  | 		// the frame.
 | ||||||
|  | 		mu := make(chan struct{}, 1) | ||||||
|  | 		mu <- struct{}{} | ||||||
|  | 		var nc prepareConn | ||||||
|  | 		c := &Conn{ | ||||||
|  | 			conn:                   &nc, | ||||||
|  | 			mu:                     mu, | ||||||
|  | 			isServer:               key.isServer, | ||||||
|  | 			compressionLevel:       key.compressionLevel, | ||||||
|  | 			enableWriteCompression: true, | ||||||
|  | 			writeBuf:               make([]byte, defaultWriteBufferSize+maxFrameHeaderSize), | ||||||
|  | 		} | ||||||
|  | 		if key.compress { | ||||||
|  | 			c.newCompressionWriter = compressNoContextTakeover | ||||||
|  | 		} | ||||||
|  | 		err = c.WriteMessage(pm.messageType, pm.data) | ||||||
|  | 		frame.data = nc.buf.Bytes() | ||||||
|  | 	}) | ||||||
|  | 	return pm.messageType, frame.data, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type prepareConn struct { | ||||||
|  | 	buf bytes.Buffer | ||||||
|  | 	net.Conn | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (pc *prepareConn) Write(p []byte) (int, error)        { return pc.buf.Write(p) } | ||||||
|  | func (pc *prepareConn) SetWriteDeadline(t time.Time) error { return nil } | ||||||
|  | @ -0,0 +1,77 @@ | ||||||
|  | // Copyright 2017 The Gorilla WebSocket 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 websocket | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bufio" | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"errors" | ||||||
|  | 	"net" | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type netDialerFunc func(network, addr string) (net.Conn, error) | ||||||
|  | 
 | ||||||
|  | func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) { | ||||||
|  | 	return fn(network, addr) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) { | ||||||
|  | 		return &httpProxyDialer{proxyURL: proxyURL, forwardDial: forwardDialer.Dial}, nil | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type httpProxyDialer struct { | ||||||
|  | 	proxyURL    *url.URL | ||||||
|  | 	forwardDial func(network, addr string) (net.Conn, error) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) { | ||||||
|  | 	hostPort, _ := hostPortNoPort(hpd.proxyURL) | ||||||
|  | 	conn, err := hpd.forwardDial(network, hostPort) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	connectHeader := make(http.Header) | ||||||
|  | 	if user := hpd.proxyURL.User; user != nil { | ||||||
|  | 		proxyUser := user.Username() | ||||||
|  | 		if proxyPassword, passwordSet := user.Password(); passwordSet { | ||||||
|  | 			credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword)) | ||||||
|  | 			connectHeader.Set("Proxy-Authorization", "Basic "+credential) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	connectReq := &http.Request{ | ||||||
|  | 		Method: http.MethodConnect, | ||||||
|  | 		URL:    &url.URL{Opaque: addr}, | ||||||
|  | 		Host:   addr, | ||||||
|  | 		Header: connectHeader, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := connectReq.Write(conn); err != nil { | ||||||
|  | 		conn.Close() | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Read response. It's OK to use and discard buffered reader here becaue
 | ||||||
|  | 	// the remote server does not speak until spoken to.
 | ||||||
|  | 	br := bufio.NewReader(conn) | ||||||
|  | 	resp, err := http.ReadResponse(br, connectReq) | ||||||
|  | 	if err != nil { | ||||||
|  | 		conn.Close() | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if resp.StatusCode != 200 { | ||||||
|  | 		conn.Close() | ||||||
|  | 		f := strings.SplitN(resp.Status, " ", 2) | ||||||
|  | 		return nil, errors.New(f[1]) | ||||||
|  | 	} | ||||||
|  | 	return conn, nil | ||||||
|  | } | ||||||
|  | @ -0,0 +1,365 @@ | ||||||
|  | // Copyright 2013 The Gorilla WebSocket 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 websocket | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bufio" | ||||||
|  | 	"errors" | ||||||
|  | 	"io" | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // HandshakeError describes an error with the handshake from the peer.
 | ||||||
|  | type HandshakeError struct { | ||||||
|  | 	message string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e HandshakeError) Error() string { return e.message } | ||||||
|  | 
 | ||||||
|  | // Upgrader specifies parameters for upgrading an HTTP connection to a
 | ||||||
|  | // WebSocket connection.
 | ||||||
|  | //
 | ||||||
|  | // It is safe to call Upgrader's methods concurrently.
 | ||||||
|  | type Upgrader struct { | ||||||
|  | 	// HandshakeTimeout specifies the duration for the handshake to complete.
 | ||||||
|  | 	HandshakeTimeout time.Duration | ||||||
|  | 
 | ||||||
|  | 	// ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer
 | ||||||
|  | 	// size is zero, then buffers allocated by the HTTP server are used. The
 | ||||||
|  | 	// I/O buffer sizes do not limit the size of the messages that can be sent
 | ||||||
|  | 	// or received.
 | ||||||
|  | 	ReadBufferSize, WriteBufferSize int | ||||||
|  | 
 | ||||||
|  | 	// WriteBufferPool is a pool of buffers for write operations. If the value
 | ||||||
|  | 	// is not set, then write buffers are allocated to the connection for the
 | ||||||
|  | 	// lifetime of the connection.
 | ||||||
|  | 	//
 | ||||||
|  | 	// A pool is most useful when the application has a modest volume of writes
 | ||||||
|  | 	// across a large number of connections.
 | ||||||
|  | 	//
 | ||||||
|  | 	// Applications should use a single pool for each unique value of
 | ||||||
|  | 	// WriteBufferSize.
 | ||||||
|  | 	WriteBufferPool BufferPool | ||||||
|  | 
 | ||||||
|  | 	// Subprotocols specifies the server's supported protocols in order of
 | ||||||
|  | 	// preference. If this field is not nil, then the Upgrade method negotiates a
 | ||||||
|  | 	// subprotocol by selecting the first match in this list with a protocol
 | ||||||
|  | 	// requested by the client. If there's no match, then no protocol is
 | ||||||
|  | 	// negotiated (the Sec-Websocket-Protocol header is not included in the
 | ||||||
|  | 	// handshake response).
 | ||||||
|  | 	Subprotocols []string | ||||||
|  | 
 | ||||||
|  | 	// Error specifies the function for generating HTTP error responses. If Error
 | ||||||
|  | 	// is nil, then http.Error is used to generate the HTTP response.
 | ||||||
|  | 	Error func(w http.ResponseWriter, r *http.Request, status int, reason error) | ||||||
|  | 
 | ||||||
|  | 	// CheckOrigin returns true if the request Origin header is acceptable. If
 | ||||||
|  | 	// CheckOrigin is nil, then a safe default is used: return false if the
 | ||||||
|  | 	// Origin request header is present and the origin host is not equal to
 | ||||||
|  | 	// request Host header.
 | ||||||
|  | 	//
 | ||||||
|  | 	// A CheckOrigin function should carefully validate the request origin to
 | ||||||
|  | 	// prevent cross-site request forgery.
 | ||||||
|  | 	CheckOrigin func(r *http.Request) bool | ||||||
|  | 
 | ||||||
|  | 	// EnableCompression specify if the server should attempt to negotiate per
 | ||||||
|  | 	// message compression (RFC 7692). Setting this value to true does not
 | ||||||
|  | 	// guarantee that compression will be supported. Currently only "no context
 | ||||||
|  | 	// takeover" modes are supported.
 | ||||||
|  | 	EnableCompression bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) { | ||||||
|  | 	err := HandshakeError{reason} | ||||||
|  | 	if u.Error != nil { | ||||||
|  | 		u.Error(w, r, status, err) | ||||||
|  | 	} else { | ||||||
|  | 		w.Header().Set("Sec-Websocket-Version", "13") | ||||||
|  | 		http.Error(w, http.StatusText(status), status) | ||||||
|  | 	} | ||||||
|  | 	return nil, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // checkSameOrigin returns true if the origin is not set or is equal to the request host.
 | ||||||
|  | func checkSameOrigin(r *http.Request) bool { | ||||||
|  | 	origin := r.Header["Origin"] | ||||||
|  | 	if len(origin) == 0 { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	u, err := url.Parse(origin[0]) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	return equalASCIIFold(u.Host, r.Host) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string { | ||||||
|  | 	if u.Subprotocols != nil { | ||||||
|  | 		clientProtocols := Subprotocols(r) | ||||||
|  | 		for _, serverProtocol := range u.Subprotocols { | ||||||
|  | 			for _, clientProtocol := range clientProtocols { | ||||||
|  | 				if clientProtocol == serverProtocol { | ||||||
|  | 					return clientProtocol | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} else if responseHeader != nil { | ||||||
|  | 		return responseHeader.Get("Sec-Websocket-Protocol") | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Upgrade upgrades the HTTP server connection to the WebSocket protocol.
 | ||||||
|  | //
 | ||||||
|  | // The responseHeader is included in the response to the client's upgrade
 | ||||||
|  | // request. Use the responseHeader to specify cookies (Set-Cookie). To specify
 | ||||||
|  | // subprotocols supported by the server, set Upgrader.Subprotocols directly.
 | ||||||
|  | //
 | ||||||
|  | // If the upgrade fails, then Upgrade replies to the client with an HTTP error
 | ||||||
|  | // response.
 | ||||||
|  | func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) { | ||||||
|  | 	const badHandshake = "websocket: the client is not using the websocket protocol: " | ||||||
|  | 
 | ||||||
|  | 	if !tokenListContainsValue(r.Header, "Connection", "upgrade") { | ||||||
|  | 		return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'upgrade' token not found in 'Connection' header") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !tokenListContainsValue(r.Header, "Upgrade", "websocket") { | ||||||
|  | 		return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'websocket' token not found in 'Upgrade' header") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if r.Method != http.MethodGet { | ||||||
|  | 		return u.returnError(w, r, http.StatusMethodNotAllowed, badHandshake+"request method is not GET") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") { | ||||||
|  | 		return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok { | ||||||
|  | 		return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-WebSocket-Extensions' headers are unsupported") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	checkOrigin := u.CheckOrigin | ||||||
|  | 	if checkOrigin == nil { | ||||||
|  | 		checkOrigin = checkSameOrigin | ||||||
|  | 	} | ||||||
|  | 	if !checkOrigin(r) { | ||||||
|  | 		return u.returnError(w, r, http.StatusForbidden, "websocket: request origin not allowed by Upgrader.CheckOrigin") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	challengeKey := r.Header.Get("Sec-Websocket-Key") | ||||||
|  | 	if challengeKey == "" { | ||||||
|  | 		return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'Sec-WebSocket-Key' header is missing or blank") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	subprotocol := u.selectSubprotocol(r, responseHeader) | ||||||
|  | 
 | ||||||
|  | 	// Negotiate PMCE
 | ||||||
|  | 	var compress bool | ||||||
|  | 	if u.EnableCompression { | ||||||
|  | 		for _, ext := range parseExtensions(r.Header) { | ||||||
|  | 			if ext[""] != "permessage-deflate" { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			compress = true | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	h, ok := w.(http.Hijacker) | ||||||
|  | 	if !ok { | ||||||
|  | 		return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker") | ||||||
|  | 	} | ||||||
|  | 	var brw *bufio.ReadWriter | ||||||
|  | 	netConn, brw, err := h.Hijack() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return u.returnError(w, r, http.StatusInternalServerError, err.Error()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if brw.Reader.Buffered() > 0 { | ||||||
|  | 		netConn.Close() | ||||||
|  | 		return nil, errors.New("websocket: client sent data before handshake is complete") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var br *bufio.Reader | ||||||
|  | 	if u.ReadBufferSize == 0 && bufioReaderSize(netConn, brw.Reader) > 256 { | ||||||
|  | 		// Reuse hijacked buffered reader as connection reader.
 | ||||||
|  | 		br = brw.Reader | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	buf := bufioWriterBuffer(netConn, brw.Writer) | ||||||
|  | 
 | ||||||
|  | 	var writeBuf []byte | ||||||
|  | 	if u.WriteBufferPool == nil && u.WriteBufferSize == 0 && len(buf) >= maxFrameHeaderSize+256 { | ||||||
|  | 		// Reuse hijacked write buffer as connection buffer.
 | ||||||
|  | 		writeBuf = buf | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	c := newConn(netConn, true, u.ReadBufferSize, u.WriteBufferSize, u.WriteBufferPool, br, writeBuf) | ||||||
|  | 	c.subprotocol = subprotocol | ||||||
|  | 
 | ||||||
|  | 	if compress { | ||||||
|  | 		c.newCompressionWriter = compressNoContextTakeover | ||||||
|  | 		c.newDecompressionReader = decompressNoContextTakeover | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Use larger of hijacked buffer and connection write buffer for header.
 | ||||||
|  | 	p := buf | ||||||
|  | 	if len(c.writeBuf) > len(p) { | ||||||
|  | 		p = c.writeBuf | ||||||
|  | 	} | ||||||
|  | 	p = p[:0] | ||||||
|  | 
 | ||||||
|  | 	p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...) | ||||||
|  | 	p = append(p, computeAcceptKey(challengeKey)...) | ||||||
|  | 	p = append(p, "\r\n"...) | ||||||
|  | 	if c.subprotocol != "" { | ||||||
|  | 		p = append(p, "Sec-WebSocket-Protocol: "...) | ||||||
|  | 		p = append(p, c.subprotocol...) | ||||||
|  | 		p = append(p, "\r\n"...) | ||||||
|  | 	} | ||||||
|  | 	if compress { | ||||||
|  | 		p = append(p, "Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...) | ||||||
|  | 	} | ||||||
|  | 	for k, vs := range responseHeader { | ||||||
|  | 		if k == "Sec-Websocket-Protocol" { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		for _, v := range vs { | ||||||
|  | 			p = append(p, k...) | ||||||
|  | 			p = append(p, ": "...) | ||||||
|  | 			for i := 0; i < len(v); i++ { | ||||||
|  | 				b := v[i] | ||||||
|  | 				if b <= 31 { | ||||||
|  | 					// prevent response splitting.
 | ||||||
|  | 					b = ' ' | ||||||
|  | 				} | ||||||
|  | 				p = append(p, b) | ||||||
|  | 			} | ||||||
|  | 			p = append(p, "\r\n"...) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	p = append(p, "\r\n"...) | ||||||
|  | 
 | ||||||
|  | 	// Clear deadlines set by HTTP server.
 | ||||||
|  | 	netConn.SetDeadline(time.Time{}) | ||||||
|  | 
 | ||||||
|  | 	if u.HandshakeTimeout > 0 { | ||||||
|  | 		netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout)) | ||||||
|  | 	} | ||||||
|  | 	if _, err = netConn.Write(p); err != nil { | ||||||
|  | 		netConn.Close() | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if u.HandshakeTimeout > 0 { | ||||||
|  | 		netConn.SetWriteDeadline(time.Time{}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return c, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Upgrade upgrades the HTTP server connection to the WebSocket protocol.
 | ||||||
|  | //
 | ||||||
|  | // Deprecated: Use websocket.Upgrader instead.
 | ||||||
|  | //
 | ||||||
|  | // Upgrade does not perform origin checking. The application is responsible for
 | ||||||
|  | // checking the Origin header before calling Upgrade. An example implementation
 | ||||||
|  | // of the same origin policy check is:
 | ||||||
|  | //
 | ||||||
|  | //	if req.Header.Get("Origin") != "http://"+req.Host {
 | ||||||
|  | //		http.Error(w, "Origin not allowed", http.StatusForbidden)
 | ||||||
|  | //		return
 | ||||||
|  | //	}
 | ||||||
|  | //
 | ||||||
|  | // If the endpoint supports subprotocols, then the application is responsible
 | ||||||
|  | // for negotiating the protocol used on the connection. Use the Subprotocols()
 | ||||||
|  | // function to get the subprotocols requested by the client. Use the
 | ||||||
|  | // Sec-Websocket-Protocol response header to specify the subprotocol selected
 | ||||||
|  | // by the application.
 | ||||||
|  | //
 | ||||||
|  | // The responseHeader is included in the response to the client's upgrade
 | ||||||
|  | // request. Use the responseHeader to specify cookies (Set-Cookie) and the
 | ||||||
|  | // negotiated subprotocol (Sec-Websocket-Protocol).
 | ||||||
|  | //
 | ||||||
|  | // The connection buffers IO to the underlying network connection. The
 | ||||||
|  | // readBufSize and writeBufSize parameters specify the size of the buffers to
 | ||||||
|  | // use. Messages can be larger than the buffers.
 | ||||||
|  | //
 | ||||||
|  | // If the request is not a valid WebSocket handshake, then Upgrade returns an
 | ||||||
|  | // error of type HandshakeError. Applications should handle this error by
 | ||||||
|  | // replying to the client with an HTTP error response.
 | ||||||
|  | func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header, readBufSize, writeBufSize int) (*Conn, error) { | ||||||
|  | 	u := Upgrader{ReadBufferSize: readBufSize, WriteBufferSize: writeBufSize} | ||||||
|  | 	u.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) { | ||||||
|  | 		// don't return errors to maintain backwards compatibility
 | ||||||
|  | 	} | ||||||
|  | 	u.CheckOrigin = func(r *http.Request) bool { | ||||||
|  | 		// allow all connections by default
 | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	return u.Upgrade(w, r, responseHeader) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Subprotocols returns the subprotocols requested by the client in the
 | ||||||
|  | // Sec-Websocket-Protocol header.
 | ||||||
|  | func Subprotocols(r *http.Request) []string { | ||||||
|  | 	h := strings.TrimSpace(r.Header.Get("Sec-Websocket-Protocol")) | ||||||
|  | 	if h == "" { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	protocols := strings.Split(h, ",") | ||||||
|  | 	for i := range protocols { | ||||||
|  | 		protocols[i] = strings.TrimSpace(protocols[i]) | ||||||
|  | 	} | ||||||
|  | 	return protocols | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // IsWebSocketUpgrade returns true if the client requested upgrade to the
 | ||||||
|  | // WebSocket protocol.
 | ||||||
|  | func IsWebSocketUpgrade(r *http.Request) bool { | ||||||
|  | 	return tokenListContainsValue(r.Header, "Connection", "upgrade") && | ||||||
|  | 		tokenListContainsValue(r.Header, "Upgrade", "websocket") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // bufioReaderSize size returns the size of a bufio.Reader.
 | ||||||
|  | func bufioReaderSize(originalReader io.Reader, br *bufio.Reader) int { | ||||||
|  | 	// This code assumes that peek on a reset reader returns
 | ||||||
|  | 	// bufio.Reader.buf[:0].
 | ||||||
|  | 	// TODO: Use bufio.Reader.Size() after Go 1.10
 | ||||||
|  | 	br.Reset(originalReader) | ||||||
|  | 	if p, err := br.Peek(0); err == nil { | ||||||
|  | 		return cap(p) | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // writeHook is an io.Writer that records the last slice passed to it vio
 | ||||||
|  | // io.Writer.Write.
 | ||||||
|  | type writeHook struct { | ||||||
|  | 	p []byte | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (wh *writeHook) Write(p []byte) (int, error) { | ||||||
|  | 	wh.p = p | ||||||
|  | 	return len(p), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // bufioWriterBuffer grabs the buffer from a bufio.Writer.
 | ||||||
|  | func bufioWriterBuffer(originalWriter io.Writer, bw *bufio.Writer) []byte { | ||||||
|  | 	// This code assumes that bufio.Writer.buf[:1] is passed to the
 | ||||||
|  | 	// bufio.Writer's underlying writer.
 | ||||||
|  | 	var wh writeHook | ||||||
|  | 	bw.Reset(&wh) | ||||||
|  | 	bw.WriteByte(0) | ||||||
|  | 	bw.Flush() | ||||||
|  | 
 | ||||||
|  | 	bw.Reset(originalWriter) | ||||||
|  | 
 | ||||||
|  | 	return wh.p[:cap(wh.p)] | ||||||
|  | } | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | //go:build go1.17
 | ||||||
|  | // +build go1.17
 | ||||||
|  | 
 | ||||||
|  | package websocket | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"crypto/tls" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func doHandshake(ctx context.Context, tlsConn *tls.Conn, cfg *tls.Config) error { | ||||||
|  | 	if err := tlsConn.HandshakeContext(ctx); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if !cfg.InsecureSkipVerify { | ||||||
|  | 		if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | //go:build !go1.17
 | ||||||
|  | // +build !go1.17
 | ||||||
|  | 
 | ||||||
|  | package websocket | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"crypto/tls" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func doHandshake(ctx context.Context, tlsConn *tls.Conn, cfg *tls.Config) error { | ||||||
|  | 	if err := tlsConn.Handshake(); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if !cfg.InsecureSkipVerify { | ||||||
|  | 		if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | @ -0,0 +1,283 @@ | ||||||
|  | // Copyright 2013 The Gorilla WebSocket 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 websocket | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"crypto/rand" | ||||||
|  | 	"crypto/sha1" | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"io" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strings" | ||||||
|  | 	"unicode/utf8" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") | ||||||
|  | 
 | ||||||
|  | func computeAcceptKey(challengeKey string) string { | ||||||
|  | 	h := sha1.New() | ||||||
|  | 	h.Write([]byte(challengeKey)) | ||||||
|  | 	h.Write(keyGUID) | ||||||
|  | 	return base64.StdEncoding.EncodeToString(h.Sum(nil)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func generateChallengeKey() (string, error) { | ||||||
|  | 	p := make([]byte, 16) | ||||||
|  | 	if _, err := io.ReadFull(rand.Reader, p); err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	return base64.StdEncoding.EncodeToString(p), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Token octets per RFC 2616.
 | ||||||
|  | var isTokenOctet = [256]bool{ | ||||||
|  | 	'!':  true, | ||||||
|  | 	'#':  true, | ||||||
|  | 	'$':  true, | ||||||
|  | 	'%':  true, | ||||||
|  | 	'&':  true, | ||||||
|  | 	'\'': true, | ||||||
|  | 	'*':  true, | ||||||
|  | 	'+':  true, | ||||||
|  | 	'-':  true, | ||||||
|  | 	'.':  true, | ||||||
|  | 	'0':  true, | ||||||
|  | 	'1':  true, | ||||||
|  | 	'2':  true, | ||||||
|  | 	'3':  true, | ||||||
|  | 	'4':  true, | ||||||
|  | 	'5':  true, | ||||||
|  | 	'6':  true, | ||||||
|  | 	'7':  true, | ||||||
|  | 	'8':  true, | ||||||
|  | 	'9':  true, | ||||||
|  | 	'A':  true, | ||||||
|  | 	'B':  true, | ||||||
|  | 	'C':  true, | ||||||
|  | 	'D':  true, | ||||||
|  | 	'E':  true, | ||||||
|  | 	'F':  true, | ||||||
|  | 	'G':  true, | ||||||
|  | 	'H':  true, | ||||||
|  | 	'I':  true, | ||||||
|  | 	'J':  true, | ||||||
|  | 	'K':  true, | ||||||
|  | 	'L':  true, | ||||||
|  | 	'M':  true, | ||||||
|  | 	'N':  true, | ||||||
|  | 	'O':  true, | ||||||
|  | 	'P':  true, | ||||||
|  | 	'Q':  true, | ||||||
|  | 	'R':  true, | ||||||
|  | 	'S':  true, | ||||||
|  | 	'T':  true, | ||||||
|  | 	'U':  true, | ||||||
|  | 	'W':  true, | ||||||
|  | 	'V':  true, | ||||||
|  | 	'X':  true, | ||||||
|  | 	'Y':  true, | ||||||
|  | 	'Z':  true, | ||||||
|  | 	'^':  true, | ||||||
|  | 	'_':  true, | ||||||
|  | 	'`':  true, | ||||||
|  | 	'a':  true, | ||||||
|  | 	'b':  true, | ||||||
|  | 	'c':  true, | ||||||
|  | 	'd':  true, | ||||||
|  | 	'e':  true, | ||||||
|  | 	'f':  true, | ||||||
|  | 	'g':  true, | ||||||
|  | 	'h':  true, | ||||||
|  | 	'i':  true, | ||||||
|  | 	'j':  true, | ||||||
|  | 	'k':  true, | ||||||
|  | 	'l':  true, | ||||||
|  | 	'm':  true, | ||||||
|  | 	'n':  true, | ||||||
|  | 	'o':  true, | ||||||
|  | 	'p':  true, | ||||||
|  | 	'q':  true, | ||||||
|  | 	'r':  true, | ||||||
|  | 	's':  true, | ||||||
|  | 	't':  true, | ||||||
|  | 	'u':  true, | ||||||
|  | 	'v':  true, | ||||||
|  | 	'w':  true, | ||||||
|  | 	'x':  true, | ||||||
|  | 	'y':  true, | ||||||
|  | 	'z':  true, | ||||||
|  | 	'|':  true, | ||||||
|  | 	'~':  true, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // skipSpace returns a slice of the string s with all leading RFC 2616 linear
 | ||||||
|  | // whitespace removed.
 | ||||||
|  | func skipSpace(s string) (rest string) { | ||||||
|  | 	i := 0 | ||||||
|  | 	for ; i < len(s); i++ { | ||||||
|  | 		if b := s[i]; b != ' ' && b != '\t' { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return s[i:] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // nextToken returns the leading RFC 2616 token of s and the string following
 | ||||||
|  | // the token.
 | ||||||
|  | func nextToken(s string) (token, rest string) { | ||||||
|  | 	i := 0 | ||||||
|  | 	for ; i < len(s); i++ { | ||||||
|  | 		if !isTokenOctet[s[i]] { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return s[:i], s[i:] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // nextTokenOrQuoted returns the leading token or quoted string per RFC 2616
 | ||||||
|  | // and the string following the token or quoted string.
 | ||||||
|  | func nextTokenOrQuoted(s string) (value string, rest string) { | ||||||
|  | 	if !strings.HasPrefix(s, "\"") { | ||||||
|  | 		return nextToken(s) | ||||||
|  | 	} | ||||||
|  | 	s = s[1:] | ||||||
|  | 	for i := 0; i < len(s); i++ { | ||||||
|  | 		switch s[i] { | ||||||
|  | 		case '"': | ||||||
|  | 			return s[:i], s[i+1:] | ||||||
|  | 		case '\\': | ||||||
|  | 			p := make([]byte, len(s)-1) | ||||||
|  | 			j := copy(p, s[:i]) | ||||||
|  | 			escape := true | ||||||
|  | 			for i = i + 1; i < len(s); i++ { | ||||||
|  | 				b := s[i] | ||||||
|  | 				switch { | ||||||
|  | 				case escape: | ||||||
|  | 					escape = false | ||||||
|  | 					p[j] = b | ||||||
|  | 					j++ | ||||||
|  | 				case b == '\\': | ||||||
|  | 					escape = true | ||||||
|  | 				case b == '"': | ||||||
|  | 					return string(p[:j]), s[i+1:] | ||||||
|  | 				default: | ||||||
|  | 					p[j] = b | ||||||
|  | 					j++ | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			return "", "" | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return "", "" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // equalASCIIFold returns true if s is equal to t with ASCII case folding as
 | ||||||
|  | // defined in RFC 4790.
 | ||||||
|  | func equalASCIIFold(s, t string) bool { | ||||||
|  | 	for s != "" && t != "" { | ||||||
|  | 		sr, size := utf8.DecodeRuneInString(s) | ||||||
|  | 		s = s[size:] | ||||||
|  | 		tr, size := utf8.DecodeRuneInString(t) | ||||||
|  | 		t = t[size:] | ||||||
|  | 		if sr == tr { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if 'A' <= sr && sr <= 'Z' { | ||||||
|  | 			sr = sr + 'a' - 'A' | ||||||
|  | 		} | ||||||
|  | 		if 'A' <= tr && tr <= 'Z' { | ||||||
|  | 			tr = tr + 'a' - 'A' | ||||||
|  | 		} | ||||||
|  | 		if sr != tr { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return s == t | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // tokenListContainsValue returns true if the 1#token header with the given
 | ||||||
|  | // name contains a token equal to value with ASCII case folding.
 | ||||||
|  | func tokenListContainsValue(header http.Header, name string, value string) bool { | ||||||
|  | headers: | ||||||
|  | 	for _, s := range header[name] { | ||||||
|  | 		for { | ||||||
|  | 			var t string | ||||||
|  | 			t, s = nextToken(skipSpace(s)) | ||||||
|  | 			if t == "" { | ||||||
|  | 				continue headers | ||||||
|  | 			} | ||||||
|  | 			s = skipSpace(s) | ||||||
|  | 			if s != "" && s[0] != ',' { | ||||||
|  | 				continue headers | ||||||
|  | 			} | ||||||
|  | 			if equalASCIIFold(t, value) { | ||||||
|  | 				return true | ||||||
|  | 			} | ||||||
|  | 			if s == "" { | ||||||
|  | 				continue headers | ||||||
|  | 			} | ||||||
|  | 			s = s[1:] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // parseExtensions parses WebSocket extensions from a header.
 | ||||||
|  | func parseExtensions(header http.Header) []map[string]string { | ||||||
|  | 	// From RFC 6455:
 | ||||||
|  | 	//
 | ||||||
|  | 	//  Sec-WebSocket-Extensions = extension-list
 | ||||||
|  | 	//  extension-list = 1#extension
 | ||||||
|  | 	//  extension = extension-token *( ";" extension-param )
 | ||||||
|  | 	//  extension-token = registered-token
 | ||||||
|  | 	//  registered-token = token
 | ||||||
|  | 	//  extension-param = token [ "=" (token | quoted-string) ]
 | ||||||
|  | 	//     ;When using the quoted-string syntax variant, the value
 | ||||||
|  | 	//     ;after quoted-string unescaping MUST conform to the
 | ||||||
|  | 	//     ;'token' ABNF.
 | ||||||
|  | 
 | ||||||
|  | 	var result []map[string]string | ||||||
|  | headers: | ||||||
|  | 	for _, s := range header["Sec-Websocket-Extensions"] { | ||||||
|  | 		for { | ||||||
|  | 			var t string | ||||||
|  | 			t, s = nextToken(skipSpace(s)) | ||||||
|  | 			if t == "" { | ||||||
|  | 				continue headers | ||||||
|  | 			} | ||||||
|  | 			ext := map[string]string{"": t} | ||||||
|  | 			for { | ||||||
|  | 				s = skipSpace(s) | ||||||
|  | 				if !strings.HasPrefix(s, ";") { | ||||||
|  | 					break | ||||||
|  | 				} | ||||||
|  | 				var k string | ||||||
|  | 				k, s = nextToken(skipSpace(s[1:])) | ||||||
|  | 				if k == "" { | ||||||
|  | 					continue headers | ||||||
|  | 				} | ||||||
|  | 				s = skipSpace(s) | ||||||
|  | 				var v string | ||||||
|  | 				if strings.HasPrefix(s, "=") { | ||||||
|  | 					v, s = nextTokenOrQuoted(skipSpace(s[1:])) | ||||||
|  | 					s = skipSpace(s) | ||||||
|  | 				} | ||||||
|  | 				if s != "" && s[0] != ',' && s[0] != ';' { | ||||||
|  | 					continue headers | ||||||
|  | 				} | ||||||
|  | 				ext[k] = v | ||||||
|  | 			} | ||||||
|  | 			if s != "" && s[0] != ',' { | ||||||
|  | 				continue headers | ||||||
|  | 			} | ||||||
|  | 			result = append(result, ext) | ||||||
|  | 			if s == "" { | ||||||
|  | 				continue headers | ||||||
|  | 			} | ||||||
|  | 			s = s[1:] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return result | ||||||
|  | } | ||||||
|  | @ -0,0 +1,473 @@ | ||||||
|  | // Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.
 | ||||||
|  | //go:generate bundle -o x_net_proxy.go golang.org/x/net/proxy
 | ||||||
|  | 
 | ||||||
|  | // Package proxy provides support for a variety of protocols to proxy network
 | ||||||
|  | // data.
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | package websocket | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"io" | ||||||
|  | 	"net" | ||||||
|  | 	"net/url" | ||||||
|  | 	"os" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type proxy_direct struct{} | ||||||
|  | 
 | ||||||
|  | // Direct is a direct proxy: one that makes network connections directly.
 | ||||||
|  | var proxy_Direct = proxy_direct{} | ||||||
|  | 
 | ||||||
|  | func (proxy_direct) Dial(network, addr string) (net.Conn, error) { | ||||||
|  | 	return net.Dial(network, addr) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // A PerHost directs connections to a default Dialer unless the host name
 | ||||||
|  | // requested matches one of a number of exceptions.
 | ||||||
|  | type proxy_PerHost struct { | ||||||
|  | 	def, bypass proxy_Dialer | ||||||
|  | 
 | ||||||
|  | 	bypassNetworks []*net.IPNet | ||||||
|  | 	bypassIPs      []net.IP | ||||||
|  | 	bypassZones    []string | ||||||
|  | 	bypassHosts    []string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewPerHost returns a PerHost Dialer that directs connections to either
 | ||||||
|  | // defaultDialer or bypass, depending on whether the connection matches one of
 | ||||||
|  | // the configured rules.
 | ||||||
|  | func proxy_NewPerHost(defaultDialer, bypass proxy_Dialer) *proxy_PerHost { | ||||||
|  | 	return &proxy_PerHost{ | ||||||
|  | 		def:    defaultDialer, | ||||||
|  | 		bypass: bypass, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Dial connects to the address addr on the given network through either
 | ||||||
|  | // defaultDialer or bypass.
 | ||||||
|  | func (p *proxy_PerHost) Dial(network, addr string) (c net.Conn, err error) { | ||||||
|  | 	host, _, err := net.SplitHostPort(addr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return p.dialerForRequest(host).Dial(network, addr) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *proxy_PerHost) dialerForRequest(host string) proxy_Dialer { | ||||||
|  | 	if ip := net.ParseIP(host); ip != nil { | ||||||
|  | 		for _, net := range p.bypassNetworks { | ||||||
|  | 			if net.Contains(ip) { | ||||||
|  | 				return p.bypass | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		for _, bypassIP := range p.bypassIPs { | ||||||
|  | 			if bypassIP.Equal(ip) { | ||||||
|  | 				return p.bypass | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return p.def | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, zone := range p.bypassZones { | ||||||
|  | 		if strings.HasSuffix(host, zone) { | ||||||
|  | 			return p.bypass | ||||||
|  | 		} | ||||||
|  | 		if host == zone[1:] { | ||||||
|  | 			// For a zone ".example.com", we match "example.com"
 | ||||||
|  | 			// too.
 | ||||||
|  | 			return p.bypass | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	for _, bypassHost := range p.bypassHosts { | ||||||
|  | 		if bypassHost == host { | ||||||
|  | 			return p.bypass | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return p.def | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AddFromString parses a string that contains comma-separated values
 | ||||||
|  | // specifying hosts that should use the bypass proxy. Each value is either an
 | ||||||
|  | // IP address, a CIDR range, a zone (*.example.com) or a host name
 | ||||||
|  | // (localhost). A best effort is made to parse the string and errors are
 | ||||||
|  | // ignored.
 | ||||||
|  | func (p *proxy_PerHost) AddFromString(s string) { | ||||||
|  | 	hosts := strings.Split(s, ",") | ||||||
|  | 	for _, host := range hosts { | ||||||
|  | 		host = strings.TrimSpace(host) | ||||||
|  | 		if len(host) == 0 { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if strings.Contains(host, "/") { | ||||||
|  | 			// We assume that it's a CIDR address like 127.0.0.0/8
 | ||||||
|  | 			if _, net, err := net.ParseCIDR(host); err == nil { | ||||||
|  | 				p.AddNetwork(net) | ||||||
|  | 			} | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if ip := net.ParseIP(host); ip != nil { | ||||||
|  | 			p.AddIP(ip) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if strings.HasPrefix(host, "*.") { | ||||||
|  | 			p.AddZone(host[1:]) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		p.AddHost(host) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AddIP specifies an IP address that will use the bypass proxy. Note that
 | ||||||
|  | // this will only take effect if a literal IP address is dialed. A connection
 | ||||||
|  | // to a named host will never match an IP.
 | ||||||
|  | func (p *proxy_PerHost) AddIP(ip net.IP) { | ||||||
|  | 	p.bypassIPs = append(p.bypassIPs, ip) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AddNetwork specifies an IP range that will use the bypass proxy. Note that
 | ||||||
|  | // this will only take effect if a literal IP address is dialed. A connection
 | ||||||
|  | // to a named host will never match.
 | ||||||
|  | func (p *proxy_PerHost) AddNetwork(net *net.IPNet) { | ||||||
|  | 	p.bypassNetworks = append(p.bypassNetworks, net) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AddZone specifies a DNS suffix that will use the bypass proxy. A zone of
 | ||||||
|  | // "example.com" matches "example.com" and all of its subdomains.
 | ||||||
|  | func (p *proxy_PerHost) AddZone(zone string) { | ||||||
|  | 	if strings.HasSuffix(zone, ".") { | ||||||
|  | 		zone = zone[:len(zone)-1] | ||||||
|  | 	} | ||||||
|  | 	if !strings.HasPrefix(zone, ".") { | ||||||
|  | 		zone = "." + zone | ||||||
|  | 	} | ||||||
|  | 	p.bypassZones = append(p.bypassZones, zone) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AddHost specifies a host name that will use the bypass proxy.
 | ||||||
|  | func (p *proxy_PerHost) AddHost(host string) { | ||||||
|  | 	if strings.HasSuffix(host, ".") { | ||||||
|  | 		host = host[:len(host)-1] | ||||||
|  | 	} | ||||||
|  | 	p.bypassHosts = append(p.bypassHosts, host) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // A Dialer is a means to establish a connection.
 | ||||||
|  | type proxy_Dialer interface { | ||||||
|  | 	// Dial connects to the given address via the proxy.
 | ||||||
|  | 	Dial(network, addr string) (c net.Conn, err error) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Auth contains authentication parameters that specific Dialers may require.
 | ||||||
|  | type proxy_Auth struct { | ||||||
|  | 	User, Password string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FromEnvironment returns the dialer specified by the proxy related variables in
 | ||||||
|  | // the environment.
 | ||||||
|  | func proxy_FromEnvironment() proxy_Dialer { | ||||||
|  | 	allProxy := proxy_allProxyEnv.Get() | ||||||
|  | 	if len(allProxy) == 0 { | ||||||
|  | 		return proxy_Direct | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	proxyURL, err := url.Parse(allProxy) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return proxy_Direct | ||||||
|  | 	} | ||||||
|  | 	proxy, err := proxy_FromURL(proxyURL, proxy_Direct) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return proxy_Direct | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	noProxy := proxy_noProxyEnv.Get() | ||||||
|  | 	if len(noProxy) == 0 { | ||||||
|  | 		return proxy | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	perHost := proxy_NewPerHost(proxy, proxy_Direct) | ||||||
|  | 	perHost.AddFromString(noProxy) | ||||||
|  | 	return perHost | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // proxySchemes is a map from URL schemes to a function that creates a Dialer
 | ||||||
|  | // from a URL with such a scheme.
 | ||||||
|  | var proxy_proxySchemes map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error) | ||||||
|  | 
 | ||||||
|  | // RegisterDialerType takes a URL scheme and a function to generate Dialers from
 | ||||||
|  | // a URL with that scheme and a forwarding Dialer. Registered schemes are used
 | ||||||
|  | // by FromURL.
 | ||||||
|  | func proxy_RegisterDialerType(scheme string, f func(*url.URL, proxy_Dialer) (proxy_Dialer, error)) { | ||||||
|  | 	if proxy_proxySchemes == nil { | ||||||
|  | 		proxy_proxySchemes = make(map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error)) | ||||||
|  | 	} | ||||||
|  | 	proxy_proxySchemes[scheme] = f | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FromURL returns a Dialer given a URL specification and an underlying
 | ||||||
|  | // Dialer for it to make network requests.
 | ||||||
|  | func proxy_FromURL(u *url.URL, forward proxy_Dialer) (proxy_Dialer, error) { | ||||||
|  | 	var auth *proxy_Auth | ||||||
|  | 	if u.User != nil { | ||||||
|  | 		auth = new(proxy_Auth) | ||||||
|  | 		auth.User = u.User.Username() | ||||||
|  | 		if p, ok := u.User.Password(); ok { | ||||||
|  | 			auth.Password = p | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch u.Scheme { | ||||||
|  | 	case "socks5": | ||||||
|  | 		return proxy_SOCKS5("tcp", u.Host, auth, forward) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// If the scheme doesn't match any of the built-in schemes, see if it
 | ||||||
|  | 	// was registered by another package.
 | ||||||
|  | 	if proxy_proxySchemes != nil { | ||||||
|  | 		if f, ok := proxy_proxySchemes[u.Scheme]; ok { | ||||||
|  | 			return f(u, forward) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil, errors.New("proxy: unknown scheme: " + u.Scheme) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	proxy_allProxyEnv = &proxy_envOnce{ | ||||||
|  | 		names: []string{"ALL_PROXY", "all_proxy"}, | ||||||
|  | 	} | ||||||
|  | 	proxy_noProxyEnv = &proxy_envOnce{ | ||||||
|  | 		names: []string{"NO_PROXY", "no_proxy"}, | ||||||
|  | 	} | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // envOnce looks up an environment variable (optionally by multiple
 | ||||||
|  | // names) once. It mitigates expensive lookups on some platforms
 | ||||||
|  | // (e.g. Windows).
 | ||||||
|  | // (Borrowed from net/http/transport.go)
 | ||||||
|  | type proxy_envOnce struct { | ||||||
|  | 	names []string | ||||||
|  | 	once  sync.Once | ||||||
|  | 	val   string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *proxy_envOnce) Get() string { | ||||||
|  | 	e.once.Do(e.init) | ||||||
|  | 	return e.val | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *proxy_envOnce) init() { | ||||||
|  | 	for _, n := range e.names { | ||||||
|  | 		e.val = os.Getenv(n) | ||||||
|  | 		if e.val != "" { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
 | ||||||
|  | // with an optional username and password. See RFC 1928 and RFC 1929.
 | ||||||
|  | func proxy_SOCKS5(network, addr string, auth *proxy_Auth, forward proxy_Dialer) (proxy_Dialer, error) { | ||||||
|  | 	s := &proxy_socks5{ | ||||||
|  | 		network: network, | ||||||
|  | 		addr:    addr, | ||||||
|  | 		forward: forward, | ||||||
|  | 	} | ||||||
|  | 	if auth != nil { | ||||||
|  | 		s.user = auth.User | ||||||
|  | 		s.password = auth.Password | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return s, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type proxy_socks5 struct { | ||||||
|  | 	user, password string | ||||||
|  | 	network, addr  string | ||||||
|  | 	forward        proxy_Dialer | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const proxy_socks5Version = 5 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	proxy_socks5AuthNone     = 0 | ||||||
|  | 	proxy_socks5AuthPassword = 2 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const proxy_socks5Connect = 1 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	proxy_socks5IP4    = 1 | ||||||
|  | 	proxy_socks5Domain = 3 | ||||||
|  | 	proxy_socks5IP6    = 4 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var proxy_socks5Errors = []string{ | ||||||
|  | 	"", | ||||||
|  | 	"general failure", | ||||||
|  | 	"connection forbidden", | ||||||
|  | 	"network unreachable", | ||||||
|  | 	"host unreachable", | ||||||
|  | 	"connection refused", | ||||||
|  | 	"TTL expired", | ||||||
|  | 	"command not supported", | ||||||
|  | 	"address type not supported", | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Dial connects to the address addr on the given network via the SOCKS5 proxy.
 | ||||||
|  | func (s *proxy_socks5) Dial(network, addr string) (net.Conn, error) { | ||||||
|  | 	switch network { | ||||||
|  | 	case "tcp", "tcp6", "tcp4": | ||||||
|  | 	default: | ||||||
|  | 		return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	conn, err := s.forward.Dial(s.network, s.addr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if err := s.connect(conn, addr); err != nil { | ||||||
|  | 		conn.Close() | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return conn, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // connect takes an existing connection to a socks5 proxy server,
 | ||||||
|  | // and commands the server to extend that connection to target,
 | ||||||
|  | // which must be a canonical address with a host and port.
 | ||||||
|  | func (s *proxy_socks5) connect(conn net.Conn, target string) error { | ||||||
|  | 	host, portStr, err := net.SplitHostPort(target) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	port, err := strconv.Atoi(portStr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return errors.New("proxy: failed to parse port number: " + portStr) | ||||||
|  | 	} | ||||||
|  | 	if port < 1 || port > 0xffff { | ||||||
|  | 		return errors.New("proxy: port number out of range: " + portStr) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// the size here is just an estimate
 | ||||||
|  | 	buf := make([]byte, 0, 6+len(host)) | ||||||
|  | 
 | ||||||
|  | 	buf = append(buf, proxy_socks5Version) | ||||||
|  | 	if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 { | ||||||
|  | 		buf = append(buf, 2 /* num auth methods */, proxy_socks5AuthNone, proxy_socks5AuthPassword) | ||||||
|  | 	} else { | ||||||
|  | 		buf = append(buf, 1 /* num auth methods */, proxy_socks5AuthNone) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if _, err := conn.Write(buf); err != nil { | ||||||
|  | 		return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if _, err := io.ReadFull(conn, buf[:2]); err != nil { | ||||||
|  | 		return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error()) | ||||||
|  | 	} | ||||||
|  | 	if buf[0] != 5 { | ||||||
|  | 		return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0]))) | ||||||
|  | 	} | ||||||
|  | 	if buf[1] == 0xff { | ||||||
|  | 		return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// See RFC 1929
 | ||||||
|  | 	if buf[1] == proxy_socks5AuthPassword { | ||||||
|  | 		buf = buf[:0] | ||||||
|  | 		buf = append(buf, 1 /* password protocol version */) | ||||||
|  | 		buf = append(buf, uint8(len(s.user))) | ||||||
|  | 		buf = append(buf, s.user...) | ||||||
|  | 		buf = append(buf, uint8(len(s.password))) | ||||||
|  | 		buf = append(buf, s.password...) | ||||||
|  | 
 | ||||||
|  | 		if _, err := conn.Write(buf); err != nil { | ||||||
|  | 			return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if _, err := io.ReadFull(conn, buf[:2]); err != nil { | ||||||
|  | 			return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if buf[1] != 0 { | ||||||
|  | 			return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	buf = buf[:0] | ||||||
|  | 	buf = append(buf, proxy_socks5Version, proxy_socks5Connect, 0 /* reserved */) | ||||||
|  | 
 | ||||||
|  | 	if ip := net.ParseIP(host); ip != nil { | ||||||
|  | 		if ip4 := ip.To4(); ip4 != nil { | ||||||
|  | 			buf = append(buf, proxy_socks5IP4) | ||||||
|  | 			ip = ip4 | ||||||
|  | 		} else { | ||||||
|  | 			buf = append(buf, proxy_socks5IP6) | ||||||
|  | 		} | ||||||
|  | 		buf = append(buf, ip...) | ||||||
|  | 	} else { | ||||||
|  | 		if len(host) > 255 { | ||||||
|  | 			return errors.New("proxy: destination host name too long: " + host) | ||||||
|  | 		} | ||||||
|  | 		buf = append(buf, proxy_socks5Domain) | ||||||
|  | 		buf = append(buf, byte(len(host))) | ||||||
|  | 		buf = append(buf, host...) | ||||||
|  | 	} | ||||||
|  | 	buf = append(buf, byte(port>>8), byte(port)) | ||||||
|  | 
 | ||||||
|  | 	if _, err := conn.Write(buf); err != nil { | ||||||
|  | 		return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if _, err := io.ReadFull(conn, buf[:4]); err != nil { | ||||||
|  | 		return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	failure := "unknown error" | ||||||
|  | 	if int(buf[1]) < len(proxy_socks5Errors) { | ||||||
|  | 		failure = proxy_socks5Errors[buf[1]] | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(failure) > 0 { | ||||||
|  | 		return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	bytesToDiscard := 0 | ||||||
|  | 	switch buf[3] { | ||||||
|  | 	case proxy_socks5IP4: | ||||||
|  | 		bytesToDiscard = net.IPv4len | ||||||
|  | 	case proxy_socks5IP6: | ||||||
|  | 		bytesToDiscard = net.IPv6len | ||||||
|  | 	case proxy_socks5Domain: | ||||||
|  | 		_, err := io.ReadFull(conn, buf[:1]) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error()) | ||||||
|  | 		} | ||||||
|  | 		bytesToDiscard = int(buf[0]) | ||||||
|  | 	default: | ||||||
|  | 		return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if cap(buf) < bytesToDiscard { | ||||||
|  | 		buf = make([]byte, bytesToDiscard) | ||||||
|  | 	} else { | ||||||
|  | 		buf = buf[:bytesToDiscard] | ||||||
|  | 	} | ||||||
|  | 	if _, err := io.ReadFull(conn, buf); err != nil { | ||||||
|  | 		return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Also need to discard the port number
 | ||||||
|  | 	if _, err := io.ReadFull(conn, buf[:2]); err != nil { | ||||||
|  | 		return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | @ -401,6 +401,7 @@ func (r *Lexer) scanToken() { | ||||||
| // consume resets the current token to allow scanning the next one.
 | // consume resets the current token to allow scanning the next one.
 | ||||||
| func (r *Lexer) consume() { | func (r *Lexer) consume() { | ||||||
| 	r.token.kind = tokenUndef | 	r.token.kind = tokenUndef | ||||||
|  | 	r.token.byteValueCloned = false | ||||||
| 	r.token.delimValue = 0 | 	r.token.delimValue = 0 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -528,6 +529,7 @@ func (r *Lexer) Skip() { | ||||||
| func (r *Lexer) SkipRecursive() { | func (r *Lexer) SkipRecursive() { | ||||||
| 	r.scanToken() | 	r.scanToken() | ||||||
| 	var start, end byte | 	var start, end byte | ||||||
|  | 	startPos := r.start | ||||||
| 
 | 
 | ||||||
| 	switch r.token.delimValue { | 	switch r.token.delimValue { | ||||||
| 	case '{': | 	case '{': | ||||||
|  | @ -553,6 +555,14 @@ func (r *Lexer) SkipRecursive() { | ||||||
| 			level-- | 			level-- | ||||||
| 			if level == 0 { | 			if level == 0 { | ||||||
| 				r.pos += i + 1 | 				r.pos += i + 1 | ||||||
|  | 				if !json.Valid(r.Data[startPos:r.pos]) { | ||||||
|  | 					r.pos = len(r.Data) | ||||||
|  | 					r.fatalError = &LexerError{ | ||||||
|  | 						Reason: "skipped array/object json value is invalid", | ||||||
|  | 						Offset: r.pos, | ||||||
|  | 						Data:   string(r.Data[r.pos:]), | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
| 		case c == '\\' && inQuotes: | 		case c == '\\' && inQuotes: | ||||||
|  | @ -702,6 +712,10 @@ func (r *Lexer) Bytes() []byte { | ||||||
| 		r.errInvalidToken("string") | 		r.errInvalidToken("string") | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  | 	if err := r.unescapeStringToken(); err != nil { | ||||||
|  | 		r.errInvalidToken("string") | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
| 	ret := make([]byte, base64.StdEncoding.DecodedLen(len(r.token.byteValue))) | 	ret := make([]byte, base64.StdEncoding.DecodedLen(len(r.token.byteValue))) | ||||||
| 	n, err := base64.StdEncoding.Decode(ret, r.token.byteValue) | 	n, err := base64.StdEncoding.Decode(ret, r.token.byteValue) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -0,0 +1,29 @@ | ||||||
|  | Copyright (c) 2014 The Go-FlowRate 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 the go-flowrate project 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. | ||||||
|  | @ -0,0 +1,267 @@ | ||||||
|  | //
 | ||||||
|  | // Written by Maxim Khitrov (November 2012)
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | // Package flowrate provides the tools for monitoring and limiting the flow rate
 | ||||||
|  | // of an arbitrary data stream.
 | ||||||
|  | package flowrate | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"math" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Monitor monitors and limits the transfer rate of a data stream.
 | ||||||
|  | type Monitor struct { | ||||||
|  | 	mu      sync.Mutex    // Mutex guarding access to all internal fields
 | ||||||
|  | 	active  bool          // Flag indicating an active transfer
 | ||||||
|  | 	start   time.Duration // Transfer start time (clock() value)
 | ||||||
|  | 	bytes   int64         // Total number of bytes transferred
 | ||||||
|  | 	samples int64         // Total number of samples taken
 | ||||||
|  | 
 | ||||||
|  | 	rSample float64 // Most recent transfer rate sample (bytes per second)
 | ||||||
|  | 	rEMA    float64 // Exponential moving average of rSample
 | ||||||
|  | 	rPeak   float64 // Peak transfer rate (max of all rSamples)
 | ||||||
|  | 	rWindow float64 // rEMA window (seconds)
 | ||||||
|  | 
 | ||||||
|  | 	sBytes int64         // Number of bytes transferred since sLast
 | ||||||
|  | 	sLast  time.Duration // Most recent sample time (stop time when inactive)
 | ||||||
|  | 	sRate  time.Duration // Sampling rate
 | ||||||
|  | 
 | ||||||
|  | 	tBytes int64         // Number of bytes expected in the current transfer
 | ||||||
|  | 	tLast  time.Duration // Time of the most recent transfer of at least 1 byte
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // New creates a new flow control monitor. Instantaneous transfer rate is
 | ||||||
|  | // measured and updated for each sampleRate interval. windowSize determines the
 | ||||||
|  | // weight of each sample in the exponential moving average (EMA) calculation.
 | ||||||
|  | // The exact formulas are:
 | ||||||
|  | //
 | ||||||
|  | // 	sampleTime = currentTime - prevSampleTime
 | ||||||
|  | // 	sampleRate = byteCount / sampleTime
 | ||||||
|  | // 	weight     = 1 - exp(-sampleTime/windowSize)
 | ||||||
|  | // 	newRate    = weight*sampleRate + (1-weight)*oldRate
 | ||||||
|  | //
 | ||||||
|  | // The default values for sampleRate and windowSize (if <= 0) are 100ms and 1s,
 | ||||||
|  | // respectively.
 | ||||||
|  | func New(sampleRate, windowSize time.Duration) *Monitor { | ||||||
|  | 	if sampleRate = clockRound(sampleRate); sampleRate <= 0 { | ||||||
|  | 		sampleRate = 5 * clockRate | ||||||
|  | 	} | ||||||
|  | 	if windowSize <= 0 { | ||||||
|  | 		windowSize = 1 * time.Second | ||||||
|  | 	} | ||||||
|  | 	now := clock() | ||||||
|  | 	return &Monitor{ | ||||||
|  | 		active:  true, | ||||||
|  | 		start:   now, | ||||||
|  | 		rWindow: windowSize.Seconds(), | ||||||
|  | 		sLast:   now, | ||||||
|  | 		sRate:   sampleRate, | ||||||
|  | 		tLast:   now, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Update records the transfer of n bytes and returns n. It should be called
 | ||||||
|  | // after each Read/Write operation, even if n is 0.
 | ||||||
|  | func (m *Monitor) Update(n int) int { | ||||||
|  | 	m.mu.Lock() | ||||||
|  | 	m.update(n) | ||||||
|  | 	m.mu.Unlock() | ||||||
|  | 	return n | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // IO is a convenience method intended to wrap io.Reader and io.Writer method
 | ||||||
|  | // execution. It calls m.Update(n) and then returns (n, err) unmodified.
 | ||||||
|  | func (m *Monitor) IO(n int, err error) (int, error) { | ||||||
|  | 	return m.Update(n), err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Done marks the transfer as finished and prevents any further updates or
 | ||||||
|  | // limiting. Instantaneous and current transfer rates drop to 0. Update, IO, and
 | ||||||
|  | // Limit methods become NOOPs. It returns the total number of bytes transferred.
 | ||||||
|  | func (m *Monitor) Done() int64 { | ||||||
|  | 	m.mu.Lock() | ||||||
|  | 	if now := m.update(0); m.sBytes > 0 { | ||||||
|  | 		m.reset(now) | ||||||
|  | 	} | ||||||
|  | 	m.active = false | ||||||
|  | 	m.tLast = 0 | ||||||
|  | 	n := m.bytes | ||||||
|  | 	m.mu.Unlock() | ||||||
|  | 	return n | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // timeRemLimit is the maximum Status.TimeRem value.
 | ||||||
|  | const timeRemLimit = 999*time.Hour + 59*time.Minute + 59*time.Second | ||||||
|  | 
 | ||||||
|  | // Status represents the current Monitor status. All transfer rates are in bytes
 | ||||||
|  | // per second rounded to the nearest byte.
 | ||||||
|  | type Status struct { | ||||||
|  | 	Active   bool          // Flag indicating an active transfer
 | ||||||
|  | 	Start    time.Time     // Transfer start time
 | ||||||
|  | 	Duration time.Duration // Time period covered by the statistics
 | ||||||
|  | 	Idle     time.Duration // Time since the last transfer of at least 1 byte
 | ||||||
|  | 	Bytes    int64         // Total number of bytes transferred
 | ||||||
|  | 	Samples  int64         // Total number of samples taken
 | ||||||
|  | 	InstRate int64         // Instantaneous transfer rate
 | ||||||
|  | 	CurRate  int64         // Current transfer rate (EMA of InstRate)
 | ||||||
|  | 	AvgRate  int64         // Average transfer rate (Bytes / Duration)
 | ||||||
|  | 	PeakRate int64         // Maximum instantaneous transfer rate
 | ||||||
|  | 	BytesRem int64         // Number of bytes remaining in the transfer
 | ||||||
|  | 	TimeRem  time.Duration // Estimated time to completion
 | ||||||
|  | 	Progress Percent       // Overall transfer progress
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Status returns current transfer status information. The returned value
 | ||||||
|  | // becomes static after a call to Done.
 | ||||||
|  | func (m *Monitor) Status() Status { | ||||||
|  | 	m.mu.Lock() | ||||||
|  | 	now := m.update(0) | ||||||
|  | 	s := Status{ | ||||||
|  | 		Active:   m.active, | ||||||
|  | 		Start:    clockToTime(m.start), | ||||||
|  | 		Duration: m.sLast - m.start, | ||||||
|  | 		Idle:     now - m.tLast, | ||||||
|  | 		Bytes:    m.bytes, | ||||||
|  | 		Samples:  m.samples, | ||||||
|  | 		PeakRate: round(m.rPeak), | ||||||
|  | 		BytesRem: m.tBytes - m.bytes, | ||||||
|  | 		Progress: percentOf(float64(m.bytes), float64(m.tBytes)), | ||||||
|  | 	} | ||||||
|  | 	if s.BytesRem < 0 { | ||||||
|  | 		s.BytesRem = 0 | ||||||
|  | 	} | ||||||
|  | 	if s.Duration > 0 { | ||||||
|  | 		rAvg := float64(s.Bytes) / s.Duration.Seconds() | ||||||
|  | 		s.AvgRate = round(rAvg) | ||||||
|  | 		if s.Active { | ||||||
|  | 			s.InstRate = round(m.rSample) | ||||||
|  | 			s.CurRate = round(m.rEMA) | ||||||
|  | 			if s.BytesRem > 0 { | ||||||
|  | 				if tRate := 0.8*m.rEMA + 0.2*rAvg; tRate > 0 { | ||||||
|  | 					ns := float64(s.BytesRem) / tRate * 1e9 | ||||||
|  | 					if ns > float64(timeRemLimit) { | ||||||
|  | 						ns = float64(timeRemLimit) | ||||||
|  | 					} | ||||||
|  | 					s.TimeRem = clockRound(time.Duration(ns)) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	m.mu.Unlock() | ||||||
|  | 	return s | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Limit restricts the instantaneous (per-sample) data flow to rate bytes per
 | ||||||
|  | // second. It returns the maximum number of bytes (0 <= n <= want) that may be
 | ||||||
|  | // transferred immediately without exceeding the limit. If block == true, the
 | ||||||
|  | // call blocks until n > 0. want is returned unmodified if want < 1, rate < 1,
 | ||||||
|  | // or the transfer is inactive (after a call to Done).
 | ||||||
|  | //
 | ||||||
|  | // At least one byte is always allowed to be transferred in any given sampling
 | ||||||
|  | // period. Thus, if the sampling rate is 100ms, the lowest achievable flow rate
 | ||||||
|  | // is 10 bytes per second.
 | ||||||
|  | //
 | ||||||
|  | // For usage examples, see the implementation of Reader and Writer in io.go.
 | ||||||
|  | func (m *Monitor) Limit(want int, rate int64, block bool) (n int) { | ||||||
|  | 	if want < 1 || rate < 1 { | ||||||
|  | 		return want | ||||||
|  | 	} | ||||||
|  | 	m.mu.Lock() | ||||||
|  | 
 | ||||||
|  | 	// Determine the maximum number of bytes that can be sent in one sample
 | ||||||
|  | 	limit := round(float64(rate) * m.sRate.Seconds()) | ||||||
|  | 	if limit <= 0 { | ||||||
|  | 		limit = 1 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// If block == true, wait until m.sBytes < limit
 | ||||||
|  | 	if now := m.update(0); block { | ||||||
|  | 		for m.sBytes >= limit && m.active { | ||||||
|  | 			now = m.waitNextSample(now) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Make limit <= want (unlimited if the transfer is no longer active)
 | ||||||
|  | 	if limit -= m.sBytes; limit > int64(want) || !m.active { | ||||||
|  | 		limit = int64(want) | ||||||
|  | 	} | ||||||
|  | 	m.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	if limit < 0 { | ||||||
|  | 		limit = 0 | ||||||
|  | 	} | ||||||
|  | 	return int(limit) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetTransferSize specifies the total size of the data transfer, which allows
 | ||||||
|  | // the Monitor to calculate the overall progress and time to completion.
 | ||||||
|  | func (m *Monitor) SetTransferSize(bytes int64) { | ||||||
|  | 	if bytes < 0 { | ||||||
|  | 		bytes = 0 | ||||||
|  | 	} | ||||||
|  | 	m.mu.Lock() | ||||||
|  | 	m.tBytes = bytes | ||||||
|  | 	m.mu.Unlock() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // update accumulates the transferred byte count for the current sample until
 | ||||||
|  | // clock() - m.sLast >= m.sRate. The monitor status is updated once the current
 | ||||||
|  | // sample is done.
 | ||||||
|  | func (m *Monitor) update(n int) (now time.Duration) { | ||||||
|  | 	if !m.active { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if now = clock(); n > 0 { | ||||||
|  | 		m.tLast = now | ||||||
|  | 	} | ||||||
|  | 	m.sBytes += int64(n) | ||||||
|  | 	if sTime := now - m.sLast; sTime >= m.sRate { | ||||||
|  | 		t := sTime.Seconds() | ||||||
|  | 		if m.rSample = float64(m.sBytes) / t; m.rSample > m.rPeak { | ||||||
|  | 			m.rPeak = m.rSample | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Exponential moving average using a method similar to *nix load
 | ||||||
|  | 		// average calculation. Longer sampling periods carry greater weight.
 | ||||||
|  | 		if m.samples > 0 { | ||||||
|  | 			w := math.Exp(-t / m.rWindow) | ||||||
|  | 			m.rEMA = m.rSample + w*(m.rEMA-m.rSample) | ||||||
|  | 		} else { | ||||||
|  | 			m.rEMA = m.rSample | ||||||
|  | 		} | ||||||
|  | 		m.reset(now) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // reset clears the current sample state in preparation for the next sample.
 | ||||||
|  | func (m *Monitor) reset(sampleTime time.Duration) { | ||||||
|  | 	m.bytes += m.sBytes | ||||||
|  | 	m.samples++ | ||||||
|  | 	m.sBytes = 0 | ||||||
|  | 	m.sLast = sampleTime | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // waitNextSample sleeps for the remainder of the current sample. The lock is
 | ||||||
|  | // released and reacquired during the actual sleep period, so it's possible for
 | ||||||
|  | // the transfer to be inactive when this method returns.
 | ||||||
|  | func (m *Monitor) waitNextSample(now time.Duration) time.Duration { | ||||||
|  | 	const minWait = 5 * time.Millisecond | ||||||
|  | 	current := m.sLast | ||||||
|  | 
 | ||||||
|  | 	// sleep until the last sample time changes (ideally, just one iteration)
 | ||||||
|  | 	for m.sLast == current && m.active { | ||||||
|  | 		d := current + m.sRate - now | ||||||
|  | 		m.mu.Unlock() | ||||||
|  | 		if d < minWait { | ||||||
|  | 			d = minWait | ||||||
|  | 		} | ||||||
|  | 		time.Sleep(d) | ||||||
|  | 		m.mu.Lock() | ||||||
|  | 		now = m.update(0) | ||||||
|  | 	} | ||||||
|  | 	return now | ||||||
|  | } | ||||||
|  | @ -0,0 +1,133 @@ | ||||||
|  | //
 | ||||||
|  | // Written by Maxim Khitrov (November 2012)
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | package flowrate | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"io" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // ErrLimit is returned by the Writer when a non-blocking write is short due to
 | ||||||
|  | // the transfer rate limit.
 | ||||||
|  | var ErrLimit = errors.New("flowrate: flow rate limit exceeded") | ||||||
|  | 
 | ||||||
|  | // Limiter is implemented by the Reader and Writer to provide a consistent
 | ||||||
|  | // interface for monitoring and controlling data transfer.
 | ||||||
|  | type Limiter interface { | ||||||
|  | 	Done() int64 | ||||||
|  | 	Status() Status | ||||||
|  | 	SetTransferSize(bytes int64) | ||||||
|  | 	SetLimit(new int64) (old int64) | ||||||
|  | 	SetBlocking(new bool) (old bool) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Reader implements io.ReadCloser with a restriction on the rate of data
 | ||||||
|  | // transfer.
 | ||||||
|  | type Reader struct { | ||||||
|  | 	io.Reader // Data source
 | ||||||
|  | 	*Monitor  // Flow control monitor
 | ||||||
|  | 
 | ||||||
|  | 	limit int64 // Rate limit in bytes per second (unlimited when <= 0)
 | ||||||
|  | 	block bool  // What to do when no new bytes can be read due to the limit
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewReader restricts all Read operations on r to limit bytes per second.
 | ||||||
|  | func NewReader(r io.Reader, limit int64) *Reader { | ||||||
|  | 	return &Reader{r, New(0, 0), limit, true} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Read reads up to len(p) bytes into p without exceeding the current transfer
 | ||||||
|  | // rate limit. It returns (0, nil) immediately if r is non-blocking and no new
 | ||||||
|  | // bytes can be read at this time.
 | ||||||
|  | func (r *Reader) Read(p []byte) (n int, err error) { | ||||||
|  | 	p = p[:r.Limit(len(p), r.limit, r.block)] | ||||||
|  | 	if len(p) > 0 { | ||||||
|  | 		n, err = r.IO(r.Reader.Read(p)) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetLimit changes the transfer rate limit to new bytes per second and returns
 | ||||||
|  | // the previous setting.
 | ||||||
|  | func (r *Reader) SetLimit(new int64) (old int64) { | ||||||
|  | 	old, r.limit = r.limit, new | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetBlocking changes the blocking behavior and returns the previous setting. A
 | ||||||
|  | // Read call on a non-blocking reader returns immediately if no additional bytes
 | ||||||
|  | // may be read at this time due to the rate limit.
 | ||||||
|  | func (r *Reader) SetBlocking(new bool) (old bool) { | ||||||
|  | 	old, r.block = r.block, new | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Close closes the underlying reader if it implements the io.Closer interface.
 | ||||||
|  | func (r *Reader) Close() error { | ||||||
|  | 	defer r.Done() | ||||||
|  | 	if c, ok := r.Reader.(io.Closer); ok { | ||||||
|  | 		return c.Close() | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Writer implements io.WriteCloser with a restriction on the rate of data
 | ||||||
|  | // transfer.
 | ||||||
|  | type Writer struct { | ||||||
|  | 	io.Writer // Data destination
 | ||||||
|  | 	*Monitor  // Flow control monitor
 | ||||||
|  | 
 | ||||||
|  | 	limit int64 // Rate limit in bytes per second (unlimited when <= 0)
 | ||||||
|  | 	block bool  // What to do when no new bytes can be written due to the limit
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewWriter restricts all Write operations on w to limit bytes per second. The
 | ||||||
|  | // transfer rate and the default blocking behavior (true) can be changed
 | ||||||
|  | // directly on the returned *Writer.
 | ||||||
|  | func NewWriter(w io.Writer, limit int64) *Writer { | ||||||
|  | 	return &Writer{w, New(0, 0), limit, true} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Write writes len(p) bytes from p to the underlying data stream without
 | ||||||
|  | // exceeding the current transfer rate limit. It returns (n, ErrLimit) if w is
 | ||||||
|  | // non-blocking and no additional bytes can be written at this time.
 | ||||||
|  | func (w *Writer) Write(p []byte) (n int, err error) { | ||||||
|  | 	var c int | ||||||
|  | 	for len(p) > 0 && err == nil { | ||||||
|  | 		s := p[:w.Limit(len(p), w.limit, w.block)] | ||||||
|  | 		if len(s) > 0 { | ||||||
|  | 			c, err = w.IO(w.Writer.Write(s)) | ||||||
|  | 		} else { | ||||||
|  | 			return n, ErrLimit | ||||||
|  | 		} | ||||||
|  | 		p = p[c:] | ||||||
|  | 		n += c | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetLimit changes the transfer rate limit to new bytes per second and returns
 | ||||||
|  | // the previous setting.
 | ||||||
|  | func (w *Writer) SetLimit(new int64) (old int64) { | ||||||
|  | 	old, w.limit = w.limit, new | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetBlocking changes the blocking behavior and returns the previous setting. A
 | ||||||
|  | // Write call on a non-blocking writer returns as soon as no additional bytes
 | ||||||
|  | // may be written at this time due to the rate limit.
 | ||||||
|  | func (w *Writer) SetBlocking(new bool) (old bool) { | ||||||
|  | 	old, w.block = w.block, new | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Close closes the underlying writer if it implements the io.Closer interface.
 | ||||||
|  | func (w *Writer) Close() error { | ||||||
|  | 	defer w.Done() | ||||||
|  | 	if c, ok := w.Writer.(io.Closer); ok { | ||||||
|  | 		return c.Close() | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | @ -0,0 +1,67 @@ | ||||||
|  | //
 | ||||||
|  | // Written by Maxim Khitrov (November 2012)
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | package flowrate | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"math" | ||||||
|  | 	"strconv" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // clockRate is the resolution and precision of clock().
 | ||||||
|  | const clockRate = 20 * time.Millisecond | ||||||
|  | 
 | ||||||
|  | // czero is the process start time rounded down to the nearest clockRate
 | ||||||
|  | // increment.
 | ||||||
|  | var czero = time.Duration(time.Now().UnixNano()) / clockRate * clockRate | ||||||
|  | 
 | ||||||
|  | // clock returns a low resolution timestamp relative to the process start time.
 | ||||||
|  | func clock() time.Duration { | ||||||
|  | 	return time.Duration(time.Now().UnixNano())/clockRate*clockRate - czero | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // clockToTime converts a clock() timestamp to an absolute time.Time value.
 | ||||||
|  | func clockToTime(c time.Duration) time.Time { | ||||||
|  | 	return time.Unix(0, int64(czero+c)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // clockRound returns d rounded to the nearest clockRate increment.
 | ||||||
|  | func clockRound(d time.Duration) time.Duration { | ||||||
|  | 	return (d + clockRate>>1) / clockRate * clockRate | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // round returns x rounded to the nearest int64 (non-negative values only).
 | ||||||
|  | func round(x float64) int64 { | ||||||
|  | 	if _, frac := math.Modf(x); frac >= 0.5 { | ||||||
|  | 		return int64(math.Ceil(x)) | ||||||
|  | 	} | ||||||
|  | 	return int64(math.Floor(x)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Percent represents a percentage in increments of 1/1000th of a percent.
 | ||||||
|  | type Percent uint32 | ||||||
|  | 
 | ||||||
|  | // percentOf calculates what percent of the total is x.
 | ||||||
|  | func percentOf(x, total float64) Percent { | ||||||
|  | 	if x < 0 || total <= 0 { | ||||||
|  | 		return 0 | ||||||
|  | 	} else if p := round(x / total * 1e5); p <= math.MaxUint32 { | ||||||
|  | 		return Percent(p) | ||||||
|  | 	} | ||||||
|  | 	return Percent(math.MaxUint32) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p Percent) Float() float64 { | ||||||
|  | 	return float64(p) * 1e-3 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p Percent) String() string { | ||||||
|  | 	var buf [12]byte | ||||||
|  | 	b := strconv.AppendUint(buf[:0], uint64(p)/1000, 10) | ||||||
|  | 	n := len(b) | ||||||
|  | 	b = strconv.AppendUint(b, 1000+uint64(p)%1000, 10) | ||||||
|  | 	b[n] = '.' | ||||||
|  | 	return string(append(b, '%')) | ||||||
|  | } | ||||||
|  | @ -3,7 +3,6 @@ | ||||||
| // license that can be found in the LICENSE file.
 | // license that can be found in the LICENSE file.
 | ||||||
| 
 | 
 | ||||||
| //go:build go1.7
 | //go:build go1.7
 | ||||||
| // +build go1.7
 |  | ||||||
| 
 | 
 | ||||||
| package context | package context | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,6 @@ | ||||||
| // license that can be found in the LICENSE file.
 | // license that can be found in the LICENSE file.
 | ||||||
| 
 | 
 | ||||||
| //go:build go1.9
 | //go:build go1.9
 | ||||||
| // +build go1.9
 |  | ||||||
| 
 | 
 | ||||||
| package context | package context | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,6 @@ | ||||||
| // license that can be found in the LICENSE file.
 | // license that can be found in the LICENSE file.
 | ||||||
| 
 | 
 | ||||||
| //go:build !go1.7
 | //go:build !go1.7
 | ||||||
| // +build !go1.7
 |  | ||||||
| 
 | 
 | ||||||
| package context | package context | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,6 @@ | ||||||
| // license that can be found in the LICENSE file.
 | // license that can be found in the LICENSE file.
 | ||||||
| 
 | 
 | ||||||
| //go:build !go1.9
 | //go:build !go1.9
 | ||||||
| // +build !go1.9
 |  | ||||||
| 
 | 
 | ||||||
| package context | package context | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,78 @@ | ||||||
|  | // Copyright 2012 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 atom provides integer codes (also known as atoms) for a fixed set of
 | ||||||
|  | // frequently occurring HTML strings: tag names and attribute keys such as "p"
 | ||||||
|  | // and "id".
 | ||||||
|  | //
 | ||||||
|  | // Sharing an atom's name between all elements with the same tag can result in
 | ||||||
|  | // fewer string allocations when tokenizing and parsing HTML. Integer
 | ||||||
|  | // comparisons are also generally faster than string comparisons.
 | ||||||
|  | //
 | ||||||
|  | // The value of an atom's particular code is not guaranteed to stay the same
 | ||||||
|  | // between versions of this package. Neither is any ordering guaranteed:
 | ||||||
|  | // whether atom.H1 < atom.H2 may also change. The codes are not guaranteed to
 | ||||||
|  | // be dense. The only guarantees are that e.g. looking up "div" will yield
 | ||||||
|  | // atom.Div, calling atom.Div.String will return "div", and atom.Div != 0.
 | ||||||
|  | package atom // import "golang.org/x/net/html/atom"
 | ||||||
|  | 
 | ||||||
|  | // Atom is an integer code for a string. The zero value maps to "".
 | ||||||
|  | type Atom uint32 | ||||||
|  | 
 | ||||||
|  | // String returns the atom's name.
 | ||||||
|  | func (a Atom) String() string { | ||||||
|  | 	start := uint32(a >> 8) | ||||||
|  | 	n := uint32(a & 0xff) | ||||||
|  | 	if start+n > uint32(len(atomText)) { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	return atomText[start : start+n] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a Atom) string() string { | ||||||
|  | 	return atomText[a>>8 : a>>8+a&0xff] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // fnv computes the FNV hash with an arbitrary starting value h.
 | ||||||
|  | func fnv(h uint32, s []byte) uint32 { | ||||||
|  | 	for i := range s { | ||||||
|  | 		h ^= uint32(s[i]) | ||||||
|  | 		h *= 16777619 | ||||||
|  | 	} | ||||||
|  | 	return h | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func match(s string, t []byte) bool { | ||||||
|  | 	for i, c := range t { | ||||||
|  | 		if s[i] != c { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Lookup returns the atom whose name is s. It returns zero if there is no
 | ||||||
|  | // such atom. The lookup is case sensitive.
 | ||||||
|  | func Lookup(s []byte) Atom { | ||||||
|  | 	if len(s) == 0 || len(s) > maxAtomLen { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  | 	h := fnv(hash0, s) | ||||||
|  | 	if a := table[h&uint32(len(table)-1)]; int(a&0xff) == len(s) && match(a.string(), s) { | ||||||
|  | 		return a | ||||||
|  | 	} | ||||||
|  | 	if a := table[(h>>16)&uint32(len(table)-1)]; int(a&0xff) == len(s) && match(a.string(), s) { | ||||||
|  | 		return a | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // String returns a string whose contents are equal to s. In that sense, it is
 | ||||||
|  | // equivalent to string(s) but may be more efficient.
 | ||||||
|  | func String(s []byte) string { | ||||||
|  | 	if a := Lookup(s); a != 0 { | ||||||
|  | 		return a.String() | ||||||
|  | 	} | ||||||
|  | 	return string(s) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,783 @@ | ||||||
|  | // Code generated by go generate gen.go; DO NOT EDIT.
 | ||||||
|  | 
 | ||||||
|  | //go:generate go run gen.go
 | ||||||
|  | 
 | ||||||
|  | package atom | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	A                         Atom = 0x1 | ||||||
|  | 	Abbr                      Atom = 0x4 | ||||||
|  | 	Accept                    Atom = 0x1a06 | ||||||
|  | 	AcceptCharset             Atom = 0x1a0e | ||||||
|  | 	Accesskey                 Atom = 0x2c09 | ||||||
|  | 	Acronym                   Atom = 0xaa07 | ||||||
|  | 	Action                    Atom = 0x27206 | ||||||
|  | 	Address                   Atom = 0x6f307 | ||||||
|  | 	Align                     Atom = 0xb105 | ||||||
|  | 	Allowfullscreen           Atom = 0x2080f | ||||||
|  | 	Allowpaymentrequest       Atom = 0xc113 | ||||||
|  | 	Allowusermedia            Atom = 0xdd0e | ||||||
|  | 	Alt                       Atom = 0xf303 | ||||||
|  | 	Annotation                Atom = 0x1c90a | ||||||
|  | 	AnnotationXml             Atom = 0x1c90e | ||||||
|  | 	Applet                    Atom = 0x31906 | ||||||
|  | 	Area                      Atom = 0x35604 | ||||||
|  | 	Article                   Atom = 0x3fc07 | ||||||
|  | 	As                        Atom = 0x3c02 | ||||||
|  | 	Aside                     Atom = 0x10705 | ||||||
|  | 	Async                     Atom = 0xff05 | ||||||
|  | 	Audio                     Atom = 0x11505 | ||||||
|  | 	Autocomplete              Atom = 0x2780c | ||||||
|  | 	Autofocus                 Atom = 0x12109 | ||||||
|  | 	Autoplay                  Atom = 0x13c08 | ||||||
|  | 	B                         Atom = 0x101 | ||||||
|  | 	Base                      Atom = 0x3b04 | ||||||
|  | 	Basefont                  Atom = 0x3b08 | ||||||
|  | 	Bdi                       Atom = 0xba03 | ||||||
|  | 	Bdo                       Atom = 0x14b03 | ||||||
|  | 	Bgsound                   Atom = 0x15e07 | ||||||
|  | 	Big                       Atom = 0x17003 | ||||||
|  | 	Blink                     Atom = 0x17305 | ||||||
|  | 	Blockquote                Atom = 0x1870a | ||||||
|  | 	Body                      Atom = 0x2804 | ||||||
|  | 	Br                        Atom = 0x202 | ||||||
|  | 	Button                    Atom = 0x19106 | ||||||
|  | 	Canvas                    Atom = 0x10306 | ||||||
|  | 	Caption                   Atom = 0x23107 | ||||||
|  | 	Center                    Atom = 0x22006 | ||||||
|  | 	Challenge                 Atom = 0x29b09 | ||||||
|  | 	Charset                   Atom = 0x2107 | ||||||
|  | 	Checked                   Atom = 0x47907 | ||||||
|  | 	Cite                      Atom = 0x19c04 | ||||||
|  | 	Class                     Atom = 0x56405 | ||||||
|  | 	Code                      Atom = 0x5c504 | ||||||
|  | 	Col                       Atom = 0x1ab03 | ||||||
|  | 	Colgroup                  Atom = 0x1ab08 | ||||||
|  | 	Color                     Atom = 0x1bf05 | ||||||
|  | 	Cols                      Atom = 0x1c404 | ||||||
|  | 	Colspan                   Atom = 0x1c407 | ||||||
|  | 	Command                   Atom = 0x1d707 | ||||||
|  | 	Content                   Atom = 0x58b07 | ||||||
|  | 	Contenteditable           Atom = 0x58b0f | ||||||
|  | 	Contextmenu               Atom = 0x3800b | ||||||
|  | 	Controls                  Atom = 0x1de08 | ||||||
|  | 	Coords                    Atom = 0x1ea06 | ||||||
|  | 	Crossorigin               Atom = 0x1fb0b | ||||||
|  | 	Data                      Atom = 0x4a504 | ||||||
|  | 	Datalist                  Atom = 0x4a508 | ||||||
|  | 	Datetime                  Atom = 0x2b808 | ||||||
|  | 	Dd                        Atom = 0x2d702 | ||||||
|  | 	Default                   Atom = 0x10a07 | ||||||
|  | 	Defer                     Atom = 0x5c705 | ||||||
|  | 	Del                       Atom = 0x45203 | ||||||
|  | 	Desc                      Atom = 0x56104 | ||||||
|  | 	Details                   Atom = 0x7207 | ||||||
|  | 	Dfn                       Atom = 0x8703 | ||||||
|  | 	Dialog                    Atom = 0xbb06 | ||||||
|  | 	Dir                       Atom = 0x9303 | ||||||
|  | 	Dirname                   Atom = 0x9307 | ||||||
|  | 	Disabled                  Atom = 0x16408 | ||||||
|  | 	Div                       Atom = 0x16b03 | ||||||
|  | 	Dl                        Atom = 0x5e602 | ||||||
|  | 	Download                  Atom = 0x46308 | ||||||
|  | 	Draggable                 Atom = 0x17a09 | ||||||
|  | 	Dropzone                  Atom = 0x40508 | ||||||
|  | 	Dt                        Atom = 0x64b02 | ||||||
|  | 	Em                        Atom = 0x6e02 | ||||||
|  | 	Embed                     Atom = 0x6e05 | ||||||
|  | 	Enctype                   Atom = 0x28d07 | ||||||
|  | 	Face                      Atom = 0x21e04 | ||||||
|  | 	Fieldset                  Atom = 0x22608 | ||||||
|  | 	Figcaption                Atom = 0x22e0a | ||||||
|  | 	Figure                    Atom = 0x24806 | ||||||
|  | 	Font                      Atom = 0x3f04 | ||||||
|  | 	Footer                    Atom = 0xf606 | ||||||
|  | 	For                       Atom = 0x25403 | ||||||
|  | 	ForeignObject             Atom = 0x2540d | ||||||
|  | 	Foreignobject             Atom = 0x2610d | ||||||
|  | 	Form                      Atom = 0x26e04 | ||||||
|  | 	Formaction                Atom = 0x26e0a | ||||||
|  | 	Formenctype               Atom = 0x2890b | ||||||
|  | 	Formmethod                Atom = 0x2a40a | ||||||
|  | 	Formnovalidate            Atom = 0x2ae0e | ||||||
|  | 	Formtarget                Atom = 0x2c00a | ||||||
|  | 	Frame                     Atom = 0x8b05 | ||||||
|  | 	Frameset                  Atom = 0x8b08 | ||||||
|  | 	H1                        Atom = 0x15c02 | ||||||
|  | 	H2                        Atom = 0x2de02 | ||||||
|  | 	H3                        Atom = 0x30d02 | ||||||
|  | 	H4                        Atom = 0x34502 | ||||||
|  | 	H5                        Atom = 0x34f02 | ||||||
|  | 	H6                        Atom = 0x64d02 | ||||||
|  | 	Head                      Atom = 0x33104 | ||||||
|  | 	Header                    Atom = 0x33106 | ||||||
|  | 	Headers                   Atom = 0x33107 | ||||||
|  | 	Height                    Atom = 0x5206 | ||||||
|  | 	Hgroup                    Atom = 0x2ca06 | ||||||
|  | 	Hidden                    Atom = 0x2d506 | ||||||
|  | 	High                      Atom = 0x2db04 | ||||||
|  | 	Hr                        Atom = 0x15702 | ||||||
|  | 	Href                      Atom = 0x2e004 | ||||||
|  | 	Hreflang                  Atom = 0x2e008 | ||||||
|  | 	Html                      Atom = 0x5604 | ||||||
|  | 	HttpEquiv                 Atom = 0x2e80a | ||||||
|  | 	I                         Atom = 0x601 | ||||||
|  | 	Icon                      Atom = 0x58a04 | ||||||
|  | 	Id                        Atom = 0x10902 | ||||||
|  | 	Iframe                    Atom = 0x2fc06 | ||||||
|  | 	Image                     Atom = 0x30205 | ||||||
|  | 	Img                       Atom = 0x30703 | ||||||
|  | 	Input                     Atom = 0x44b05 | ||||||
|  | 	Inputmode                 Atom = 0x44b09 | ||||||
|  | 	Ins                       Atom = 0x20403 | ||||||
|  | 	Integrity                 Atom = 0x23f09 | ||||||
|  | 	Is                        Atom = 0x16502 | ||||||
|  | 	Isindex                   Atom = 0x30f07 | ||||||
|  | 	Ismap                     Atom = 0x31605 | ||||||
|  | 	Itemid                    Atom = 0x38b06 | ||||||
|  | 	Itemprop                  Atom = 0x19d08 | ||||||
|  | 	Itemref                   Atom = 0x3cd07 | ||||||
|  | 	Itemscope                 Atom = 0x67109 | ||||||
|  | 	Itemtype                  Atom = 0x31f08 | ||||||
|  | 	Kbd                       Atom = 0xb903 | ||||||
|  | 	Keygen                    Atom = 0x3206 | ||||||
|  | 	Keytype                   Atom = 0xd607 | ||||||
|  | 	Kind                      Atom = 0x17704 | ||||||
|  | 	Label                     Atom = 0x5905 | ||||||
|  | 	Lang                      Atom = 0x2e404 | ||||||
|  | 	Legend                    Atom = 0x18106 | ||||||
|  | 	Li                        Atom = 0xb202 | ||||||
|  | 	Link                      Atom = 0x17404 | ||||||
|  | 	List                      Atom = 0x4a904 | ||||||
|  | 	Listing                   Atom = 0x4a907 | ||||||
|  | 	Loop                      Atom = 0x5d04 | ||||||
|  | 	Low                       Atom = 0xc303 | ||||||
|  | 	Main                      Atom = 0x1004 | ||||||
|  | 	Malignmark                Atom = 0xb00a | ||||||
|  | 	Manifest                  Atom = 0x6d708 | ||||||
|  | 	Map                       Atom = 0x31803 | ||||||
|  | 	Mark                      Atom = 0xb604 | ||||||
|  | 	Marquee                   Atom = 0x32707 | ||||||
|  | 	Math                      Atom = 0x32e04 | ||||||
|  | 	Max                       Atom = 0x33d03 | ||||||
|  | 	Maxlength                 Atom = 0x33d09 | ||||||
|  | 	Media                     Atom = 0xe605 | ||||||
|  | 	Mediagroup                Atom = 0xe60a | ||||||
|  | 	Menu                      Atom = 0x38704 | ||||||
|  | 	Menuitem                  Atom = 0x38708 | ||||||
|  | 	Meta                      Atom = 0x4b804 | ||||||
|  | 	Meter                     Atom = 0x9805 | ||||||
|  | 	Method                    Atom = 0x2a806 | ||||||
|  | 	Mglyph                    Atom = 0x30806 | ||||||
|  | 	Mi                        Atom = 0x34702 | ||||||
|  | 	Min                       Atom = 0x34703 | ||||||
|  | 	Minlength                 Atom = 0x34709 | ||||||
|  | 	Mn                        Atom = 0x2b102 | ||||||
|  | 	Mo                        Atom = 0xa402 | ||||||
|  | 	Ms                        Atom = 0x67402 | ||||||
|  | 	Mtext                     Atom = 0x35105 | ||||||
|  | 	Multiple                  Atom = 0x35f08 | ||||||
|  | 	Muted                     Atom = 0x36705 | ||||||
|  | 	Name                      Atom = 0x9604 | ||||||
|  | 	Nav                       Atom = 0x1303 | ||||||
|  | 	Nobr                      Atom = 0x3704 | ||||||
|  | 	Noembed                   Atom = 0x6c07 | ||||||
|  | 	Noframes                  Atom = 0x8908 | ||||||
|  | 	Nomodule                  Atom = 0xa208 | ||||||
|  | 	Nonce                     Atom = 0x1a605 | ||||||
|  | 	Noscript                  Atom = 0x21608 | ||||||
|  | 	Novalidate                Atom = 0x2b20a | ||||||
|  | 	Object                    Atom = 0x26806 | ||||||
|  | 	Ol                        Atom = 0x13702 | ||||||
|  | 	Onabort                   Atom = 0x19507 | ||||||
|  | 	Onafterprint              Atom = 0x2360c | ||||||
|  | 	Onautocomplete            Atom = 0x2760e | ||||||
|  | 	Onautocompleteerror       Atom = 0x27613 | ||||||
|  | 	Onauxclick                Atom = 0x61f0a | ||||||
|  | 	Onbeforeprint             Atom = 0x69e0d | ||||||
|  | 	Onbeforeunload            Atom = 0x6e70e | ||||||
|  | 	Onblur                    Atom = 0x56d06 | ||||||
|  | 	Oncancel                  Atom = 0x11908 | ||||||
|  | 	Oncanplay                 Atom = 0x14d09 | ||||||
|  | 	Oncanplaythrough          Atom = 0x14d10 | ||||||
|  | 	Onchange                  Atom = 0x41b08 | ||||||
|  | 	Onclick                   Atom = 0x2f507 | ||||||
|  | 	Onclose                   Atom = 0x36c07 | ||||||
|  | 	Oncontextmenu             Atom = 0x37e0d | ||||||
|  | 	Oncopy                    Atom = 0x39106 | ||||||
|  | 	Oncuechange               Atom = 0x3970b | ||||||
|  | 	Oncut                     Atom = 0x3a205 | ||||||
|  | 	Ondblclick                Atom = 0x3a70a | ||||||
|  | 	Ondrag                    Atom = 0x3b106 | ||||||
|  | 	Ondragend                 Atom = 0x3b109 | ||||||
|  | 	Ondragenter               Atom = 0x3ba0b | ||||||
|  | 	Ondragexit                Atom = 0x3c50a | ||||||
|  | 	Ondragleave               Atom = 0x3df0b | ||||||
|  | 	Ondragover                Atom = 0x3ea0a | ||||||
|  | 	Ondragstart               Atom = 0x3f40b | ||||||
|  | 	Ondrop                    Atom = 0x40306 | ||||||
|  | 	Ondurationchange          Atom = 0x41310 | ||||||
|  | 	Onemptied                 Atom = 0x40a09 | ||||||
|  | 	Onended                   Atom = 0x42307 | ||||||
|  | 	Onerror                   Atom = 0x42a07 | ||||||
|  | 	Onfocus                   Atom = 0x43107 | ||||||
|  | 	Onhashchange              Atom = 0x43d0c | ||||||
|  | 	Oninput                   Atom = 0x44907 | ||||||
|  | 	Oninvalid                 Atom = 0x45509 | ||||||
|  | 	Onkeydown                 Atom = 0x45e09 | ||||||
|  | 	Onkeypress                Atom = 0x46b0a | ||||||
|  | 	Onkeyup                   Atom = 0x48007 | ||||||
|  | 	Onlanguagechange          Atom = 0x48d10 | ||||||
|  | 	Onload                    Atom = 0x49d06 | ||||||
|  | 	Onloadeddata              Atom = 0x49d0c | ||||||
|  | 	Onloadedmetadata          Atom = 0x4b010 | ||||||
|  | 	Onloadend                 Atom = 0x4c609 | ||||||
|  | 	Onloadstart               Atom = 0x4cf0b | ||||||
|  | 	Onmessage                 Atom = 0x4da09 | ||||||
|  | 	Onmessageerror            Atom = 0x4da0e | ||||||
|  | 	Onmousedown               Atom = 0x4e80b | ||||||
|  | 	Onmouseenter              Atom = 0x4f30c | ||||||
|  | 	Onmouseleave              Atom = 0x4ff0c | ||||||
|  | 	Onmousemove               Atom = 0x50b0b | ||||||
|  | 	Onmouseout                Atom = 0x5160a | ||||||
|  | 	Onmouseover               Atom = 0x5230b | ||||||
|  | 	Onmouseup                 Atom = 0x52e09 | ||||||
|  | 	Onmousewheel              Atom = 0x53c0c | ||||||
|  | 	Onoffline                 Atom = 0x54809 | ||||||
|  | 	Ononline                  Atom = 0x55108 | ||||||
|  | 	Onpagehide                Atom = 0x5590a | ||||||
|  | 	Onpageshow                Atom = 0x5730a | ||||||
|  | 	Onpaste                   Atom = 0x57f07 | ||||||
|  | 	Onpause                   Atom = 0x59a07 | ||||||
|  | 	Onplay                    Atom = 0x5a406 | ||||||
|  | 	Onplaying                 Atom = 0x5a409 | ||||||
|  | 	Onpopstate                Atom = 0x5ad0a | ||||||
|  | 	Onprogress                Atom = 0x5b70a | ||||||
|  | 	Onratechange              Atom = 0x5cc0c | ||||||
|  | 	Onrejectionhandled        Atom = 0x5d812 | ||||||
|  | 	Onreset                   Atom = 0x5ea07 | ||||||
|  | 	Onresize                  Atom = 0x5f108 | ||||||
|  | 	Onscroll                  Atom = 0x60008 | ||||||
|  | 	Onsecuritypolicyviolation Atom = 0x60819 | ||||||
|  | 	Onseeked                  Atom = 0x62908 | ||||||
|  | 	Onseeking                 Atom = 0x63109 | ||||||
|  | 	Onselect                  Atom = 0x63a08 | ||||||
|  | 	Onshow                    Atom = 0x64406 | ||||||
|  | 	Onsort                    Atom = 0x64f06 | ||||||
|  | 	Onstalled                 Atom = 0x65909 | ||||||
|  | 	Onstorage                 Atom = 0x66209 | ||||||
|  | 	Onsubmit                  Atom = 0x66b08 | ||||||
|  | 	Onsuspend                 Atom = 0x67b09 | ||||||
|  | 	Ontimeupdate              Atom = 0x400c | ||||||
|  | 	Ontoggle                  Atom = 0x68408 | ||||||
|  | 	Onunhandledrejection      Atom = 0x68c14 | ||||||
|  | 	Onunload                  Atom = 0x6ab08 | ||||||
|  | 	Onvolumechange            Atom = 0x6b30e | ||||||
|  | 	Onwaiting                 Atom = 0x6c109 | ||||||
|  | 	Onwheel                   Atom = 0x6ca07 | ||||||
|  | 	Open                      Atom = 0x1a304 | ||||||
|  | 	Optgroup                  Atom = 0x5f08 | ||||||
|  | 	Optimum                   Atom = 0x6d107 | ||||||
|  | 	Option                    Atom = 0x6e306 | ||||||
|  | 	Output                    Atom = 0x51d06 | ||||||
|  | 	P                         Atom = 0xc01 | ||||||
|  | 	Param                     Atom = 0xc05 | ||||||
|  | 	Pattern                   Atom = 0x6607 | ||||||
|  | 	Picture                   Atom = 0x7b07 | ||||||
|  | 	Ping                      Atom = 0xef04 | ||||||
|  | 	Placeholder               Atom = 0x1310b | ||||||
|  | 	Plaintext                 Atom = 0x1b209 | ||||||
|  | 	Playsinline               Atom = 0x1400b | ||||||
|  | 	Poster                    Atom = 0x2cf06 | ||||||
|  | 	Pre                       Atom = 0x47003 | ||||||
|  | 	Preload                   Atom = 0x48607 | ||||||
|  | 	Progress                  Atom = 0x5b908 | ||||||
|  | 	Prompt                    Atom = 0x53606 | ||||||
|  | 	Public                    Atom = 0x58606 | ||||||
|  | 	Q                         Atom = 0xcf01 | ||||||
|  | 	Radiogroup                Atom = 0x30a | ||||||
|  | 	Rb                        Atom = 0x3a02 | ||||||
|  | 	Readonly                  Atom = 0x35708 | ||||||
|  | 	Referrerpolicy            Atom = 0x3d10e | ||||||
|  | 	Rel                       Atom = 0x48703 | ||||||
|  | 	Required                  Atom = 0x24c08 | ||||||
|  | 	Reversed                  Atom = 0x8008 | ||||||
|  | 	Rows                      Atom = 0x9c04 | ||||||
|  | 	Rowspan                   Atom = 0x9c07 | ||||||
|  | 	Rp                        Atom = 0x23c02 | ||||||
|  | 	Rt                        Atom = 0x19a02 | ||||||
|  | 	Rtc                       Atom = 0x19a03 | ||||||
|  | 	Ruby                      Atom = 0xfb04 | ||||||
|  | 	S                         Atom = 0x2501 | ||||||
|  | 	Samp                      Atom = 0x7804 | ||||||
|  | 	Sandbox                   Atom = 0x12907 | ||||||
|  | 	Scope                     Atom = 0x67505 | ||||||
|  | 	Scoped                    Atom = 0x67506 | ||||||
|  | 	Script                    Atom = 0x21806 | ||||||
|  | 	Seamless                  Atom = 0x37108 | ||||||
|  | 	Section                   Atom = 0x56807 | ||||||
|  | 	Select                    Atom = 0x63c06 | ||||||
|  | 	Selected                  Atom = 0x63c08 | ||||||
|  | 	Shape                     Atom = 0x1e505 | ||||||
|  | 	Size                      Atom = 0x5f504 | ||||||
|  | 	Sizes                     Atom = 0x5f505 | ||||||
|  | 	Slot                      Atom = 0x1ef04 | ||||||
|  | 	Small                     Atom = 0x20605 | ||||||
|  | 	Sortable                  Atom = 0x65108 | ||||||
|  | 	Sorted                    Atom = 0x33706 | ||||||
|  | 	Source                    Atom = 0x37806 | ||||||
|  | 	Spacer                    Atom = 0x43706 | ||||||
|  | 	Span                      Atom = 0x9f04 | ||||||
|  | 	Spellcheck                Atom = 0x4740a | ||||||
|  | 	Src                       Atom = 0x5c003 | ||||||
|  | 	Srcdoc                    Atom = 0x5c006 | ||||||
|  | 	Srclang                   Atom = 0x5f907 | ||||||
|  | 	Srcset                    Atom = 0x6f906 | ||||||
|  | 	Start                     Atom = 0x3fa05 | ||||||
|  | 	Step                      Atom = 0x58304 | ||||||
|  | 	Strike                    Atom = 0xd206 | ||||||
|  | 	Strong                    Atom = 0x6dd06 | ||||||
|  | 	Style                     Atom = 0x6ff05 | ||||||
|  | 	Sub                       Atom = 0x66d03 | ||||||
|  | 	Summary                   Atom = 0x70407 | ||||||
|  | 	Sup                       Atom = 0x70b03 | ||||||
|  | 	Svg                       Atom = 0x70e03 | ||||||
|  | 	System                    Atom = 0x71106 | ||||||
|  | 	Tabindex                  Atom = 0x4be08 | ||||||
|  | 	Table                     Atom = 0x59505 | ||||||
|  | 	Target                    Atom = 0x2c406 | ||||||
|  | 	Tbody                     Atom = 0x2705 | ||||||
|  | 	Td                        Atom = 0x9202 | ||||||
|  | 	Template                  Atom = 0x71408 | ||||||
|  | 	Textarea                  Atom = 0x35208 | ||||||
|  | 	Tfoot                     Atom = 0xf505 | ||||||
|  | 	Th                        Atom = 0x15602 | ||||||
|  | 	Thead                     Atom = 0x33005 | ||||||
|  | 	Time                      Atom = 0x4204 | ||||||
|  | 	Title                     Atom = 0x11005 | ||||||
|  | 	Tr                        Atom = 0xcc02 | ||||||
|  | 	Track                     Atom = 0x1ba05 | ||||||
|  | 	Translate                 Atom = 0x1f209 | ||||||
|  | 	Tt                        Atom = 0x6802 | ||||||
|  | 	Type                      Atom = 0xd904 | ||||||
|  | 	Typemustmatch             Atom = 0x2900d | ||||||
|  | 	U                         Atom = 0xb01 | ||||||
|  | 	Ul                        Atom = 0xa702 | ||||||
|  | 	Updateviacache            Atom = 0x460e | ||||||
|  | 	Usemap                    Atom = 0x59e06 | ||||||
|  | 	Value                     Atom = 0x1505 | ||||||
|  | 	Var                       Atom = 0x16d03 | ||||||
|  | 	Video                     Atom = 0x2f105 | ||||||
|  | 	Wbr                       Atom = 0x57c03 | ||||||
|  | 	Width                     Atom = 0x64905 | ||||||
|  | 	Workertype                Atom = 0x71c0a | ||||||
|  | 	Wrap                      Atom = 0x72604 | ||||||
|  | 	Xmp                       Atom = 0x12f03 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const hash0 = 0x81cdf10e | ||||||
|  | 
 | ||||||
|  | const maxAtomLen = 25 | ||||||
|  | 
 | ||||||
|  | var table = [1 << 9]Atom{ | ||||||
|  | 	0x1:   0xe60a,  // mediagroup
 | ||||||
|  | 	0x2:   0x2e404, // lang
 | ||||||
|  | 	0x4:   0x2c09,  // accesskey
 | ||||||
|  | 	0x5:   0x8b08,  // frameset
 | ||||||
|  | 	0x7:   0x63a08, // onselect
 | ||||||
|  | 	0x8:   0x71106, // system
 | ||||||
|  | 	0xa:   0x64905, // width
 | ||||||
|  | 	0xc:   0x2890b, // formenctype
 | ||||||
|  | 	0xd:   0x13702, // ol
 | ||||||
|  | 	0xe:   0x3970b, // oncuechange
 | ||||||
|  | 	0x10:  0x14b03, // bdo
 | ||||||
|  | 	0x11:  0x11505, // audio
 | ||||||
|  | 	0x12:  0x17a09, // draggable
 | ||||||
|  | 	0x14:  0x2f105, // video
 | ||||||
|  | 	0x15:  0x2b102, // mn
 | ||||||
|  | 	0x16:  0x38704, // menu
 | ||||||
|  | 	0x17:  0x2cf06, // poster
 | ||||||
|  | 	0x19:  0xf606,  // footer
 | ||||||
|  | 	0x1a:  0x2a806, // method
 | ||||||
|  | 	0x1b:  0x2b808, // datetime
 | ||||||
|  | 	0x1c:  0x19507, // onabort
 | ||||||
|  | 	0x1d:  0x460e,  // updateviacache
 | ||||||
|  | 	0x1e:  0xff05,  // async
 | ||||||
|  | 	0x1f:  0x49d06, // onload
 | ||||||
|  | 	0x21:  0x11908, // oncancel
 | ||||||
|  | 	0x22:  0x62908, // onseeked
 | ||||||
|  | 	0x23:  0x30205, // image
 | ||||||
|  | 	0x24:  0x5d812, // onrejectionhandled
 | ||||||
|  | 	0x26:  0x17404, // link
 | ||||||
|  | 	0x27:  0x51d06, // output
 | ||||||
|  | 	0x28:  0x33104, // head
 | ||||||
|  | 	0x29:  0x4ff0c, // onmouseleave
 | ||||||
|  | 	0x2a:  0x57f07, // onpaste
 | ||||||
|  | 	0x2b:  0x5a409, // onplaying
 | ||||||
|  | 	0x2c:  0x1c407, // colspan
 | ||||||
|  | 	0x2f:  0x1bf05, // color
 | ||||||
|  | 	0x30:  0x5f504, // size
 | ||||||
|  | 	0x31:  0x2e80a, // http-equiv
 | ||||||
|  | 	0x33:  0x601,   // i
 | ||||||
|  | 	0x34:  0x5590a, // onpagehide
 | ||||||
|  | 	0x35:  0x68c14, // onunhandledrejection
 | ||||||
|  | 	0x37:  0x42a07, // onerror
 | ||||||
|  | 	0x3a:  0x3b08,  // basefont
 | ||||||
|  | 	0x3f:  0x1303,  // nav
 | ||||||
|  | 	0x40:  0x17704, // kind
 | ||||||
|  | 	0x41:  0x35708, // readonly
 | ||||||
|  | 	0x42:  0x30806, // mglyph
 | ||||||
|  | 	0x44:  0xb202,  // li
 | ||||||
|  | 	0x46:  0x2d506, // hidden
 | ||||||
|  | 	0x47:  0x70e03, // svg
 | ||||||
|  | 	0x48:  0x58304, // step
 | ||||||
|  | 	0x49:  0x23f09, // integrity
 | ||||||
|  | 	0x4a:  0x58606, // public
 | ||||||
|  | 	0x4c:  0x1ab03, // col
 | ||||||
|  | 	0x4d:  0x1870a, // blockquote
 | ||||||
|  | 	0x4e:  0x34f02, // h5
 | ||||||
|  | 	0x50:  0x5b908, // progress
 | ||||||
|  | 	0x51:  0x5f505, // sizes
 | ||||||
|  | 	0x52:  0x34502, // h4
 | ||||||
|  | 	0x56:  0x33005, // thead
 | ||||||
|  | 	0x57:  0xd607,  // keytype
 | ||||||
|  | 	0x58:  0x5b70a, // onprogress
 | ||||||
|  | 	0x59:  0x44b09, // inputmode
 | ||||||
|  | 	0x5a:  0x3b109, // ondragend
 | ||||||
|  | 	0x5d:  0x3a205, // oncut
 | ||||||
|  | 	0x5e:  0x43706, // spacer
 | ||||||
|  | 	0x5f:  0x1ab08, // colgroup
 | ||||||
|  | 	0x62:  0x16502, // is
 | ||||||
|  | 	0x65:  0x3c02,  // as
 | ||||||
|  | 	0x66:  0x54809, // onoffline
 | ||||||
|  | 	0x67:  0x33706, // sorted
 | ||||||
|  | 	0x69:  0x48d10, // onlanguagechange
 | ||||||
|  | 	0x6c:  0x43d0c, // onhashchange
 | ||||||
|  | 	0x6d:  0x9604,  // name
 | ||||||
|  | 	0x6e:  0xf505,  // tfoot
 | ||||||
|  | 	0x6f:  0x56104, // desc
 | ||||||
|  | 	0x70:  0x33d03, // max
 | ||||||
|  | 	0x72:  0x1ea06, // coords
 | ||||||
|  | 	0x73:  0x30d02, // h3
 | ||||||
|  | 	0x74:  0x6e70e, // onbeforeunload
 | ||||||
|  | 	0x75:  0x9c04,  // rows
 | ||||||
|  | 	0x76:  0x63c06, // select
 | ||||||
|  | 	0x77:  0x9805,  // meter
 | ||||||
|  | 	0x78:  0x38b06, // itemid
 | ||||||
|  | 	0x79:  0x53c0c, // onmousewheel
 | ||||||
|  | 	0x7a:  0x5c006, // srcdoc
 | ||||||
|  | 	0x7d:  0x1ba05, // track
 | ||||||
|  | 	0x7f:  0x31f08, // itemtype
 | ||||||
|  | 	0x82:  0xa402,  // mo
 | ||||||
|  | 	0x83:  0x41b08, // onchange
 | ||||||
|  | 	0x84:  0x33107, // headers
 | ||||||
|  | 	0x85:  0x5cc0c, // onratechange
 | ||||||
|  | 	0x86:  0x60819, // onsecuritypolicyviolation
 | ||||||
|  | 	0x88:  0x4a508, // datalist
 | ||||||
|  | 	0x89:  0x4e80b, // onmousedown
 | ||||||
|  | 	0x8a:  0x1ef04, // slot
 | ||||||
|  | 	0x8b:  0x4b010, // onloadedmetadata
 | ||||||
|  | 	0x8c:  0x1a06,  // accept
 | ||||||
|  | 	0x8d:  0x26806, // object
 | ||||||
|  | 	0x91:  0x6b30e, // onvolumechange
 | ||||||
|  | 	0x92:  0x2107,  // charset
 | ||||||
|  | 	0x93:  0x27613, // onautocompleteerror
 | ||||||
|  | 	0x94:  0xc113,  // allowpaymentrequest
 | ||||||
|  | 	0x95:  0x2804,  // body
 | ||||||
|  | 	0x96:  0x10a07, // default
 | ||||||
|  | 	0x97:  0x63c08, // selected
 | ||||||
|  | 	0x98:  0x21e04, // face
 | ||||||
|  | 	0x99:  0x1e505, // shape
 | ||||||
|  | 	0x9b:  0x68408, // ontoggle
 | ||||||
|  | 	0x9e:  0x64b02, // dt
 | ||||||
|  | 	0x9f:  0xb604,  // mark
 | ||||||
|  | 	0xa1:  0xb01,   // u
 | ||||||
|  | 	0xa4:  0x6ab08, // onunload
 | ||||||
|  | 	0xa5:  0x5d04,  // loop
 | ||||||
|  | 	0xa6:  0x16408, // disabled
 | ||||||
|  | 	0xaa:  0x42307, // onended
 | ||||||
|  | 	0xab:  0xb00a,  // malignmark
 | ||||||
|  | 	0xad:  0x67b09, // onsuspend
 | ||||||
|  | 	0xae:  0x35105, // mtext
 | ||||||
|  | 	0xaf:  0x64f06, // onsort
 | ||||||
|  | 	0xb0:  0x19d08, // itemprop
 | ||||||
|  | 	0xb3:  0x67109, // itemscope
 | ||||||
|  | 	0xb4:  0x17305, // blink
 | ||||||
|  | 	0xb6:  0x3b106, // ondrag
 | ||||||
|  | 	0xb7:  0xa702,  // ul
 | ||||||
|  | 	0xb8:  0x26e04, // form
 | ||||||
|  | 	0xb9:  0x12907, // sandbox
 | ||||||
|  | 	0xba:  0x8b05,  // frame
 | ||||||
|  | 	0xbb:  0x1505,  // value
 | ||||||
|  | 	0xbc:  0x66209, // onstorage
 | ||||||
|  | 	0xbf:  0xaa07,  // acronym
 | ||||||
|  | 	0xc0:  0x19a02, // rt
 | ||||||
|  | 	0xc2:  0x202,   // br
 | ||||||
|  | 	0xc3:  0x22608, // fieldset
 | ||||||
|  | 	0xc4:  0x2900d, // typemustmatch
 | ||||||
|  | 	0xc5:  0xa208,  // nomodule
 | ||||||
|  | 	0xc6:  0x6c07,  // noembed
 | ||||||
|  | 	0xc7:  0x69e0d, // onbeforeprint
 | ||||||
|  | 	0xc8:  0x19106, // button
 | ||||||
|  | 	0xc9:  0x2f507, // onclick
 | ||||||
|  | 	0xca:  0x70407, // summary
 | ||||||
|  | 	0xcd:  0xfb04,  // ruby
 | ||||||
|  | 	0xce:  0x56405, // class
 | ||||||
|  | 	0xcf:  0x3f40b, // ondragstart
 | ||||||
|  | 	0xd0:  0x23107, // caption
 | ||||||
|  | 	0xd4:  0xdd0e,  // allowusermedia
 | ||||||
|  | 	0xd5:  0x4cf0b, // onloadstart
 | ||||||
|  | 	0xd9:  0x16b03, // div
 | ||||||
|  | 	0xda:  0x4a904, // list
 | ||||||
|  | 	0xdb:  0x32e04, // math
 | ||||||
|  | 	0xdc:  0x44b05, // input
 | ||||||
|  | 	0xdf:  0x3ea0a, // ondragover
 | ||||||
|  | 	0xe0:  0x2de02, // h2
 | ||||||
|  | 	0xe2:  0x1b209, // plaintext
 | ||||||
|  | 	0xe4:  0x4f30c, // onmouseenter
 | ||||||
|  | 	0xe7:  0x47907, // checked
 | ||||||
|  | 	0xe8:  0x47003, // pre
 | ||||||
|  | 	0xea:  0x35f08, // multiple
 | ||||||
|  | 	0xeb:  0xba03,  // bdi
 | ||||||
|  | 	0xec:  0x33d09, // maxlength
 | ||||||
|  | 	0xed:  0xcf01,  // q
 | ||||||
|  | 	0xee:  0x61f0a, // onauxclick
 | ||||||
|  | 	0xf0:  0x57c03, // wbr
 | ||||||
|  | 	0xf2:  0x3b04,  // base
 | ||||||
|  | 	0xf3:  0x6e306, // option
 | ||||||
|  | 	0xf5:  0x41310, // ondurationchange
 | ||||||
|  | 	0xf7:  0x8908,  // noframes
 | ||||||
|  | 	0xf9:  0x40508, // dropzone
 | ||||||
|  | 	0xfb:  0x67505, // scope
 | ||||||
|  | 	0xfc:  0x8008,  // reversed
 | ||||||
|  | 	0xfd:  0x3ba0b, // ondragenter
 | ||||||
|  | 	0xfe:  0x3fa05, // start
 | ||||||
|  | 	0xff:  0x12f03, // xmp
 | ||||||
|  | 	0x100: 0x5f907, // srclang
 | ||||||
|  | 	0x101: 0x30703, // img
 | ||||||
|  | 	0x104: 0x101,   // b
 | ||||||
|  | 	0x105: 0x25403, // for
 | ||||||
|  | 	0x106: 0x10705, // aside
 | ||||||
|  | 	0x107: 0x44907, // oninput
 | ||||||
|  | 	0x108: 0x35604, // area
 | ||||||
|  | 	0x109: 0x2a40a, // formmethod
 | ||||||
|  | 	0x10a: 0x72604, // wrap
 | ||||||
|  | 	0x10c: 0x23c02, // rp
 | ||||||
|  | 	0x10d: 0x46b0a, // onkeypress
 | ||||||
|  | 	0x10e: 0x6802,  // tt
 | ||||||
|  | 	0x110: 0x34702, // mi
 | ||||||
|  | 	0x111: 0x36705, // muted
 | ||||||
|  | 	0x112: 0xf303,  // alt
 | ||||||
|  | 	0x113: 0x5c504, // code
 | ||||||
|  | 	0x114: 0x6e02,  // em
 | ||||||
|  | 	0x115: 0x3c50a, // ondragexit
 | ||||||
|  | 	0x117: 0x9f04,  // span
 | ||||||
|  | 	0x119: 0x6d708, // manifest
 | ||||||
|  | 	0x11a: 0x38708, // menuitem
 | ||||||
|  | 	0x11b: 0x58b07, // content
 | ||||||
|  | 	0x11d: 0x6c109, // onwaiting
 | ||||||
|  | 	0x11f: 0x4c609, // onloadend
 | ||||||
|  | 	0x121: 0x37e0d, // oncontextmenu
 | ||||||
|  | 	0x123: 0x56d06, // onblur
 | ||||||
|  | 	0x124: 0x3fc07, // article
 | ||||||
|  | 	0x125: 0x9303,  // dir
 | ||||||
|  | 	0x126: 0xef04,  // ping
 | ||||||
|  | 	0x127: 0x24c08, // required
 | ||||||
|  | 	0x128: 0x45509, // oninvalid
 | ||||||
|  | 	0x129: 0xb105,  // align
 | ||||||
|  | 	0x12b: 0x58a04, // icon
 | ||||||
|  | 	0x12c: 0x64d02, // h6
 | ||||||
|  | 	0x12d: 0x1c404, // cols
 | ||||||
|  | 	0x12e: 0x22e0a, // figcaption
 | ||||||
|  | 	0x12f: 0x45e09, // onkeydown
 | ||||||
|  | 	0x130: 0x66b08, // onsubmit
 | ||||||
|  | 	0x131: 0x14d09, // oncanplay
 | ||||||
|  | 	0x132: 0x70b03, // sup
 | ||||||
|  | 	0x133: 0xc01,   // p
 | ||||||
|  | 	0x135: 0x40a09, // onemptied
 | ||||||
|  | 	0x136: 0x39106, // oncopy
 | ||||||
|  | 	0x137: 0x19c04, // cite
 | ||||||
|  | 	0x138: 0x3a70a, // ondblclick
 | ||||||
|  | 	0x13a: 0x50b0b, // onmousemove
 | ||||||
|  | 	0x13c: 0x66d03, // sub
 | ||||||
|  | 	0x13d: 0x48703, // rel
 | ||||||
|  | 	0x13e: 0x5f08,  // optgroup
 | ||||||
|  | 	0x142: 0x9c07,  // rowspan
 | ||||||
|  | 	0x143: 0x37806, // source
 | ||||||
|  | 	0x144: 0x21608, // noscript
 | ||||||
|  | 	0x145: 0x1a304, // open
 | ||||||
|  | 	0x146: 0x20403, // ins
 | ||||||
|  | 	0x147: 0x2540d, // foreignObject
 | ||||||
|  | 	0x148: 0x5ad0a, // onpopstate
 | ||||||
|  | 	0x14a: 0x28d07, // enctype
 | ||||||
|  | 	0x14b: 0x2760e, // onautocomplete
 | ||||||
|  | 	0x14c: 0x35208, // textarea
 | ||||||
|  | 	0x14e: 0x2780c, // autocomplete
 | ||||||
|  | 	0x14f: 0x15702, // hr
 | ||||||
|  | 	0x150: 0x1de08, // controls
 | ||||||
|  | 	0x151: 0x10902, // id
 | ||||||
|  | 	0x153: 0x2360c, // onafterprint
 | ||||||
|  | 	0x155: 0x2610d, // foreignobject
 | ||||||
|  | 	0x156: 0x32707, // marquee
 | ||||||
|  | 	0x157: 0x59a07, // onpause
 | ||||||
|  | 	0x158: 0x5e602, // dl
 | ||||||
|  | 	0x159: 0x5206,  // height
 | ||||||
|  | 	0x15a: 0x34703, // min
 | ||||||
|  | 	0x15b: 0x9307,  // dirname
 | ||||||
|  | 	0x15c: 0x1f209, // translate
 | ||||||
|  | 	0x15d: 0x5604,  // html
 | ||||||
|  | 	0x15e: 0x34709, // minlength
 | ||||||
|  | 	0x15f: 0x48607, // preload
 | ||||||
|  | 	0x160: 0x71408, // template
 | ||||||
|  | 	0x161: 0x3df0b, // ondragleave
 | ||||||
|  | 	0x162: 0x3a02,  // rb
 | ||||||
|  | 	0x164: 0x5c003, // src
 | ||||||
|  | 	0x165: 0x6dd06, // strong
 | ||||||
|  | 	0x167: 0x7804,  // samp
 | ||||||
|  | 	0x168: 0x6f307, // address
 | ||||||
|  | 	0x169: 0x55108, // ononline
 | ||||||
|  | 	0x16b: 0x1310b, // placeholder
 | ||||||
|  | 	0x16c: 0x2c406, // target
 | ||||||
|  | 	0x16d: 0x20605, // small
 | ||||||
|  | 	0x16e: 0x6ca07, // onwheel
 | ||||||
|  | 	0x16f: 0x1c90a, // annotation
 | ||||||
|  | 	0x170: 0x4740a, // spellcheck
 | ||||||
|  | 	0x171: 0x7207,  // details
 | ||||||
|  | 	0x172: 0x10306, // canvas
 | ||||||
|  | 	0x173: 0x12109, // autofocus
 | ||||||
|  | 	0x174: 0xc05,   // param
 | ||||||
|  | 	0x176: 0x46308, // download
 | ||||||
|  | 	0x177: 0x45203, // del
 | ||||||
|  | 	0x178: 0x36c07, // onclose
 | ||||||
|  | 	0x179: 0xb903,  // kbd
 | ||||||
|  | 	0x17a: 0x31906, // applet
 | ||||||
|  | 	0x17b: 0x2e004, // href
 | ||||||
|  | 	0x17c: 0x5f108, // onresize
 | ||||||
|  | 	0x17e: 0x49d0c, // onloadeddata
 | ||||||
|  | 	0x180: 0xcc02,  // tr
 | ||||||
|  | 	0x181: 0x2c00a, // formtarget
 | ||||||
|  | 	0x182: 0x11005, // title
 | ||||||
|  | 	0x183: 0x6ff05, // style
 | ||||||
|  | 	0x184: 0xd206,  // strike
 | ||||||
|  | 	0x185: 0x59e06, // usemap
 | ||||||
|  | 	0x186: 0x2fc06, // iframe
 | ||||||
|  | 	0x187: 0x1004,  // main
 | ||||||
|  | 	0x189: 0x7b07,  // picture
 | ||||||
|  | 	0x18c: 0x31605, // ismap
 | ||||||
|  | 	0x18e: 0x4a504, // data
 | ||||||
|  | 	0x18f: 0x5905,  // label
 | ||||||
|  | 	0x191: 0x3d10e, // referrerpolicy
 | ||||||
|  | 	0x192: 0x15602, // th
 | ||||||
|  | 	0x194: 0x53606, // prompt
 | ||||||
|  | 	0x195: 0x56807, // section
 | ||||||
|  | 	0x197: 0x6d107, // optimum
 | ||||||
|  | 	0x198: 0x2db04, // high
 | ||||||
|  | 	0x199: 0x15c02, // h1
 | ||||||
|  | 	0x19a: 0x65909, // onstalled
 | ||||||
|  | 	0x19b: 0x16d03, // var
 | ||||||
|  | 	0x19c: 0x4204,  // time
 | ||||||
|  | 	0x19e: 0x67402, // ms
 | ||||||
|  | 	0x19f: 0x33106, // header
 | ||||||
|  | 	0x1a0: 0x4da09, // onmessage
 | ||||||
|  | 	0x1a1: 0x1a605, // nonce
 | ||||||
|  | 	0x1a2: 0x26e0a, // formaction
 | ||||||
|  | 	0x1a3: 0x22006, // center
 | ||||||
|  | 	0x1a4: 0x3704,  // nobr
 | ||||||
|  | 	0x1a5: 0x59505, // table
 | ||||||
|  | 	0x1a6: 0x4a907, // listing
 | ||||||
|  | 	0x1a7: 0x18106, // legend
 | ||||||
|  | 	0x1a9: 0x29b09, // challenge
 | ||||||
|  | 	0x1aa: 0x24806, // figure
 | ||||||
|  | 	0x1ab: 0xe605,  // media
 | ||||||
|  | 	0x1ae: 0xd904,  // type
 | ||||||
|  | 	0x1af: 0x3f04,  // font
 | ||||||
|  | 	0x1b0: 0x4da0e, // onmessageerror
 | ||||||
|  | 	0x1b1: 0x37108, // seamless
 | ||||||
|  | 	0x1b2: 0x8703,  // dfn
 | ||||||
|  | 	0x1b3: 0x5c705, // defer
 | ||||||
|  | 	0x1b4: 0xc303,  // low
 | ||||||
|  | 	0x1b5: 0x19a03, // rtc
 | ||||||
|  | 	0x1b6: 0x5230b, // onmouseover
 | ||||||
|  | 	0x1b7: 0x2b20a, // novalidate
 | ||||||
|  | 	0x1b8: 0x71c0a, // workertype
 | ||||||
|  | 	0x1ba: 0x3cd07, // itemref
 | ||||||
|  | 	0x1bd: 0x1,     // a
 | ||||||
|  | 	0x1be: 0x31803, // map
 | ||||||
|  | 	0x1bf: 0x400c,  // ontimeupdate
 | ||||||
|  | 	0x1c0: 0x15e07, // bgsound
 | ||||||
|  | 	0x1c1: 0x3206,  // keygen
 | ||||||
|  | 	0x1c2: 0x2705,  // tbody
 | ||||||
|  | 	0x1c5: 0x64406, // onshow
 | ||||||
|  | 	0x1c7: 0x2501,  // s
 | ||||||
|  | 	0x1c8: 0x6607,  // pattern
 | ||||||
|  | 	0x1cc: 0x14d10, // oncanplaythrough
 | ||||||
|  | 	0x1ce: 0x2d702, // dd
 | ||||||
|  | 	0x1cf: 0x6f906, // srcset
 | ||||||
|  | 	0x1d0: 0x17003, // big
 | ||||||
|  | 	0x1d2: 0x65108, // sortable
 | ||||||
|  | 	0x1d3: 0x48007, // onkeyup
 | ||||||
|  | 	0x1d5: 0x5a406, // onplay
 | ||||||
|  | 	0x1d7: 0x4b804, // meta
 | ||||||
|  | 	0x1d8: 0x40306, // ondrop
 | ||||||
|  | 	0x1da: 0x60008, // onscroll
 | ||||||
|  | 	0x1db: 0x1fb0b, // crossorigin
 | ||||||
|  | 	0x1dc: 0x5730a, // onpageshow
 | ||||||
|  | 	0x1dd: 0x4,     // abbr
 | ||||||
|  | 	0x1de: 0x9202,  // td
 | ||||||
|  | 	0x1df: 0x58b0f, // contenteditable
 | ||||||
|  | 	0x1e0: 0x27206, // action
 | ||||||
|  | 	0x1e1: 0x1400b, // playsinline
 | ||||||
|  | 	0x1e2: 0x43107, // onfocus
 | ||||||
|  | 	0x1e3: 0x2e008, // hreflang
 | ||||||
|  | 	0x1e5: 0x5160a, // onmouseout
 | ||||||
|  | 	0x1e6: 0x5ea07, // onreset
 | ||||||
|  | 	0x1e7: 0x13c08, // autoplay
 | ||||||
|  | 	0x1e8: 0x63109, // onseeking
 | ||||||
|  | 	0x1ea: 0x67506, // scoped
 | ||||||
|  | 	0x1ec: 0x30a,   // radiogroup
 | ||||||
|  | 	0x1ee: 0x3800b, // contextmenu
 | ||||||
|  | 	0x1ef: 0x52e09, // onmouseup
 | ||||||
|  | 	0x1f1: 0x2ca06, // hgroup
 | ||||||
|  | 	0x1f2: 0x2080f, // allowfullscreen
 | ||||||
|  | 	0x1f3: 0x4be08, // tabindex
 | ||||||
|  | 	0x1f6: 0x30f07, // isindex
 | ||||||
|  | 	0x1f7: 0x1a0e,  // accept-charset
 | ||||||
|  | 	0x1f8: 0x2ae0e, // formnovalidate
 | ||||||
|  | 	0x1fb: 0x1c90e, // annotation-xml
 | ||||||
|  | 	0x1fc: 0x6e05,  // embed
 | ||||||
|  | 	0x1fd: 0x21806, // script
 | ||||||
|  | 	0x1fe: 0xbb06,  // dialog
 | ||||||
|  | 	0x1ff: 0x1d707, // command
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const atomText = "abbradiogrouparamainavalueaccept-charsetbodyaccesskeygenobrb" + | ||||||
|  | 	"asefontimeupdateviacacheightmlabelooptgroupatternoembedetail" + | ||||||
|  | 	"sampictureversedfnoframesetdirnameterowspanomoduleacronymali" + | ||||||
|  | 	"gnmarkbdialogallowpaymentrequestrikeytypeallowusermediagroup" + | ||||||
|  | 	"ingaltfooterubyasyncanvasidefaultitleaudioncancelautofocusan" + | ||||||
|  | 	"dboxmplaceholderautoplaysinlinebdoncanplaythrough1bgsoundisa" + | ||||||
|  | 	"bledivarbigblinkindraggablegendblockquotebuttonabortcitempro" + | ||||||
|  | 	"penoncecolgrouplaintextrackcolorcolspannotation-xmlcommandco" + | ||||||
|  | 	"ntrolshapecoordslotranslatecrossoriginsmallowfullscreenoscri" + | ||||||
|  | 	"ptfacenterfieldsetfigcaptionafterprintegrityfigurequiredfore" + | ||||||
|  | 	"ignObjectforeignobjectformactionautocompleteerrorformenctype" + | ||||||
|  | 	"mustmatchallengeformmethodformnovalidatetimeformtargethgroup" + | ||||||
|  | 	"osterhiddenhigh2hreflanghttp-equivideonclickiframeimageimgly" + | ||||||
|  | 	"ph3isindexismappletitemtypemarqueematheadersortedmaxlength4m" + | ||||||
|  | 	"inlength5mtextareadonlymultiplemutedoncloseamlessourceoncont" + | ||||||
|  | 	"extmenuitemidoncopyoncuechangeoncutondblclickondragendondrag" + | ||||||
|  | 	"enterondragexitemreferrerpolicyondragleaveondragoverondragst" + | ||||||
|  | 	"articleondropzonemptiedondurationchangeonendedonerroronfocus" + | ||||||
|  | 	"paceronhashchangeoninputmodeloninvalidonkeydownloadonkeypres" + | ||||||
|  | 	"spellcheckedonkeyupreloadonlanguagechangeonloadeddatalisting" + | ||||||
|  | 	"onloadedmetadatabindexonloadendonloadstartonmessageerroronmo" + | ||||||
|  | 	"usedownonmouseenteronmouseleaveonmousemoveonmouseoutputonmou" + | ||||||
|  | 	"seoveronmouseupromptonmousewheelonofflineononlineonpagehides" + | ||||||
|  | 	"classectionbluronpageshowbronpastepublicontenteditableonpaus" + | ||||||
|  | 	"emaponplayingonpopstateonprogressrcdocodeferonratechangeonre" + | ||||||
|  | 	"jectionhandledonresetonresizesrclangonscrollonsecuritypolicy" + | ||||||
|  | 	"violationauxclickonseekedonseekingonselectedonshowidth6onsor" + | ||||||
|  | 	"tableonstalledonstorageonsubmitemscopedonsuspendontoggleonun" + | ||||||
|  | 	"handledrejectionbeforeprintonunloadonvolumechangeonwaitingon" + | ||||||
|  | 	"wheeloptimumanifestrongoptionbeforeunloaddressrcsetstylesumm" + | ||||||
|  | 	"arysupsvgsystemplateworkertypewrap" | ||||||
|  | @ -0,0 +1,111 @@ | ||||||
|  | // Copyright 2011 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 html | ||||||
|  | 
 | ||||||
|  | // Section 12.2.4.2 of the HTML5 specification says "The following elements
 | ||||||
|  | // have varying levels of special parsing rules".
 | ||||||
|  | // https://html.spec.whatwg.org/multipage/syntax.html#the-stack-of-open-elements
 | ||||||
|  | var isSpecialElementMap = map[string]bool{ | ||||||
|  | 	"address":    true, | ||||||
|  | 	"applet":     true, | ||||||
|  | 	"area":       true, | ||||||
|  | 	"article":    true, | ||||||
|  | 	"aside":      true, | ||||||
|  | 	"base":       true, | ||||||
|  | 	"basefont":   true, | ||||||
|  | 	"bgsound":    true, | ||||||
|  | 	"blockquote": true, | ||||||
|  | 	"body":       true, | ||||||
|  | 	"br":         true, | ||||||
|  | 	"button":     true, | ||||||
|  | 	"caption":    true, | ||||||
|  | 	"center":     true, | ||||||
|  | 	"col":        true, | ||||||
|  | 	"colgroup":   true, | ||||||
|  | 	"dd":         true, | ||||||
|  | 	"details":    true, | ||||||
|  | 	"dir":        true, | ||||||
|  | 	"div":        true, | ||||||
|  | 	"dl":         true, | ||||||
|  | 	"dt":         true, | ||||||
|  | 	"embed":      true, | ||||||
|  | 	"fieldset":   true, | ||||||
|  | 	"figcaption": true, | ||||||
|  | 	"figure":     true, | ||||||
|  | 	"footer":     true, | ||||||
|  | 	"form":       true, | ||||||
|  | 	"frame":      true, | ||||||
|  | 	"frameset":   true, | ||||||
|  | 	"h1":         true, | ||||||
|  | 	"h2":         true, | ||||||
|  | 	"h3":         true, | ||||||
|  | 	"h4":         true, | ||||||
|  | 	"h5":         true, | ||||||
|  | 	"h6":         true, | ||||||
|  | 	"head":       true, | ||||||
|  | 	"header":     true, | ||||||
|  | 	"hgroup":     true, | ||||||
|  | 	"hr":         true, | ||||||
|  | 	"html":       true, | ||||||
|  | 	"iframe":     true, | ||||||
|  | 	"img":        true, | ||||||
|  | 	"input":      true, | ||||||
|  | 	"keygen":     true, // "keygen" has been removed from the spec, but are kept here for backwards compatibility.
 | ||||||
|  | 	"li":         true, | ||||||
|  | 	"link":       true, | ||||||
|  | 	"listing":    true, | ||||||
|  | 	"main":       true, | ||||||
|  | 	"marquee":    true, | ||||||
|  | 	"menu":       true, | ||||||
|  | 	"meta":       true, | ||||||
|  | 	"nav":        true, | ||||||
|  | 	"noembed":    true, | ||||||
|  | 	"noframes":   true, | ||||||
|  | 	"noscript":   true, | ||||||
|  | 	"object":     true, | ||||||
|  | 	"ol":         true, | ||||||
|  | 	"p":          true, | ||||||
|  | 	"param":      true, | ||||||
|  | 	"plaintext":  true, | ||||||
|  | 	"pre":        true, | ||||||
|  | 	"script":     true, | ||||||
|  | 	"section":    true, | ||||||
|  | 	"select":     true, | ||||||
|  | 	"source":     true, | ||||||
|  | 	"style":      true, | ||||||
|  | 	"summary":    true, | ||||||
|  | 	"table":      true, | ||||||
|  | 	"tbody":      true, | ||||||
|  | 	"td":         true, | ||||||
|  | 	"template":   true, | ||||||
|  | 	"textarea":   true, | ||||||
|  | 	"tfoot":      true, | ||||||
|  | 	"th":         true, | ||||||
|  | 	"thead":      true, | ||||||
|  | 	"title":      true, | ||||||
|  | 	"tr":         true, | ||||||
|  | 	"track":      true, | ||||||
|  | 	"ul":         true, | ||||||
|  | 	"wbr":        true, | ||||||
|  | 	"xmp":        true, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isSpecialElement(element *Node) bool { | ||||||
|  | 	switch element.Namespace { | ||||||
|  | 	case "", "html": | ||||||
|  | 		return isSpecialElementMap[element.Data] | ||||||
|  | 	case "math": | ||||||
|  | 		switch element.Data { | ||||||
|  | 		case "mi", "mo", "mn", "ms", "mtext", "annotation-xml": | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	case "svg": | ||||||
|  | 		switch element.Data { | ||||||
|  | 		case "foreignObject", "desc", "title": | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | @ -0,0 +1,127 @@ | ||||||
|  | // Copyright 2010 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 html implements an HTML5-compliant tokenizer and parser. | ||||||
|  | 
 | ||||||
|  | Tokenization is done by creating a Tokenizer for an io.Reader r. It is the | ||||||
|  | caller's responsibility to ensure that r provides UTF-8 encoded HTML. | ||||||
|  | 
 | ||||||
|  | 	z := html.NewTokenizer(r) | ||||||
|  | 
 | ||||||
|  | Given a Tokenizer z, the HTML is tokenized by repeatedly calling z.Next(), | ||||||
|  | which parses the next token and returns its type, or an error: | ||||||
|  | 
 | ||||||
|  | 	for { | ||||||
|  | 		tt := z.Next() | ||||||
|  | 		if tt == html.ErrorToken { | ||||||
|  | 			// ...
 | ||||||
|  | 			return ... | ||||||
|  | 		} | ||||||
|  | 		// Process the current token.
 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | There are two APIs for retrieving the current token. The high-level API is to | ||||||
|  | call Token; the low-level API is to call Text or TagName / TagAttr. Both APIs | ||||||
|  | allow optionally calling Raw after Next but before Token, Text, TagName, or | ||||||
|  | TagAttr. In EBNF notation, the valid call sequence per token is: | ||||||
|  | 
 | ||||||
|  | 	Next {Raw} [ Token | Text | TagName {TagAttr} ] | ||||||
|  | 
 | ||||||
|  | Token returns an independent data structure that completely describes a token. | ||||||
|  | Entities (such as "<") are unescaped, tag names and attribute keys are | ||||||
|  | lower-cased, and attributes are collected into a []Attribute. For example: | ||||||
|  | 
 | ||||||
|  | 	for { | ||||||
|  | 		if z.Next() == html.ErrorToken { | ||||||
|  | 			// Returning io.EOF indicates success.
 | ||||||
|  | 			return z.Err() | ||||||
|  | 		} | ||||||
|  | 		emitToken(z.Token()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | The low-level API performs fewer allocations and copies, but the contents of | ||||||
|  | the []byte values returned by Text, TagName and TagAttr may change on the next | ||||||
|  | call to Next. For example, to extract an HTML page's anchor text: | ||||||
|  | 
 | ||||||
|  | 	depth := 0 | ||||||
|  | 	for { | ||||||
|  | 		tt := z.Next() | ||||||
|  | 		switch tt { | ||||||
|  | 		case html.ErrorToken: | ||||||
|  | 			return z.Err() | ||||||
|  | 		case html.TextToken: | ||||||
|  | 			if depth > 0 { | ||||||
|  | 				// emitBytes should copy the []byte it receives,
 | ||||||
|  | 				// if it doesn't process it immediately.
 | ||||||
|  | 				emitBytes(z.Text()) | ||||||
|  | 			} | ||||||
|  | 		case html.StartTagToken, html.EndTagToken: | ||||||
|  | 			tn, _ := z.TagName() | ||||||
|  | 			if len(tn) == 1 && tn[0] == 'a' { | ||||||
|  | 				if tt == html.StartTagToken { | ||||||
|  | 					depth++ | ||||||
|  | 				} else { | ||||||
|  | 					depth-- | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | Parsing is done by calling Parse with an io.Reader, which returns the root of | ||||||
|  | the parse tree (the document element) as a *Node. It is the caller's | ||||||
|  | responsibility to ensure that the Reader provides UTF-8 encoded HTML. For | ||||||
|  | example, to process each anchor node in depth-first order: | ||||||
|  | 
 | ||||||
|  | 	doc, err := html.Parse(r) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// ...
 | ||||||
|  | 	} | ||||||
|  | 	var f func(*html.Node) | ||||||
|  | 	f = func(n *html.Node) { | ||||||
|  | 		if n.Type == html.ElementNode && n.Data == "a" { | ||||||
|  | 			// Do something with n...
 | ||||||
|  | 		} | ||||||
|  | 		for c := n.FirstChild; c != nil; c = c.NextSibling { | ||||||
|  | 			f(c) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	f(doc) | ||||||
|  | 
 | ||||||
|  | The relevant specifications include: | ||||||
|  | https://html.spec.whatwg.org/multipage/syntax.html and
 | ||||||
|  | https://html.spec.whatwg.org/multipage/syntax.html#tokenization
 | ||||||
|  | 
 | ||||||
|  | # Security Considerations | ||||||
|  | 
 | ||||||
|  | Care should be taken when parsing and interpreting HTML, whether full documents | ||||||
|  | or fragments, within the framework of the HTML specification, especially with | ||||||
|  | regard to untrusted inputs. | ||||||
|  | 
 | ||||||
|  | This package provides both a tokenizer and a parser, which implement the | ||||||
|  | tokenization, and tokenization and tree construction stages of the WHATWG HTML | ||||||
|  | parsing specification respectively. While the tokenizer parses and normalizes | ||||||
|  | individual HTML tokens, only the parser constructs the DOM tree from the | ||||||
|  | tokenized HTML, as described in the tree construction stage of the | ||||||
|  | specification, dynamically modifying or extending the docuemnt's DOM tree. | ||||||
|  | 
 | ||||||
|  | If your use case requires semantically well-formed HTML documents, as defined by | ||||||
|  | the WHATWG specification, the parser should be used rather than the tokenizer. | ||||||
|  | 
 | ||||||
|  | In security contexts, if trust decisions are being made using the tokenized or | ||||||
|  | parsed content, the input must be re-serialized (for instance by using Render or | ||||||
|  | Token.String) in order for those trust decisions to hold, as the process of | ||||||
|  | tokenization or parsing may alter the content. | ||||||
|  | */ | ||||||
|  | package html // import "golang.org/x/net/html"
 | ||||||
|  | 
 | ||||||
|  | // The tokenization algorithm implemented by this package is not a line-by-line
 | ||||||
|  | // transliteration of the relatively verbose state-machine in the WHATWG
 | ||||||
|  | // specification. A more direct approach is used instead, where the program
 | ||||||
|  | // counter implies the state, such as whether it is tokenizing a tag or a text
 | ||||||
|  | // node. Specification compliance is verified by checking expected and actual
 | ||||||
|  | // outputs over a test suite rather than aiming for algorithmic fidelity.
 | ||||||
|  | 
 | ||||||
|  | // TODO(nigeltao): Does a DOM API belong in this package or a separate one?
 | ||||||
|  | // TODO(nigeltao): How does parsing interact with a JavaScript engine?
 | ||||||
|  | @ -0,0 +1,156 @@ | ||||||
|  | // Copyright 2011 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 html | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // parseDoctype parses the data from a DoctypeToken into a name,
 | ||||||
|  | // public identifier, and system identifier. It returns a Node whose Type
 | ||||||
|  | // is DoctypeNode, whose Data is the name, and which has attributes
 | ||||||
|  | // named "system" and "public" for the two identifiers if they were present.
 | ||||||
|  | // quirks is whether the document should be parsed in "quirks mode".
 | ||||||
|  | func parseDoctype(s string) (n *Node, quirks bool) { | ||||||
|  | 	n = &Node{Type: DoctypeNode} | ||||||
|  | 
 | ||||||
|  | 	// Find the name.
 | ||||||
|  | 	space := strings.IndexAny(s, whitespace) | ||||||
|  | 	if space == -1 { | ||||||
|  | 		space = len(s) | ||||||
|  | 	} | ||||||
|  | 	n.Data = s[:space] | ||||||
|  | 	// The comparison to "html" is case-sensitive.
 | ||||||
|  | 	if n.Data != "html" { | ||||||
|  | 		quirks = true | ||||||
|  | 	} | ||||||
|  | 	n.Data = strings.ToLower(n.Data) | ||||||
|  | 	s = strings.TrimLeft(s[space:], whitespace) | ||||||
|  | 
 | ||||||
|  | 	if len(s) < 6 { | ||||||
|  | 		// It can't start with "PUBLIC" or "SYSTEM".
 | ||||||
|  | 		// Ignore the rest of the string.
 | ||||||
|  | 		return n, quirks || s != "" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	key := strings.ToLower(s[:6]) | ||||||
|  | 	s = s[6:] | ||||||
|  | 	for key == "public" || key == "system" { | ||||||
|  | 		s = strings.TrimLeft(s, whitespace) | ||||||
|  | 		if s == "" { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		quote := s[0] | ||||||
|  | 		if quote != '"' && quote != '\'' { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		s = s[1:] | ||||||
|  | 		q := strings.IndexRune(s, rune(quote)) | ||||||
|  | 		var id string | ||||||
|  | 		if q == -1 { | ||||||
|  | 			id = s | ||||||
|  | 			s = "" | ||||||
|  | 		} else { | ||||||
|  | 			id = s[:q] | ||||||
|  | 			s = s[q+1:] | ||||||
|  | 		} | ||||||
|  | 		n.Attr = append(n.Attr, Attribute{Key: key, Val: id}) | ||||||
|  | 		if key == "public" { | ||||||
|  | 			key = "system" | ||||||
|  | 		} else { | ||||||
|  | 			key = "" | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if key != "" || s != "" { | ||||||
|  | 		quirks = true | ||||||
|  | 	} else if len(n.Attr) > 0 { | ||||||
|  | 		if n.Attr[0].Key == "public" { | ||||||
|  | 			public := strings.ToLower(n.Attr[0].Val) | ||||||
|  | 			switch public { | ||||||
|  | 			case "-//w3o//dtd w3 html strict 3.0//en//", "-/w3d/dtd html 4.0 transitional/en", "html": | ||||||
|  | 				quirks = true | ||||||
|  | 			default: | ||||||
|  | 				for _, q := range quirkyIDs { | ||||||
|  | 					if strings.HasPrefix(public, q) { | ||||||
|  | 						quirks = true | ||||||
|  | 						break | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			// The following two public IDs only cause quirks mode if there is no system ID.
 | ||||||
|  | 			if len(n.Attr) == 1 && (strings.HasPrefix(public, "-//w3c//dtd html 4.01 frameset//") || | ||||||
|  | 				strings.HasPrefix(public, "-//w3c//dtd html 4.01 transitional//")) { | ||||||
|  | 				quirks = true | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if lastAttr := n.Attr[len(n.Attr)-1]; lastAttr.Key == "system" && | ||||||
|  | 			strings.ToLower(lastAttr.Val) == "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd" { | ||||||
|  | 			quirks = true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return n, quirks | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // quirkyIDs is a list of public doctype identifiers that cause a document
 | ||||||
|  | // to be interpreted in quirks mode. The identifiers should be in lower case.
 | ||||||
|  | var quirkyIDs = []string{ | ||||||
|  | 	"+//silmaril//dtd html pro v0r11 19970101//", | ||||||
|  | 	"-//advasoft ltd//dtd html 3.0 aswedit + extensions//", | ||||||
|  | 	"-//as//dtd html 3.0 aswedit + extensions//", | ||||||
|  | 	"-//ietf//dtd html 2.0 level 1//", | ||||||
|  | 	"-//ietf//dtd html 2.0 level 2//", | ||||||
|  | 	"-//ietf//dtd html 2.0 strict level 1//", | ||||||
|  | 	"-//ietf//dtd html 2.0 strict level 2//", | ||||||
|  | 	"-//ietf//dtd html 2.0 strict//", | ||||||
|  | 	"-//ietf//dtd html 2.0//", | ||||||
|  | 	"-//ietf//dtd html 2.1e//", | ||||||
|  | 	"-//ietf//dtd html 3.0//", | ||||||
|  | 	"-//ietf//dtd html 3.2 final//", | ||||||
|  | 	"-//ietf//dtd html 3.2//", | ||||||
|  | 	"-//ietf//dtd html 3//", | ||||||
|  | 	"-//ietf//dtd html level 0//", | ||||||
|  | 	"-//ietf//dtd html level 1//", | ||||||
|  | 	"-//ietf//dtd html level 2//", | ||||||
|  | 	"-//ietf//dtd html level 3//", | ||||||
|  | 	"-//ietf//dtd html strict level 0//", | ||||||
|  | 	"-//ietf//dtd html strict level 1//", | ||||||
|  | 	"-//ietf//dtd html strict level 2//", | ||||||
|  | 	"-//ietf//dtd html strict level 3//", | ||||||
|  | 	"-//ietf//dtd html strict//", | ||||||
|  | 	"-//ietf//dtd html//", | ||||||
|  | 	"-//metrius//dtd metrius presentational//", | ||||||
|  | 	"-//microsoft//dtd internet explorer 2.0 html strict//", | ||||||
|  | 	"-//microsoft//dtd internet explorer 2.0 html//", | ||||||
|  | 	"-//microsoft//dtd internet explorer 2.0 tables//", | ||||||
|  | 	"-//microsoft//dtd internet explorer 3.0 html strict//", | ||||||
|  | 	"-//microsoft//dtd internet explorer 3.0 html//", | ||||||
|  | 	"-//microsoft//dtd internet explorer 3.0 tables//", | ||||||
|  | 	"-//netscape comm. corp.//dtd html//", | ||||||
|  | 	"-//netscape comm. corp.//dtd strict html//", | ||||||
|  | 	"-//o'reilly and associates//dtd html 2.0//", | ||||||
|  | 	"-//o'reilly and associates//dtd html extended 1.0//", | ||||||
|  | 	"-//o'reilly and associates//dtd html extended relaxed 1.0//", | ||||||
|  | 	"-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//", | ||||||
|  | 	"-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//", | ||||||
|  | 	"-//spyglass//dtd html 2.0 extended//", | ||||||
|  | 	"-//sq//dtd html 2.0 hotmetal + extensions//", | ||||||
|  | 	"-//sun microsystems corp.//dtd hotjava html//", | ||||||
|  | 	"-//sun microsystems corp.//dtd hotjava strict html//", | ||||||
|  | 	"-//w3c//dtd html 3 1995-03-24//", | ||||||
|  | 	"-//w3c//dtd html 3.2 draft//", | ||||||
|  | 	"-//w3c//dtd html 3.2 final//", | ||||||
|  | 	"-//w3c//dtd html 3.2//", | ||||||
|  | 	"-//w3c//dtd html 3.2s draft//", | ||||||
|  | 	"-//w3c//dtd html 4.0 frameset//", | ||||||
|  | 	"-//w3c//dtd html 4.0 transitional//", | ||||||
|  | 	"-//w3c//dtd html experimental 19960712//", | ||||||
|  | 	"-//w3c//dtd html experimental 970421//", | ||||||
|  | 	"-//w3c//dtd w3 html//", | ||||||
|  | 	"-//w3o//dtd w3 html 3.0//", | ||||||
|  | 	"-//webtechs//dtd mozilla html 2.0//", | ||||||
|  | 	"-//webtechs//dtd mozilla html//", | ||||||
|  | } | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,339 @@ | ||||||
|  | // Copyright 2010 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 html | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"strings" | ||||||
|  | 	"unicode/utf8" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // These replacements permit compatibility with old numeric entities that
 | ||||||
|  | // assumed Windows-1252 encoding.
 | ||||||
|  | // https://html.spec.whatwg.org/multipage/syntax.html#consume-a-character-reference
 | ||||||
|  | var replacementTable = [...]rune{ | ||||||
|  | 	'\u20AC', // First entry is what 0x80 should be replaced with.
 | ||||||
|  | 	'\u0081', | ||||||
|  | 	'\u201A', | ||||||
|  | 	'\u0192', | ||||||
|  | 	'\u201E', | ||||||
|  | 	'\u2026', | ||||||
|  | 	'\u2020', | ||||||
|  | 	'\u2021', | ||||||
|  | 	'\u02C6', | ||||||
|  | 	'\u2030', | ||||||
|  | 	'\u0160', | ||||||
|  | 	'\u2039', | ||||||
|  | 	'\u0152', | ||||||
|  | 	'\u008D', | ||||||
|  | 	'\u017D', | ||||||
|  | 	'\u008F', | ||||||
|  | 	'\u0090', | ||||||
|  | 	'\u2018', | ||||||
|  | 	'\u2019', | ||||||
|  | 	'\u201C', | ||||||
|  | 	'\u201D', | ||||||
|  | 	'\u2022', | ||||||
|  | 	'\u2013', | ||||||
|  | 	'\u2014', | ||||||
|  | 	'\u02DC', | ||||||
|  | 	'\u2122', | ||||||
|  | 	'\u0161', | ||||||
|  | 	'\u203A', | ||||||
|  | 	'\u0153', | ||||||
|  | 	'\u009D', | ||||||
|  | 	'\u017E', | ||||||
|  | 	'\u0178', // Last entry is 0x9F.
 | ||||||
|  | 	// 0x00->'\uFFFD' is handled programmatically.
 | ||||||
|  | 	// 0x0D->'\u000D' is a no-op.
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // unescapeEntity reads an entity like "<" from b[src:] and writes the
 | ||||||
|  | // corresponding "<" to b[dst:], returning the incremented dst and src cursors.
 | ||||||
|  | // Precondition: b[src] == '&' && dst <= src.
 | ||||||
|  | // attribute should be true if parsing an attribute value.
 | ||||||
|  | func unescapeEntity(b []byte, dst, src int, attribute bool) (dst1, src1 int) { | ||||||
|  | 	// https://html.spec.whatwg.org/multipage/syntax.html#consume-a-character-reference
 | ||||||
|  | 
 | ||||||
|  | 	// i starts at 1 because we already know that s[0] == '&'.
 | ||||||
|  | 	i, s := 1, b[src:] | ||||||
|  | 
 | ||||||
|  | 	if len(s) <= 1 { | ||||||
|  | 		b[dst] = b[src] | ||||||
|  | 		return dst + 1, src + 1 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if s[i] == '#' { | ||||||
|  | 		if len(s) <= 3 { // We need to have at least "&#.".
 | ||||||
|  | 			b[dst] = b[src] | ||||||
|  | 			return dst + 1, src + 1 | ||||||
|  | 		} | ||||||
|  | 		i++ | ||||||
|  | 		c := s[i] | ||||||
|  | 		hex := false | ||||||
|  | 		if c == 'x' || c == 'X' { | ||||||
|  | 			hex = true | ||||||
|  | 			i++ | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		x := '\x00' | ||||||
|  | 		for i < len(s) { | ||||||
|  | 			c = s[i] | ||||||
|  | 			i++ | ||||||
|  | 			if hex { | ||||||
|  | 				if '0' <= c && c <= '9' { | ||||||
|  | 					x = 16*x + rune(c) - '0' | ||||||
|  | 					continue | ||||||
|  | 				} else if 'a' <= c && c <= 'f' { | ||||||
|  | 					x = 16*x + rune(c) - 'a' + 10 | ||||||
|  | 					continue | ||||||
|  | 				} else if 'A' <= c && c <= 'F' { | ||||||
|  | 					x = 16*x + rune(c) - 'A' + 10 | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 			} else if '0' <= c && c <= '9' { | ||||||
|  | 				x = 10*x + rune(c) - '0' | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			if c != ';' { | ||||||
|  | 				i-- | ||||||
|  | 			} | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if i <= 3 { // No characters matched.
 | ||||||
|  | 			b[dst] = b[src] | ||||||
|  | 			return dst + 1, src + 1 | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if 0x80 <= x && x <= 0x9F { | ||||||
|  | 			// Replace characters from Windows-1252 with UTF-8 equivalents.
 | ||||||
|  | 			x = replacementTable[x-0x80] | ||||||
|  | 		} else if x == 0 || (0xD800 <= x && x <= 0xDFFF) || x > 0x10FFFF { | ||||||
|  | 			// Replace invalid characters with the replacement character.
 | ||||||
|  | 			x = '\uFFFD' | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return dst + utf8.EncodeRune(b[dst:], x), src + i | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Consume the maximum number of characters possible, with the
 | ||||||
|  | 	// consumed characters matching one of the named references.
 | ||||||
|  | 
 | ||||||
|  | 	for i < len(s) { | ||||||
|  | 		c := s[i] | ||||||
|  | 		i++ | ||||||
|  | 		// Lower-cased characters are more common in entities, so we check for them first.
 | ||||||
|  | 		if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if c != ';' { | ||||||
|  | 			i-- | ||||||
|  | 		} | ||||||
|  | 		break | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	entityName := string(s[1:i]) | ||||||
|  | 	if entityName == "" { | ||||||
|  | 		// No-op.
 | ||||||
|  | 	} else if attribute && entityName[len(entityName)-1] != ';' && len(s) > i && s[i] == '=' { | ||||||
|  | 		// No-op.
 | ||||||
|  | 	} else if x := entity[entityName]; x != 0 { | ||||||
|  | 		return dst + utf8.EncodeRune(b[dst:], x), src + i | ||||||
|  | 	} else if x := entity2[entityName]; x[0] != 0 { | ||||||
|  | 		dst1 := dst + utf8.EncodeRune(b[dst:], x[0]) | ||||||
|  | 		return dst1 + utf8.EncodeRune(b[dst1:], x[1]), src + i | ||||||
|  | 	} else if !attribute { | ||||||
|  | 		maxLen := len(entityName) - 1 | ||||||
|  | 		if maxLen > longestEntityWithoutSemicolon { | ||||||
|  | 			maxLen = longestEntityWithoutSemicolon | ||||||
|  | 		} | ||||||
|  | 		for j := maxLen; j > 1; j-- { | ||||||
|  | 			if x := entity[entityName[:j]]; x != 0 { | ||||||
|  | 				return dst + utf8.EncodeRune(b[dst:], x), src + j + 1 | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	dst1, src1 = dst+i, src+i | ||||||
|  | 	copy(b[dst:dst1], b[src:src1]) | ||||||
|  | 	return dst1, src1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // unescape unescapes b's entities in-place, so that "a<b" becomes "a<b".
 | ||||||
|  | // attribute should be true if parsing an attribute value.
 | ||||||
|  | func unescape(b []byte, attribute bool) []byte { | ||||||
|  | 	for i, c := range b { | ||||||
|  | 		if c == '&' { | ||||||
|  | 			dst, src := unescapeEntity(b, i, i, attribute) | ||||||
|  | 			for src < len(b) { | ||||||
|  | 				c := b[src] | ||||||
|  | 				if c == '&' { | ||||||
|  | 					dst, src = unescapeEntity(b, dst, src, attribute) | ||||||
|  | 				} else { | ||||||
|  | 					b[dst] = c | ||||||
|  | 					dst, src = dst+1, src+1 | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			return b[0:dst] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // lower lower-cases the A-Z bytes in b in-place, so that "aBc" becomes "abc".
 | ||||||
|  | func lower(b []byte) []byte { | ||||||
|  | 	for i, c := range b { | ||||||
|  | 		if 'A' <= c && c <= 'Z' { | ||||||
|  | 			b[i] = c + 'a' - 'A' | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // escapeComment is like func escape but escapes its input bytes less often.
 | ||||||
|  | // Per https://github.com/golang/go/issues/58246 some HTML comments are (1)
 | ||||||
|  | // meaningful and (2) contain angle brackets that we'd like to avoid escaping
 | ||||||
|  | // unless we have to.
 | ||||||
|  | //
 | ||||||
|  | // "We have to" includes the '&' byte, since that introduces other escapes.
 | ||||||
|  | //
 | ||||||
|  | // It also includes those bytes (not including EOF) that would otherwise end
 | ||||||
|  | // the comment. Per the summary table at the bottom of comment_test.go, this is
 | ||||||
|  | // the '>' byte that, per above, we'd like to avoid escaping unless we have to.
 | ||||||
|  | //
 | ||||||
|  | // Studying the summary table (and T actions in its '>' column) closely, we
 | ||||||
|  | // only need to escape in states 43, 44, 49, 51 and 52. State 43 is at the
 | ||||||
|  | // start of the comment data. State 52 is after a '!'. The other three states
 | ||||||
|  | // are after a '-'.
 | ||||||
|  | //
 | ||||||
|  | // Our algorithm is thus to escape every '&' and to escape '>' if and only if:
 | ||||||
|  | //   - The '>' is after a '!' or '-' (in the unescaped data) or
 | ||||||
|  | //   - The '>' is at the start of the comment data (after the opening "<!--").
 | ||||||
|  | func escapeComment(w writer, s string) error { | ||||||
|  | 	// When modifying this function, consider manually increasing the
 | ||||||
|  | 	// maxSuffixLen constant in func TestComments, from 6 to e.g. 9 or more.
 | ||||||
|  | 	// That increase should only be temporary, not committed, as it
 | ||||||
|  | 	// exponentially affects the test running time.
 | ||||||
|  | 
 | ||||||
|  | 	if len(s) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Loop:
 | ||||||
|  | 	//   - Grow j such that s[i:j] does not need escaping.
 | ||||||
|  | 	//   - If s[j] does need escaping, output s[i:j] and an escaped s[j],
 | ||||||
|  | 	//     resetting i and j to point past that s[j] byte.
 | ||||||
|  | 	i := 0 | ||||||
|  | 	for j := 0; j < len(s); j++ { | ||||||
|  | 		escaped := "" | ||||||
|  | 		switch s[j] { | ||||||
|  | 		case '&': | ||||||
|  | 			escaped = "&" | ||||||
|  | 
 | ||||||
|  | 		case '>': | ||||||
|  | 			if j > 0 { | ||||||
|  | 				if prev := s[j-1]; (prev != '!') && (prev != '-') { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			escaped = ">" | ||||||
|  | 
 | ||||||
|  | 		default: | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if i < j { | ||||||
|  | 			if _, err := w.WriteString(s[i:j]); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if _, err := w.WriteString(escaped); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		i = j + 1 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if i < len(s) { | ||||||
|  | 		if _, err := w.WriteString(s[i:]); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // escapeCommentString is to EscapeString as escapeComment is to escape.
 | ||||||
|  | func escapeCommentString(s string) string { | ||||||
|  | 	if strings.IndexAny(s, "&>") == -1 { | ||||||
|  | 		return s | ||||||
|  | 	} | ||||||
|  | 	var buf bytes.Buffer | ||||||
|  | 	escapeComment(&buf, s) | ||||||
|  | 	return buf.String() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const escapedChars = "&'<>\"\r" | ||||||
|  | 
 | ||||||
|  | func escape(w writer, s string) error { | ||||||
|  | 	i := strings.IndexAny(s, escapedChars) | ||||||
|  | 	for i != -1 { | ||||||
|  | 		if _, err := w.WriteString(s[:i]); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		var esc string | ||||||
|  | 		switch s[i] { | ||||||
|  | 		case '&': | ||||||
|  | 			esc = "&" | ||||||
|  | 		case '\'': | ||||||
|  | 			// "'" is shorter than "'" and apos was not in HTML until HTML5.
 | ||||||
|  | 			esc = "'" | ||||||
|  | 		case '<': | ||||||
|  | 			esc = "<" | ||||||
|  | 		case '>': | ||||||
|  | 			esc = ">" | ||||||
|  | 		case '"': | ||||||
|  | 			// """ is shorter than """.
 | ||||||
|  | 			esc = """ | ||||||
|  | 		case '\r': | ||||||
|  | 			esc = "
" | ||||||
|  | 		default: | ||||||
|  | 			panic("unrecognized escape character") | ||||||
|  | 		} | ||||||
|  | 		s = s[i+1:] | ||||||
|  | 		if _, err := w.WriteString(esc); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		i = strings.IndexAny(s, escapedChars) | ||||||
|  | 	} | ||||||
|  | 	_, err := w.WriteString(s) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // EscapeString escapes special characters like "<" to become "<". It
 | ||||||
|  | // escapes only five such characters: <, >, &, ' and ".
 | ||||||
|  | // UnescapeString(EscapeString(s)) == s always holds, but the converse isn't
 | ||||||
|  | // always true.
 | ||||||
|  | func EscapeString(s string) string { | ||||||
|  | 	if strings.IndexAny(s, escapedChars) == -1 { | ||||||
|  | 		return s | ||||||
|  | 	} | ||||||
|  | 	var buf bytes.Buffer | ||||||
|  | 	escape(&buf, s) | ||||||
|  | 	return buf.String() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UnescapeString unescapes entities like "<" to become "<". It unescapes a
 | ||||||
|  | // larger range of entities than EscapeString escapes. For example, "á"
 | ||||||
|  | // unescapes to "á", as does "á" and "&xE1;".
 | ||||||
|  | // UnescapeString(EscapeString(s)) == s always holds, but the converse isn't
 | ||||||
|  | // always true.
 | ||||||
|  | func UnescapeString(s string) string { | ||||||
|  | 	for _, c := range s { | ||||||
|  | 		if c == '&' { | ||||||
|  | 			return string(unescape([]byte(s), false)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return s | ||||||
|  | } | ||||||
|  | @ -0,0 +1,222 @@ | ||||||
|  | // Copyright 2011 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 html | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func adjustAttributeNames(aa []Attribute, nameMap map[string]string) { | ||||||
|  | 	for i := range aa { | ||||||
|  | 		if newName, ok := nameMap[aa[i].Key]; ok { | ||||||
|  | 			aa[i].Key = newName | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func adjustForeignAttributes(aa []Attribute) { | ||||||
|  | 	for i, a := range aa { | ||||||
|  | 		if a.Key == "" || a.Key[0] != 'x' { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		switch a.Key { | ||||||
|  | 		case "xlink:actuate", "xlink:arcrole", "xlink:href", "xlink:role", "xlink:show", | ||||||
|  | 			"xlink:title", "xlink:type", "xml:base", "xml:lang", "xml:space", "xmlns:xlink": | ||||||
|  | 			j := strings.Index(a.Key, ":") | ||||||
|  | 			aa[i].Namespace = a.Key[:j] | ||||||
|  | 			aa[i].Key = a.Key[j+1:] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func htmlIntegrationPoint(n *Node) bool { | ||||||
|  | 	if n.Type != ElementNode { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	switch n.Namespace { | ||||||
|  | 	case "math": | ||||||
|  | 		if n.Data == "annotation-xml" { | ||||||
|  | 			for _, a := range n.Attr { | ||||||
|  | 				if a.Key == "encoding" { | ||||||
|  | 					val := strings.ToLower(a.Val) | ||||||
|  | 					if val == "text/html" || val == "application/xhtml+xml" { | ||||||
|  | 						return true | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	case "svg": | ||||||
|  | 		switch n.Data { | ||||||
|  | 		case "desc", "foreignObject", "title": | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func mathMLTextIntegrationPoint(n *Node) bool { | ||||||
|  | 	if n.Namespace != "math" { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	switch n.Data { | ||||||
|  | 	case "mi", "mo", "mn", "ms", "mtext": | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Section 12.2.6.5.
 | ||||||
|  | var breakout = map[string]bool{ | ||||||
|  | 	"b":          true, | ||||||
|  | 	"big":        true, | ||||||
|  | 	"blockquote": true, | ||||||
|  | 	"body":       true, | ||||||
|  | 	"br":         true, | ||||||
|  | 	"center":     true, | ||||||
|  | 	"code":       true, | ||||||
|  | 	"dd":         true, | ||||||
|  | 	"div":        true, | ||||||
|  | 	"dl":         true, | ||||||
|  | 	"dt":         true, | ||||||
|  | 	"em":         true, | ||||||
|  | 	"embed":      true, | ||||||
|  | 	"h1":         true, | ||||||
|  | 	"h2":         true, | ||||||
|  | 	"h3":         true, | ||||||
|  | 	"h4":         true, | ||||||
|  | 	"h5":         true, | ||||||
|  | 	"h6":         true, | ||||||
|  | 	"head":       true, | ||||||
|  | 	"hr":         true, | ||||||
|  | 	"i":          true, | ||||||
|  | 	"img":        true, | ||||||
|  | 	"li":         true, | ||||||
|  | 	"listing":    true, | ||||||
|  | 	"menu":       true, | ||||||
|  | 	"meta":       true, | ||||||
|  | 	"nobr":       true, | ||||||
|  | 	"ol":         true, | ||||||
|  | 	"p":          true, | ||||||
|  | 	"pre":        true, | ||||||
|  | 	"ruby":       true, | ||||||
|  | 	"s":          true, | ||||||
|  | 	"small":      true, | ||||||
|  | 	"span":       true, | ||||||
|  | 	"strong":     true, | ||||||
|  | 	"strike":     true, | ||||||
|  | 	"sub":        true, | ||||||
|  | 	"sup":        true, | ||||||
|  | 	"table":      true, | ||||||
|  | 	"tt":         true, | ||||||
|  | 	"u":          true, | ||||||
|  | 	"ul":         true, | ||||||
|  | 	"var":        true, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Section 12.2.6.5.
 | ||||||
|  | var svgTagNameAdjustments = map[string]string{ | ||||||
|  | 	"altglyph":            "altGlyph", | ||||||
|  | 	"altglyphdef":         "altGlyphDef", | ||||||
|  | 	"altglyphitem":        "altGlyphItem", | ||||||
|  | 	"animatecolor":        "animateColor", | ||||||
|  | 	"animatemotion":       "animateMotion", | ||||||
|  | 	"animatetransform":    "animateTransform", | ||||||
|  | 	"clippath":            "clipPath", | ||||||
|  | 	"feblend":             "feBlend", | ||||||
|  | 	"fecolormatrix":       "feColorMatrix", | ||||||
|  | 	"fecomponenttransfer": "feComponentTransfer", | ||||||
|  | 	"fecomposite":         "feComposite", | ||||||
|  | 	"feconvolvematrix":    "feConvolveMatrix", | ||||||
|  | 	"fediffuselighting":   "feDiffuseLighting", | ||||||
|  | 	"fedisplacementmap":   "feDisplacementMap", | ||||||
|  | 	"fedistantlight":      "feDistantLight", | ||||||
|  | 	"feflood":             "feFlood", | ||||||
|  | 	"fefunca":             "feFuncA", | ||||||
|  | 	"fefuncb":             "feFuncB", | ||||||
|  | 	"fefuncg":             "feFuncG", | ||||||
|  | 	"fefuncr":             "feFuncR", | ||||||
|  | 	"fegaussianblur":      "feGaussianBlur", | ||||||
|  | 	"feimage":             "feImage", | ||||||
|  | 	"femerge":             "feMerge", | ||||||
|  | 	"femergenode":         "feMergeNode", | ||||||
|  | 	"femorphology":        "feMorphology", | ||||||
|  | 	"feoffset":            "feOffset", | ||||||
|  | 	"fepointlight":        "fePointLight", | ||||||
|  | 	"fespecularlighting":  "feSpecularLighting", | ||||||
|  | 	"fespotlight":         "feSpotLight", | ||||||
|  | 	"fetile":              "feTile", | ||||||
|  | 	"feturbulence":        "feTurbulence", | ||||||
|  | 	"foreignobject":       "foreignObject", | ||||||
|  | 	"glyphref":            "glyphRef", | ||||||
|  | 	"lineargradient":      "linearGradient", | ||||||
|  | 	"radialgradient":      "radialGradient", | ||||||
|  | 	"textpath":            "textPath", | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Section 12.2.6.1
 | ||||||
|  | var mathMLAttributeAdjustments = map[string]string{ | ||||||
|  | 	"definitionurl": "definitionURL", | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var svgAttributeAdjustments = map[string]string{ | ||||||
|  | 	"attributename":       "attributeName", | ||||||
|  | 	"attributetype":       "attributeType", | ||||||
|  | 	"basefrequency":       "baseFrequency", | ||||||
|  | 	"baseprofile":         "baseProfile", | ||||||
|  | 	"calcmode":            "calcMode", | ||||||
|  | 	"clippathunits":       "clipPathUnits", | ||||||
|  | 	"diffuseconstant":     "diffuseConstant", | ||||||
|  | 	"edgemode":            "edgeMode", | ||||||
|  | 	"filterunits":         "filterUnits", | ||||||
|  | 	"glyphref":            "glyphRef", | ||||||
|  | 	"gradienttransform":   "gradientTransform", | ||||||
|  | 	"gradientunits":       "gradientUnits", | ||||||
|  | 	"kernelmatrix":        "kernelMatrix", | ||||||
|  | 	"kernelunitlength":    "kernelUnitLength", | ||||||
|  | 	"keypoints":           "keyPoints", | ||||||
|  | 	"keysplines":          "keySplines", | ||||||
|  | 	"keytimes":            "keyTimes", | ||||||
|  | 	"lengthadjust":        "lengthAdjust", | ||||||
|  | 	"limitingconeangle":   "limitingConeAngle", | ||||||
|  | 	"markerheight":        "markerHeight", | ||||||
|  | 	"markerunits":         "markerUnits", | ||||||
|  | 	"markerwidth":         "markerWidth", | ||||||
|  | 	"maskcontentunits":    "maskContentUnits", | ||||||
|  | 	"maskunits":           "maskUnits", | ||||||
|  | 	"numoctaves":          "numOctaves", | ||||||
|  | 	"pathlength":          "pathLength", | ||||||
|  | 	"patterncontentunits": "patternContentUnits", | ||||||
|  | 	"patterntransform":    "patternTransform", | ||||||
|  | 	"patternunits":        "patternUnits", | ||||||
|  | 	"pointsatx":           "pointsAtX", | ||||||
|  | 	"pointsaty":           "pointsAtY", | ||||||
|  | 	"pointsatz":           "pointsAtZ", | ||||||
|  | 	"preservealpha":       "preserveAlpha", | ||||||
|  | 	"preserveaspectratio": "preserveAspectRatio", | ||||||
|  | 	"primitiveunits":      "primitiveUnits", | ||||||
|  | 	"refx":                "refX", | ||||||
|  | 	"refy":                "refY", | ||||||
|  | 	"repeatcount":         "repeatCount", | ||||||
|  | 	"repeatdur":           "repeatDur", | ||||||
|  | 	"requiredextensions":  "requiredExtensions", | ||||||
|  | 	"requiredfeatures":    "requiredFeatures", | ||||||
|  | 	"specularconstant":    "specularConstant", | ||||||
|  | 	"specularexponent":    "specularExponent", | ||||||
|  | 	"spreadmethod":        "spreadMethod", | ||||||
|  | 	"startoffset":         "startOffset", | ||||||
|  | 	"stddeviation":        "stdDeviation", | ||||||
|  | 	"stitchtiles":         "stitchTiles", | ||||||
|  | 	"surfacescale":        "surfaceScale", | ||||||
|  | 	"systemlanguage":      "systemLanguage", | ||||||
|  | 	"tablevalues":         "tableValues", | ||||||
|  | 	"targetx":             "targetX", | ||||||
|  | 	"targety":             "targetY", | ||||||
|  | 	"textlength":          "textLength", | ||||||
|  | 	"viewbox":             "viewBox", | ||||||
|  | 	"viewtarget":          "viewTarget", | ||||||
|  | 	"xchannelselector":    "xChannelSelector", | ||||||
|  | 	"ychannelselector":    "yChannelSelector", | ||||||
|  | 	"zoomandpan":          "zoomAndPan", | ||||||
|  | } | ||||||
|  | @ -0,0 +1,225 @@ | ||||||
|  | // Copyright 2011 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 html | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"golang.org/x/net/html/atom" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // A NodeType is the type of a Node.
 | ||||||
|  | type NodeType uint32 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	ErrorNode NodeType = iota | ||||||
|  | 	TextNode | ||||||
|  | 	DocumentNode | ||||||
|  | 	ElementNode | ||||||
|  | 	CommentNode | ||||||
|  | 	DoctypeNode | ||||||
|  | 	// RawNode nodes are not returned by the parser, but can be part of the
 | ||||||
|  | 	// Node tree passed to func Render to insert raw HTML (without escaping).
 | ||||||
|  | 	// If so, this package makes no guarantee that the rendered HTML is secure
 | ||||||
|  | 	// (from e.g. Cross Site Scripting attacks) or well-formed.
 | ||||||
|  | 	RawNode | ||||||
|  | 	scopeMarkerNode | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Section 12.2.4.3 says "The markers are inserted when entering applet,
 | ||||||
|  | // object, marquee, template, td, th, and caption elements, and are used
 | ||||||
|  | // to prevent formatting from "leaking" into applet, object, marquee,
 | ||||||
|  | // template, td, th, and caption elements".
 | ||||||
|  | var scopeMarker = Node{Type: scopeMarkerNode} | ||||||
|  | 
 | ||||||
|  | // A Node consists of a NodeType and some Data (tag name for element nodes,
 | ||||||
|  | // content for text) and are part of a tree of Nodes. Element nodes may also
 | ||||||
|  | // have a Namespace and contain a slice of Attributes. Data is unescaped, so
 | ||||||
|  | // that it looks like "a<b" rather than "a<b". For element nodes, DataAtom
 | ||||||
|  | // is the atom for Data, or zero if Data is not a known tag name.
 | ||||||
|  | //
 | ||||||
|  | // An empty Namespace implies a "http://www.w3.org/1999/xhtml" namespace.
 | ||||||
|  | // Similarly, "math" is short for "http://www.w3.org/1998/Math/MathML", and
 | ||||||
|  | // "svg" is short for "http://www.w3.org/2000/svg".
 | ||||||
|  | type Node struct { | ||||||
|  | 	Parent, FirstChild, LastChild, PrevSibling, NextSibling *Node | ||||||
|  | 
 | ||||||
|  | 	Type      NodeType | ||||||
|  | 	DataAtom  atom.Atom | ||||||
|  | 	Data      string | ||||||
|  | 	Namespace string | ||||||
|  | 	Attr      []Attribute | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // InsertBefore inserts newChild as a child of n, immediately before oldChild
 | ||||||
|  | // in the sequence of n's children. oldChild may be nil, in which case newChild
 | ||||||
|  | // is appended to the end of n's children.
 | ||||||
|  | //
 | ||||||
|  | // It will panic if newChild already has a parent or siblings.
 | ||||||
|  | func (n *Node) InsertBefore(newChild, oldChild *Node) { | ||||||
|  | 	if newChild.Parent != nil || newChild.PrevSibling != nil || newChild.NextSibling != nil { | ||||||
|  | 		panic("html: InsertBefore called for an attached child Node") | ||||||
|  | 	} | ||||||
|  | 	var prev, next *Node | ||||||
|  | 	if oldChild != nil { | ||||||
|  | 		prev, next = oldChild.PrevSibling, oldChild | ||||||
|  | 	} else { | ||||||
|  | 		prev = n.LastChild | ||||||
|  | 	} | ||||||
|  | 	if prev != nil { | ||||||
|  | 		prev.NextSibling = newChild | ||||||
|  | 	} else { | ||||||
|  | 		n.FirstChild = newChild | ||||||
|  | 	} | ||||||
|  | 	if next != nil { | ||||||
|  | 		next.PrevSibling = newChild | ||||||
|  | 	} else { | ||||||
|  | 		n.LastChild = newChild | ||||||
|  | 	} | ||||||
|  | 	newChild.Parent = n | ||||||
|  | 	newChild.PrevSibling = prev | ||||||
|  | 	newChild.NextSibling = next | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AppendChild adds a node c as a child of n.
 | ||||||
|  | //
 | ||||||
|  | // It will panic if c already has a parent or siblings.
 | ||||||
|  | func (n *Node) AppendChild(c *Node) { | ||||||
|  | 	if c.Parent != nil || c.PrevSibling != nil || c.NextSibling != nil { | ||||||
|  | 		panic("html: AppendChild called for an attached child Node") | ||||||
|  | 	} | ||||||
|  | 	last := n.LastChild | ||||||
|  | 	if last != nil { | ||||||
|  | 		last.NextSibling = c | ||||||
|  | 	} else { | ||||||
|  | 		n.FirstChild = c | ||||||
|  | 	} | ||||||
|  | 	n.LastChild = c | ||||||
|  | 	c.Parent = n | ||||||
|  | 	c.PrevSibling = last | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RemoveChild removes a node c that is a child of n. Afterwards, c will have
 | ||||||
|  | // no parent and no siblings.
 | ||||||
|  | //
 | ||||||
|  | // It will panic if c's parent is not n.
 | ||||||
|  | func (n *Node) RemoveChild(c *Node) { | ||||||
|  | 	if c.Parent != n { | ||||||
|  | 		panic("html: RemoveChild called for a non-child Node") | ||||||
|  | 	} | ||||||
|  | 	if n.FirstChild == c { | ||||||
|  | 		n.FirstChild = c.NextSibling | ||||||
|  | 	} | ||||||
|  | 	if c.NextSibling != nil { | ||||||
|  | 		c.NextSibling.PrevSibling = c.PrevSibling | ||||||
|  | 	} | ||||||
|  | 	if n.LastChild == c { | ||||||
|  | 		n.LastChild = c.PrevSibling | ||||||
|  | 	} | ||||||
|  | 	if c.PrevSibling != nil { | ||||||
|  | 		c.PrevSibling.NextSibling = c.NextSibling | ||||||
|  | 	} | ||||||
|  | 	c.Parent = nil | ||||||
|  | 	c.PrevSibling = nil | ||||||
|  | 	c.NextSibling = nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // reparentChildren reparents all of src's child nodes to dst.
 | ||||||
|  | func reparentChildren(dst, src *Node) { | ||||||
|  | 	for { | ||||||
|  | 		child := src.FirstChild | ||||||
|  | 		if child == nil { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		src.RemoveChild(child) | ||||||
|  | 		dst.AppendChild(child) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // clone returns a new node with the same type, data and attributes.
 | ||||||
|  | // The clone has no parent, no siblings and no children.
 | ||||||
|  | func (n *Node) clone() *Node { | ||||||
|  | 	m := &Node{ | ||||||
|  | 		Type:     n.Type, | ||||||
|  | 		DataAtom: n.DataAtom, | ||||||
|  | 		Data:     n.Data, | ||||||
|  | 		Attr:     make([]Attribute, len(n.Attr)), | ||||||
|  | 	} | ||||||
|  | 	copy(m.Attr, n.Attr) | ||||||
|  | 	return m | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // nodeStack is a stack of nodes.
 | ||||||
|  | type nodeStack []*Node | ||||||
|  | 
 | ||||||
|  | // pop pops the stack. It will panic if s is empty.
 | ||||||
|  | func (s *nodeStack) pop() *Node { | ||||||
|  | 	i := len(*s) | ||||||
|  | 	n := (*s)[i-1] | ||||||
|  | 	*s = (*s)[:i-1] | ||||||
|  | 	return n | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // top returns the most recently pushed node, or nil if s is empty.
 | ||||||
|  | func (s *nodeStack) top() *Node { | ||||||
|  | 	if i := len(*s); i > 0 { | ||||||
|  | 		return (*s)[i-1] | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // index returns the index of the top-most occurrence of n in the stack, or -1
 | ||||||
|  | // if n is not present.
 | ||||||
|  | func (s *nodeStack) index(n *Node) int { | ||||||
|  | 	for i := len(*s) - 1; i >= 0; i-- { | ||||||
|  | 		if (*s)[i] == n { | ||||||
|  | 			return i | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return -1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // contains returns whether a is within s.
 | ||||||
|  | func (s *nodeStack) contains(a atom.Atom) bool { | ||||||
|  | 	for _, n := range *s { | ||||||
|  | 		if n.DataAtom == a && n.Namespace == "" { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // insert inserts a node at the given index.
 | ||||||
|  | func (s *nodeStack) insert(i int, n *Node) { | ||||||
|  | 	(*s) = append(*s, nil) | ||||||
|  | 	copy((*s)[i+1:], (*s)[i:]) | ||||||
|  | 	(*s)[i] = n | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // remove removes a node from the stack. It is a no-op if n is not present.
 | ||||||
|  | func (s *nodeStack) remove(n *Node) { | ||||||
|  | 	i := s.index(n) | ||||||
|  | 	if i == -1 { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	copy((*s)[i:], (*s)[i+1:]) | ||||||
|  | 	j := len(*s) - 1 | ||||||
|  | 	(*s)[j] = nil | ||||||
|  | 	*s = (*s)[:j] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type insertionModeStack []insertionMode | ||||||
|  | 
 | ||||||
|  | func (s *insertionModeStack) pop() (im insertionMode) { | ||||||
|  | 	i := len(*s) | ||||||
|  | 	im = (*s)[i-1] | ||||||
|  | 	*s = (*s)[:i-1] | ||||||
|  | 	return im | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *insertionModeStack) top() insertionMode { | ||||||
|  | 	if i := len(*s); i > 0 { | ||||||
|  | 		return (*s)[i-1] | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,293 @@ | ||||||
|  | // Copyright 2011 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 html | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bufio" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type writer interface { | ||||||
|  | 	io.Writer | ||||||
|  | 	io.ByteWriter | ||||||
|  | 	WriteString(string) (int, error) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Render renders the parse tree n to the given writer.
 | ||||||
|  | //
 | ||||||
|  | // Rendering is done on a 'best effort' basis: calling Parse on the output of
 | ||||||
|  | // Render will always result in something similar to the original tree, but it
 | ||||||
|  | // is not necessarily an exact clone unless the original tree was 'well-formed'.
 | ||||||
|  | // 'Well-formed' is not easily specified; the HTML5 specification is
 | ||||||
|  | // complicated.
 | ||||||
|  | //
 | ||||||
|  | // Calling Parse on arbitrary input typically results in a 'well-formed' parse
 | ||||||
|  | // tree. However, it is possible for Parse to yield a 'badly-formed' parse tree.
 | ||||||
|  | // For example, in a 'well-formed' parse tree, no <a> element is a child of
 | ||||||
|  | // another <a> element: parsing "<a><a>" results in two sibling elements.
 | ||||||
|  | // Similarly, in a 'well-formed' parse tree, no <a> element is a child of a
 | ||||||
|  | // <table> element: parsing "<p><table><a>" results in a <p> with two sibling
 | ||||||
|  | // children; the <a> is reparented to the <table>'s parent. However, calling
 | ||||||
|  | // Parse on "<a><table><a>" does not return an error, but the result has an <a>
 | ||||||
|  | // element with an <a> child, and is therefore not 'well-formed'.
 | ||||||
|  | //
 | ||||||
|  | // Programmatically constructed trees are typically also 'well-formed', but it
 | ||||||
|  | // is possible to construct a tree that looks innocuous but, when rendered and
 | ||||||
|  | // re-parsed, results in a different tree. A simple example is that a solitary
 | ||||||
|  | // text node would become a tree containing <html>, <head> and <body> elements.
 | ||||||
|  | // Another example is that the programmatic equivalent of "a<head>b</head>c"
 | ||||||
|  | // becomes "<html><head><head/><body>abc</body></html>".
 | ||||||
|  | func Render(w io.Writer, n *Node) error { | ||||||
|  | 	if x, ok := w.(writer); ok { | ||||||
|  | 		return render(x, n) | ||||||
|  | 	} | ||||||
|  | 	buf := bufio.NewWriter(w) | ||||||
|  | 	if err := render(buf, n); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return buf.Flush() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // plaintextAbort is returned from render1 when a <plaintext> element
 | ||||||
|  | // has been rendered. No more end tags should be rendered after that.
 | ||||||
|  | var plaintextAbort = errors.New("html: internal error (plaintext abort)") | ||||||
|  | 
 | ||||||
|  | func render(w writer, n *Node) error { | ||||||
|  | 	err := render1(w, n) | ||||||
|  | 	if err == plaintextAbort { | ||||||
|  | 		err = nil | ||||||
|  | 	} | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func render1(w writer, n *Node) error { | ||||||
|  | 	// Render non-element nodes; these are the easy cases.
 | ||||||
|  | 	switch n.Type { | ||||||
|  | 	case ErrorNode: | ||||||
|  | 		return errors.New("html: cannot render an ErrorNode node") | ||||||
|  | 	case TextNode: | ||||||
|  | 		return escape(w, n.Data) | ||||||
|  | 	case DocumentNode: | ||||||
|  | 		for c := n.FirstChild; c != nil; c = c.NextSibling { | ||||||
|  | 			if err := render1(w, c); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	case ElementNode: | ||||||
|  | 		// No-op.
 | ||||||
|  | 	case CommentNode: | ||||||
|  | 		if _, err := w.WriteString("<!--"); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		if err := escapeComment(w, n.Data); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		if _, err := w.WriteString("-->"); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	case DoctypeNode: | ||||||
|  | 		if _, err := w.WriteString("<!DOCTYPE "); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		if err := escape(w, n.Data); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		if n.Attr != nil { | ||||||
|  | 			var p, s string | ||||||
|  | 			for _, a := range n.Attr { | ||||||
|  | 				switch a.Key { | ||||||
|  | 				case "public": | ||||||
|  | 					p = a.Val | ||||||
|  | 				case "system": | ||||||
|  | 					s = a.Val | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			if p != "" { | ||||||
|  | 				if _, err := w.WriteString(" PUBLIC "); err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 				if err := writeQuoted(w, p); err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 				if s != "" { | ||||||
|  | 					if err := w.WriteByte(' '); err != nil { | ||||||
|  | 						return err | ||||||
|  | 					} | ||||||
|  | 					if err := writeQuoted(w, s); err != nil { | ||||||
|  | 						return err | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} else if s != "" { | ||||||
|  | 				if _, err := w.WriteString(" SYSTEM "); err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 				if err := writeQuoted(w, s); err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return w.WriteByte('>') | ||||||
|  | 	case RawNode: | ||||||
|  | 		_, err := w.WriteString(n.Data) | ||||||
|  | 		return err | ||||||
|  | 	default: | ||||||
|  | 		return errors.New("html: unknown node type") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Render the <xxx> opening tag.
 | ||||||
|  | 	if err := w.WriteByte('<'); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if _, err := w.WriteString(n.Data); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	for _, a := range n.Attr { | ||||||
|  | 		if err := w.WriteByte(' '); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		if a.Namespace != "" { | ||||||
|  | 			if _, err := w.WriteString(a.Namespace); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			if err := w.WriteByte(':'); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if _, err := w.WriteString(a.Key); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		if _, err := w.WriteString(`="`); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		if err := escape(w, a.Val); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		if err := w.WriteByte('"'); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if voidElements[n.Data] { | ||||||
|  | 		if n.FirstChild != nil { | ||||||
|  | 			return fmt.Errorf("html: void element <%s> has child nodes", n.Data) | ||||||
|  | 		} | ||||||
|  | 		_, err := w.WriteString("/>") | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if err := w.WriteByte('>'); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Add initial newline where there is danger of a newline beging ignored.
 | ||||||
|  | 	if c := n.FirstChild; c != nil && c.Type == TextNode && strings.HasPrefix(c.Data, "\n") { | ||||||
|  | 		switch n.Data { | ||||||
|  | 		case "pre", "listing", "textarea": | ||||||
|  | 			if err := w.WriteByte('\n'); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Render any child nodes
 | ||||||
|  | 	if childTextNodesAreLiteral(n) { | ||||||
|  | 		for c := n.FirstChild; c != nil; c = c.NextSibling { | ||||||
|  | 			if c.Type == TextNode { | ||||||
|  | 				if _, err := w.WriteString(c.Data); err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				if err := render1(w, c); err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if n.Data == "plaintext" { | ||||||
|  | 			// Don't render anything else. <plaintext> must be the
 | ||||||
|  | 			// last element in the file, with no closing tag.
 | ||||||
|  | 			return plaintextAbort | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		for c := n.FirstChild; c != nil; c = c.NextSibling { | ||||||
|  | 			if err := render1(w, c); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Render the </xxx> closing tag.
 | ||||||
|  | 	if _, err := w.WriteString("</"); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if _, err := w.WriteString(n.Data); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return w.WriteByte('>') | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func childTextNodesAreLiteral(n *Node) bool { | ||||||
|  | 	// Per WHATWG HTML 13.3, if the parent of the current node is a style,
 | ||||||
|  | 	// script, xmp, iframe, noembed, noframes, or plaintext element, and the
 | ||||||
|  | 	// current node is a text node, append the value of the node's data
 | ||||||
|  | 	// literally. The specification is not explicit about it, but we only
 | ||||||
|  | 	// enforce this if we are in the HTML namespace (i.e. when the namespace is
 | ||||||
|  | 	// "").
 | ||||||
|  | 	// NOTE: we also always include noscript elements, although the
 | ||||||
|  | 	// specification states that they should only be rendered as such if
 | ||||||
|  | 	// scripting is enabled for the node (which is not something we track).
 | ||||||
|  | 	if n.Namespace != "" { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	switch n.Data { | ||||||
|  | 	case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp": | ||||||
|  | 		return true | ||||||
|  | 	default: | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // writeQuoted writes s to w surrounded by quotes. Normally it will use double
 | ||||||
|  | // quotes, but if s contains a double quote, it will use single quotes.
 | ||||||
|  | // It is used for writing the identifiers in a doctype declaration.
 | ||||||
|  | // In valid HTML, they can't contain both types of quotes.
 | ||||||
|  | func writeQuoted(w writer, s string) error { | ||||||
|  | 	var q byte = '"' | ||||||
|  | 	if strings.Contains(s, `"`) { | ||||||
|  | 		q = '\'' | ||||||
|  | 	} | ||||||
|  | 	if err := w.WriteByte(q); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if _, err := w.WriteString(s); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if err := w.WriteByte(q); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Section 12.1.2, "Elements", gives this list of void elements. Void elements
 | ||||||
|  | // are those that can't have any contents.
 | ||||||
|  | var voidElements = map[string]bool{ | ||||||
|  | 	"area":   true, | ||||||
|  | 	"base":   true, | ||||||
|  | 	"br":     true, | ||||||
|  | 	"col":    true, | ||||||
|  | 	"embed":  true, | ||||||
|  | 	"hr":     true, | ||||||
|  | 	"img":    true, | ||||||
|  | 	"input":  true, | ||||||
|  | 	"keygen": true, // "keygen" has been removed from the spec, but are kept here for backwards compatibility.
 | ||||||
|  | 	"link":   true, | ||||||
|  | 	"meta":   true, | ||||||
|  | 	"param":  true, | ||||||
|  | 	"source": true, | ||||||
|  | 	"track":  true, | ||||||
|  | 	"wbr":    true, | ||||||
|  | } | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -20,41 +20,44 @@ import ( | ||||||
| // TODO: Benchmark to determine if the pools are necessary. The GC may have
 | // TODO: Benchmark to determine if the pools are necessary. The GC may have
 | ||||||
| // improved enough that we can instead allocate chunks like this:
 | // improved enough that we can instead allocate chunks like this:
 | ||||||
| // make([]byte, max(16<<10, expectedBytesRemaining))
 | // make([]byte, max(16<<10, expectedBytesRemaining))
 | ||||||
| var ( | var dataChunkPools = [...]sync.Pool{ | ||||||
| 	dataChunkSizeClasses = []int{ | 	{New: func() interface{} { return new([1 << 10]byte) }}, | ||||||
| 		1 << 10, | 	{New: func() interface{} { return new([2 << 10]byte) }}, | ||||||
| 		2 << 10, | 	{New: func() interface{} { return new([4 << 10]byte) }}, | ||||||
| 		4 << 10, | 	{New: func() interface{} { return new([8 << 10]byte) }}, | ||||||
| 		8 << 10, | 	{New: func() interface{} { return new([16 << 10]byte) }}, | ||||||
| 		16 << 10, | } | ||||||
| 	} |  | ||||||
| 	dataChunkPools = [...]sync.Pool{ |  | ||||||
| 		{New: func() interface{} { return make([]byte, 1<<10) }}, |  | ||||||
| 		{New: func() interface{} { return make([]byte, 2<<10) }}, |  | ||||||
| 		{New: func() interface{} { return make([]byte, 4<<10) }}, |  | ||||||
| 		{New: func() interface{} { return make([]byte, 8<<10) }}, |  | ||||||
| 		{New: func() interface{} { return make([]byte, 16<<10) }}, |  | ||||||
| 	} |  | ||||||
| ) |  | ||||||
| 
 | 
 | ||||||
| func getDataBufferChunk(size int64) []byte { | func getDataBufferChunk(size int64) []byte { | ||||||
| 	i := 0 | 	switch { | ||||||
| 	for ; i < len(dataChunkSizeClasses)-1; i++ { | 	case size <= 1<<10: | ||||||
| 		if size <= int64(dataChunkSizeClasses[i]) { | 		return dataChunkPools[0].Get().(*[1 << 10]byte)[:] | ||||||
| 			break | 	case size <= 2<<10: | ||||||
| 		} | 		return dataChunkPools[1].Get().(*[2 << 10]byte)[:] | ||||||
|  | 	case size <= 4<<10: | ||||||
|  | 		return dataChunkPools[2].Get().(*[4 << 10]byte)[:] | ||||||
|  | 	case size <= 8<<10: | ||||||
|  | 		return dataChunkPools[3].Get().(*[8 << 10]byte)[:] | ||||||
|  | 	default: | ||||||
|  | 		return dataChunkPools[4].Get().(*[16 << 10]byte)[:] | ||||||
| 	} | 	} | ||||||
| 	return dataChunkPools[i].Get().([]byte) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func putDataBufferChunk(p []byte) { | func putDataBufferChunk(p []byte) { | ||||||
| 	for i, n := range dataChunkSizeClasses { | 	switch len(p) { | ||||||
| 		if len(p) == n { | 	case 1 << 10: | ||||||
| 			dataChunkPools[i].Put(p) | 		dataChunkPools[0].Put((*[1 << 10]byte)(p)) | ||||||
| 			return | 	case 2 << 10: | ||||||
| 		} | 		dataChunkPools[1].Put((*[2 << 10]byte)(p)) | ||||||
|  | 	case 4 << 10: | ||||||
|  | 		dataChunkPools[2].Put((*[4 << 10]byte)(p)) | ||||||
|  | 	case 8 << 10: | ||||||
|  | 		dataChunkPools[3].Put((*[8 << 10]byte)(p)) | ||||||
|  | 	case 16 << 10: | ||||||
|  | 		dataChunkPools[4].Put((*[16 << 10]byte)(p)) | ||||||
|  | 	default: | ||||||
|  | 		panic(fmt.Sprintf("unexpected buffer len=%v", len(p))) | ||||||
| 	} | 	} | ||||||
| 	panic(fmt.Sprintf("unexpected buffer len=%v", len(p))) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // dataBuffer is an io.ReadWriter backed by a list of data chunks.
 | // dataBuffer is an io.ReadWriter backed by a list of data chunks.
 | ||||||
|  |  | ||||||
|  | @ -1,30 +0,0 @@ | ||||||
| // Copyright 2018 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 go1.11
 |  | ||||||
| // +build go1.11
 |  | ||||||
| 
 |  | ||||||
| package http2 |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"net/http/httptrace" |  | ||||||
| 	"net/textproto" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func traceHasWroteHeaderField(trace *httptrace.ClientTrace) bool { |  | ||||||
| 	return trace != nil && trace.WroteHeaderField != nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func traceWroteHeaderField(trace *httptrace.ClientTrace, k, v string) { |  | ||||||
| 	if trace != nil && trace.WroteHeaderField != nil { |  | ||||||
| 		trace.WroteHeaderField(k, []string{v}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func traceGot1xxResponseFunc(trace *httptrace.ClientTrace) func(int, textproto.MIMEHeader) error { |  | ||||||
| 	if trace != nil { |  | ||||||
| 		return trace.Got1xxResponse |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue